基础数据结构 2.3 栈的应用与实现

1. 栈的定义

栈(Stack)是一种后进先出(LIFO, Last In First Out)的数据结构。它的基本操作包括入栈(push)、出栈(pop)和查看栈顶元素(peek)。栈的特点是只能在一端进行插入和删除操作,这使得栈在某些特定场景下非常有用。

1.1 栈的基本操作

  • 入栈(Push):将一个元素添加到栈顶。
  • 出栈(Pop):移除并返回栈顶元素。
  • 查看栈顶元素(Peek):返回栈顶元素但不移除它。
  • 判断栈是否为空(IsEmpty):检查栈中是否有元素。

2. 栈的实现

栈可以通过数组或链表来实现。下面分别介绍这两种实现方式。

2.1 数组实现栈

使用数组实现栈的基本思路是维护一个数组和一个指向栈顶的指针。以下是用 Python 实现的栈类:

class ArrayStack:
    def __init__(self, capacity=10):
        self.capacity = capacity
        self.stack = [None] * capacity
        self.top = -1  # 栈顶指针

    def is_empty(self):
        return self.top == -1

    def is_full(self):
        return self.top == self.capacity - 1

    def push(self, item):
        if self.is_full():
            raise Exception("Stack is full")
        self.top += 1
        self.stack[self.top] = item

    def pop(self):
        if self.is_empty():
            raise Exception("Stack is empty")
        item = self.stack[self.top]
        self.stack[self.top] = None  # 清空栈顶元素
        self.top -= 1
        return item

    def peek(self):
        if self.is_empty():
            raise Exception("Stack is empty")
        return self.stack[self.top]

    def size(self):
        return self.top + 1

    def __str__(self):
        return str(self.stack[:self.top + 1])

优点

  • 快速访问:由于数组的随机访问特性,栈顶元素的访问速度非常快。
  • 内存局部性:数组在内存中是连续存储的,具有良好的缓存性能。

缺点

  • 固定大小:栈的大小在创建时就固定,可能会导致空间浪费或溢出。
  • 扩展性差:如果需要动态扩展,必须手动实现扩展逻辑。

2.2 链表实现栈

链表实现栈的基本思路是使用节点(Node)来存储数据,每个节点指向下一个节点。以下是用 Python 实现的链表栈类:

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

class LinkedListStack:
    def __init__(self):
        self.top = None  # 栈顶指针
        self.size = 0

    def is_empty(self):
        return self.top is None

    def push(self, item):
        new_node = Node(item)
        new_node.next = self.top
        self.top = new_node
        self.size += 1

    def pop(self):
        if self.is_empty():
            raise Exception("Stack is empty")
        item = self.top.data
        self.top = self.top.next
        self.size -= 1
        return item

    def peek(self):
        if self.is_empty():
            raise Exception("Stack is empty")
        return self.top.data

    def get_size(self):
        return self.size

    def __str__(self):
        current = self.top
        elements = []
        while current:
            elements.append(current.data)
            current = current.next
        return str(elements)

优点

  • 动态大小:链表可以根据需要动态扩展,避免了固定大小的限制。
  • 内存利用率高:只在需要时分配内存,避免了空间浪费。

缺点

  • 额外的内存开销:每个节点需要额外存储指针,导致内存开销增加。
  • 访问速度较慢:由于链表的非连续存储,访问速度可能不如数组快。

3. 栈的应用

栈在计算机科学中有广泛的应用,以下是一些常见的应用场景:

3.1 表达式求值

栈常用于求解中缀表达式、后缀表达式(逆波兰表示法)等。通过使用栈,可以有效地处理运算符的优先级和括号匹配。

示例代码:后缀表达式求值

def evaluate_postfix(expression):
    stack = ArrayStack()
    for token in expression.split():
        if token.isdigit():
            stack.push(int(token))
        else:
            b = stack.pop()
            a = stack.pop()
            if token == '+':
                stack.push(a + b)
            elif token == '-':
                stack.push(a - b)
            elif token == '*':
                stack.push(a * b)
            elif token == '/':
                stack.push(a / b)
    return stack.pop()

# 示例
expression = "3 4 + 2 * 7 /"
result = evaluate_postfix(expression)
print(f"后缀表达式 {expression} 的结果是: {result}")

3.2 括号匹配

在编译器中,栈用于检查括号是否匹配。通过遍历字符串,将左括号入栈,遇到右括号时出栈进行匹配。

示例代码:括号匹配

def is_balanced(expression):
    stack = ArrayStack()
    brackets = {')': '(', '}': '{', ']': '['}
    for char in expression:
        if char in brackets.values():
            stack.push(char)
        elif char in brackets.keys():
            if stack.is_empty() or stack.pop() != brackets[char]:
                return False
    return stack.is_empty()

# 示例
expression = "{[()()]}"
print(f"表达式 {expression} 是否平衡: {is_balanced(expression)}")

3.3 深度优先搜索(DFS)

在图的遍历中,栈可以用于实现深度优先搜索。通过使用栈来存储待访问的节点,可以有效地实现图的遍历。

示例代码:深度优先搜索

def dfs(graph, start):
    stack = ArrayStack()
    visited = set()
    stack.push(start)

    while not stack.is_empty():
        vertex = stack.pop()
        if vertex not in visited:
            visited.add(vertex)
            print(vertex, end=' ')
            for neighbor in graph[vertex]:
                stack.push(neighbor)

# 示例图
graph = {
    'A': ['B', 'C'],
    'B': ['D', 'E'],
    'C': ['F'],
    'D': [],
    'E': ['F'],
    'F': []
}

print("深度优先搜索结果:")
dfs(graph, 'A')

4. 注意事项

  1. 栈溢出:在使用数组实现栈时,需注意栈的大小限制,避免栈溢出。
  2. 内存管理:在链表实现中,需注意内存的分配和释放,避免内存泄漏。
  3. 异常处理:在出栈和查看栈顶元素时,需处理栈空的异常情况。
  4. 性能考虑:在选择栈的实现方式时,需根据具体应用场景考虑性能和内存使用。

5. 总结

栈是一种重要的基础数据结构,具有简单而强大的特性。通过理解栈的基本操作及其实现方式,我们可以在多种场景中有效地应用栈。无论是表达式求值、括号匹配还是图的遍历,栈都能提供优雅的解决方案。希望本教程能帮助你深入理解栈的应用与实现。