Skip to content

Commit b1ac8a2

Browse files
chore: new article written
1 parent 1dacd3a commit b1ac8a2

File tree

1 file changed

+161
-0
lines changed

1 file changed

+161
-0
lines changed
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
---
2+
title: "深入理解并实现基本的栈(Stack)数据结构"
3+
author: "李睿远"
4+
date: "Sep 11, 2025"
5+
description: "栈数据结构理论实现与应用"
6+
latex: true
7+
pdf: true
8+
---
9+
10+
11+
栈是计算机科学中最基础且无处不在的数据结构之一。想象一下日常生活中叠盘子的场景:我们总是将新盘子放在最上面,取用时也从最上面开始拿。这种后进先出的行为正是栈的核心理念。在计算机领域,栈的应用广泛而关键,例如函数调用栈管理着程序的执行流程,浏览器中的「后退」按钮依赖于栈来记录历史页面,甚至表达式求值和撤销操作都离不开栈的支持。本文将带领您从理论层面深入理解栈的概念,并通过代码实现两种常见的栈结构,最后探讨其经典应用场景。
12+
13+
## 栈的核心概念剖析
14+
15+
栈是一种线性数据结构,其操作遵循后进先出(LIFO, Last-In, First-Out)原则。这意味着最后一个被添加的元素将是第一个被移除的。栈的核心操作包括入栈(Push)和出栈(Pop)。入栈操作将一个新元素添加到栈顶,而出栈操作则移除并返回栈顶元素。此外,栈还支持一些辅助操作,例如查看栈顶元素(peek 或 top)、检查栈是否为空(isEmpty)、检查栈是否已满(isFull,仅适用于基于数组的实现)以及获取栈的大小(size)。从抽象数据类型(ADT)的角度来看,栈可以定义为支持这些操作的一个集合,其接口确保了数据访问的严格顺序性。
16+
17+
## 栈的实现方式(一):基于数组
18+
19+
基于数组的实现是栈的一种常见方式,其思路是使用一个固定大小的数组来存储元素,并通过一个称为 top 的指针来跟踪栈顶的位置。初始化时,top 通常设置为 -1,表示栈为空。当执行入栈操作时,top 递增,并将新元素存储在数组的相应索引处;出栈操作则返回 top 指向的元素,并将 top 递减。这种实现的关键在于处理边界情况,例如当栈为空时尝试出栈会引发错误,而当栈满时尝试入栈会导致溢出。
20+
21+
以下是一个用 Python 实现的基于数组的栈示例代码:
22+
23+
```python
24+
class ArrayStack:
25+
def __init__(self, capacity):
26+
self.capacity = capacity
27+
self.stack = [None] * capacity
28+
self.top = -1
29+
30+
def push(self, item):
31+
if self.is_full():
32+
raise Exception("Stack is full")
33+
self.top += 1
34+
self.stack[self.top] = item
35+
36+
def pop(self):
37+
if self.is_empty():
38+
raise Exception("Stack is empty")
39+
item = self.stack[self.top]
40+
self.top -= 1
41+
return item
42+
43+
def peek(self):
44+
if self.is_empty():
45+
raise Exception("Stack is empty")
46+
return self.stack[self.top]
47+
48+
def is_empty(self):
49+
return self.top == -1
50+
51+
def is_full(self):
52+
return self.top == self.capacity - 1
53+
54+
def size(self):
55+
return self.top + 1
56+
```
57+
58+
在这段代码中,我们定义了一个 ArrayStack 类,其初始化方法接受一个容量参数,并创建一个相应大小的数组。push 方法首先检查栈是否已满,如果未满,则递增 top 并将元素存入数组;pop 方法检查栈是否为空,然后返回栈顶元素并递减 top。peek 方法类似,但不修改栈。所有核心操作的时间复杂度均为 O(1),因为它们只涉及简单的索引操作。空间复杂度为 O(n),其中 n 是数组的容量。基于数组的栈实现简单高效,但缺点在于容量固定,可能发生栈溢出。
59+
60+
## 栈的实现方式(二):基于链表
61+
62+
基于链表的栈实现提供了动态扩容的能力,其思路是使用单链表来存储元素,并将链表的头部作为栈顶。这样,入栈操作相当于在链表头部插入新节点,出栈操作则是移除头部节点。这种实现无需预先分配固定大小,因此更适合不确定数据量的场景。每个节点包含数据和指向下一个节点的指针,栈本身只需维护一个指向头部的指针。
63+
64+
以下是一个用 Python 实现的基于链表的栈示例代码:
65+
66+
```python
67+
class Node:
68+
def __init__(self, data):
69+
self.data = data
70+
self.next = None
71+
72+
class LinkedListStack:
73+
def __init__(self):
74+
self.head = None
75+
76+
def push(self, item):
77+
new_node = Node(item)
78+
new_node.next = self.head
79+
self.head = new_node
80+
81+
def pop(self):
82+
if self.is_empty():
83+
raise Exception("Stack is empty")
84+
item = self.head.data
85+
self.head = self.head.next
86+
return item
87+
88+
def peek(self):
89+
if self.is_empty():
90+
raise Exception("Stack is empty")
91+
return self.head.data
92+
93+
def is_empty(self):
94+
return self.head is None
95+
96+
def size(self):
97+
count = 0
98+
current = self.head
99+
while current:
100+
count += 1
101+
current = current.next
102+
return count
103+
```
104+
105+
在这段代码中,我们首先定义了一个 Node 类来表示链表节点,每个节点包含数据和一个指向下一个节点的指针。LinkedListStack 类的初始化方法将 head 指针设为 None,表示空栈。push 方法创建新节点并将其插入到链表头部;pop 方法检查栈是否为空,然后返回头部数据并更新 head 指针。peek 方法类似,但不修改链表。所有核心操作的时间复杂度均为 O(1),因为链表头部的操作是常数时间的。空间复杂度为 O(n),每个元素需要额外的指针空间。基于链表的栈优点在于动态容量,但缺点是需要更多内存用于指针,实现稍复杂。
106+
107+
## 两种实现方式的对比与选择
108+
109+
在选择栈的实现方式时,需要根据应用场景权衡利弊。数组实现具有固定容量,性能稳定且无内存分配开销,但可能发生栈溢出;链表实现则支持动态扩容,无需担心栈满,但每个元素需要额外指针空间,且操作可能有微小内存分配开销。总体而言,如果能预估数据量上限且追求极致性能,数组实现是更好的选择;如果需要处理不确定大小的数据,链表实现更灵活。在实际开发中,还应考虑语言特性和库支持,例如在 Python 中,列表本身就可以模拟栈,但理解底层实现有助于优化和调试。
110+
111+
## 栈的经典应用场景实战
112+
113+
栈在计算机科学中有许多经典应用,其中之一是括号匹配检查。这个问题要求检查一个字符串中的括号(如 `()`, `[]`, `{}`)是否正确匹配和闭合。算法思路是遍历字符串,遇到左括号时入栈,遇到右括号时与栈顶左括号匹配,如果匹配则出栈,否则返回错误。最终,栈应为空表示所有括号匹配。以下是核心代码片段:
114+
115+
```python
116+
def is_balanced(expression):
117+
stack = []
118+
mapping = {')': '(', ']': '[', '}': '{'}
119+
for char in expression:
120+
if char in mapping.values():
121+
stack.append(char)
122+
elif char in mapping.keys():
123+
if not stack or stack.pop() != mapping[char]:
124+
return False
125+
return not stack
126+
```
127+
128+
这段代码使用一个列表模拟栈,遍历表达式时处理括号。时间复杂度为 O(n),其中 n 是表达式长度。
129+
130+
另一个应用是表达式求值, specifically 逆波兰表达式(后缀表达式)。例如,表达式 `["2", "1", "+", "3", "*"]` 等价于 `(2 + 1) * 3`。算法思路是遍历表达式,遇到数字时入栈,遇到运算符时弹出两个操作数进行计算,并将结果入栈。最终栈顶即为结果。以下是核心代码片段:
131+
132+
```python
133+
def eval_rpn(tokens):
134+
stack = []
135+
for token in tokens:
136+
if token in "+-*/":
137+
b = stack.pop()
138+
a = stack.pop()
139+
if token == '+':
140+
stack.append(a + b)
141+
elif token == '-':
142+
stack.append(a - b)
143+
elif token == '*':
144+
stack.append(a * b)
145+
elif token == '/':
146+
stack.append(int(a / b))
147+
else:
148+
stack.append(int(token))
149+
return stack.pop()
150+
```
151+
152+
这段代码处理数字和运算符,利用栈进行中间结果存储。时间复杂度为 O(n)。
153+
154+
栈还广泛应用于函数调用栈(Call Stack),这是编程语言中管理函数调用、局部变量和返回地址的核心机制。每当函数被调用时,其信息被压入栈;函数返回时,信息被弹出。这确保了程序的正确执行流程,是栈最基础的应用之一。
155+
156+
157+
通过本文,我们深入探讨了栈的核心特性、两种实现方式及其经典应用。栈作为一种后进先出的数据结构,在计算机科学中扮演着不可或缺的角色。基于数组的实现简单高效,适合固定容量场景;基于链表的实现动态灵活,适合不确定数据量的情况。经典应用如括号匹配和表达式求值展示了栈的实际价值。作为进阶思考,读者可以尝试用栈来实现队列,或者设计一个支持 O(1) 时间获取最小元素的栈(Min Stack)。此外,栈内存与堆内存的区别以及深度优先搜索(DFS)算法中栈的应用也是值得延伸阅读的主题。
158+
159+
## 互动环节
160+
161+
欢迎读者在评论区分享您对栈的理解或实现代码。例如,您可以尝试用栈来反转一个字符串:遍历字符串并将每个字符入栈,然后出栈即可得到反转结果。这是一个简单的练习,有助于巩固栈的操作。如果您有任何问题或想法,请随时留言讨论!

0 commit comments

Comments
 (0)