Menu
CoddyTech

Ngăn xếp (stack)

Cập nhật lần cuối

Ngăn xếp (stack) là một tập hợp chỉ có đúng một đầu mở. Bạn thêm một giá trị bằng cách push nó lên đỉnh, và lấy một giá trị ra bằng cách pop phần tử ở đỉnh, nên giá trị vào sau cùng luôn ra đầu tiên. Đó là ý nghĩa của LIFO, và đó là toàn bộ quy tắc: không có cách nào chạm vào phần giữa nếu chưa lấy ra những gì nằm phía trên. Nhấn Phát ở trên và xem cột cao dần sau mỗi lần push và thấp dần từ cùng đầu đó sau mỗi lần pop.

Chính giới hạn đó là mấu chốt. Vì cả hai thao tác chỉ chạm vào đỉnh, mỗi thao tác đều tốn O(1) dù ngăn xếp cao đến đâu, và sự dễ đoán đó là lý do ngăn xếp nằm bên dưới rất nhiều thứ trong máy tính: ngăn xếp lời gọi chạy đệ quy, lịch sử hoàn tác trong trình soạn thảo, việc kiểm tra cặp ngoặc trong bộ phân tích cú pháp, và ngăn xếp tường minh biến tìm kiếm theo chiều sâu đệ quy thành một vòng lặp. Đổi đầu lấy ra và bạn có một hàng đợi.

Độ phức tạp thời gian và không gian

Với ngăn xếp tiêu chuẩn dùng mảng hoặc danh sách liên kết:

Thao tácĐộ phức tạpGhi chú
PushO(1)O(1) khấu hao trên mảng động, thỉnh thoảng phải đổi kích thước.
PopO(1)Luôn là phần tử ở đỉnh, nên không phải dời phần tử.
Peek (đỉnh)O(1)Đọc phần tử ở đỉnh mà không lấy ra.
Tìm kiếmO(n)Không phải việc của ngăn xếp: bạn phải pop dần xuống.
Không gianO(n)Mỗi giá trị được lưu chiếm một ô.

Từng bước

BướcĐiều gì xảy ra
1Ngăn xếp bắt đầu rỗng, đỉnh không trỏ vào đâu cả.
2Push ghi giá trị vào vị trí đỉnh và dời đỉnh lên một ô.
3Mỗi lần push tiếp theo đặt giá trị ngay phía trên giá trị trước đó.
4Pop đọc giá trị ở đỉnh, rồi dời đỉnh xuống một ô.
5Giá trị được trả về luôn là giá trị được push gần nhất.
6Pop một ngăn xếp rỗng là lỗi, gọi là stack underflow, nên code thực tế kiểm tra is_empty() trước.

Ví dụ minh họa

Push 3, 7, 5 rồi lấy hết ngăn xếp ra:

Thao tácNgăn xếp (từ đáy lên đỉnh)Trả về
push(3)[3]không có gì
push(7)[3, 7]không có gì
push(5)[3, 7, 5]không có gì
pop()[3, 7]5, giá trị mới nhất
pop()[3]7
pop()[]3, giá trị cũ nhất, ra sau cùng

Khi nào nên dùng ngăn xếp

Nên dùng khiTránh dùng khi
Bạn cần lấy lại phần tử mới nhất trước: hoàn tác, nút quay lại, kiểm tra cặp ngoặcBạn cần phần tử cũ nhất trước, đó là việc của hàng đợi
Bạn đang chuyển một thuật toán đệ quy thành vòng lặpBạn cần tìm kiếm hoặc truy cập theo chỉ số vào giữa dữ liệu
Bạn đang phân tích cấu trúc lồng nhau như biểu thức, JSON hoặc HTMLNhiều nơi cần truy cập tùy ý, khi đó mảng hoặc map phù hợp hơn
Bạn muốn chèn và lấy ra được bảo đảm O(1), không cần cân bằng lạiBạn cần dữ liệu luôn được sắp xếp, điều mà heap hoặc cây mang lại

Code Stack

Một cách cài đặt Stack gọn gàng, chạy được ngay bằng Python, JavaScript, Java, C++, C. Chọn một ngôn ngữ, sao chép code hoặc mở sẵn trong Coddy Playground.

Code Stack bằng Python

Python
1stack = []2
3# Push three values onto the top4for value in [3, 7, 5]:5    stack.append(value)6    print(f"push {value} -> {stack}")7
8# Pop them back off: last in, first out9while stack:10    value = stack.pop()11    print(f"pop  {value} -> {stack}")12
13print("empty:", len(stack) == 0)
Chạy code này trong Playground Python

Câu hỏi thường gặp về ngăn xếp

LIFO nghĩa là gì?
Last in, first out (vào sau, ra trước): giá trị được push gần nhất là giá trị được pop đầu tiên. Hình ảnh quen thuộc là một chồng đĩa, bạn lấy chiếc đĩa vừa đặt lên, không phải chiếc ở dưới cùng. Hàng đợi theo nguyên tắc ngược lại, FIFO.
Ngăn xếp và hàng đợi khác nhau như thế nào?
Chỉ khác ở đầu mà bạn lấy phần tử ra. Cả hai đều thêm vào một đầu trong O(1); ngăn xếp lấy ra từ chính đầu đó (LIFO), hàng đợi lấy ra từ đầu còn lại (FIFO). Mọi thứ khác, kể cả bảng độ phức tạp ở trên, đều giống hệt nhau.
Các thao tác chính trên ngăn xếp là gì?
push thêm một giá trị lên đỉnh, pop lấy ra và trả về giá trị ở đỉnh, peek (đôi khi là top) đọc giá trị ở đỉnh mà không lấy ra, và is_empty cho biết còn gì không. Cả bốn đều tốn O(1).
Tràn ngăn xếp (stack overflow) là gì?
Là khi push vào một ngăn xếp đã hết chỗ. Trường hợp nổi tiếng là ngăn xếp lời gọi: mỗi lời gọi hàm push một khung, nên một hàm đệ quy không bao giờ chạm điều kiện dừng sẽ push mãi cho đến khi vượt giới hạn ngăn xếp của môi trường chạy và chương trình bị lỗi. Lỗi ngược lại, pop một ngăn xếp rỗng, gọi là stack underflow.
Ngăn xếp được cài đặt như thế nào?
Có hai cách phổ biến. Mảng động push và pop ở cuối, tốn O(1) khấu hao và thân thiện với cache: list của Python và ArrayDeque của Java hoạt động theo cách này. Danh sách liên kết push và pop ở đầu, tốn O(1) cả trong trường hợp xấu nhất, không cần đổi kích thước nhưng mỗi phần tử tốn thêm một con trỏ. std::stack của C++ là một adaptor mặc định chạy trên std::deque, một mảng phân đoạn, và chấp nhận container khác nếu bạn truyền vào.
Ngăn xếp được dùng ở đâu trong các chương trình thực tế?
Ngăn xếp lời gọi cho các lời gọi hàm và đệ quy, lịch sử hoàn tác và làm lại, nút quay lại của trình duyệt, tính giá trị biểu thức và kiểm tra cặp ngoặc trong bộ phân tích cú pháp, và ngăn xếp tường minh chuyển tìm kiếm theo chiều sâu đệ quy thành vòng lặp.
Hình minh họa các ngôn ngữ lập trình của Coddy

Thành thạo thuật toán cùng Coddy

Bắt đầu