Skip to content

4.1 栈的 LIFO 特性与 list 实现

栈是一种后进先出(Last In First Out, LIFO)的数据结构,就像一摞盘子,你只能从最上面拿盘子或者放盘子。在 Python 中,我们可以直接用内置的 list 来实现栈的基本操作。

功能名称实例调用方法具体功能、注意事项、必需参数/可选参数
入栈stack.append(item)将元素添加到栈顶,时间复杂度 O(1)
出栈stack.pop()移除并返回栈顶元素,如果栈为空会抛出 IndexError
查看栈顶stack[-1]返回栈顶元素但不移除,需要先检查栈是否为空
判断空栈len(stack) == 0not stack检查栈是否为空,避免对空栈进行 pop 或访问

下面是一个完整的栈实现示例:

python
# 定义一个简单的栈类
class Stack:
    def __init__(self):
        """初始化空栈"""
        self.items = []
    
    def push(self, item):
        """入栈操作 - 将元素添加到栈顶"""
        self.items.append(item)
    
    def pop(self):
        """出栈操作 - 移除并返回栈顶元素"""
        if self.is_empty():
            raise IndexError("pop from empty stack")
        return self.items.pop()
    
    def peek(self):
        """查看栈顶元素但不移除"""
        if self.is_empty():
            raise IndexError("peek from empty stack")
        return self.items[-1]
    
    def is_empty(self):
        """判断栈是否为空"""
        return len(self.items) == 0
    
    def size(self):
        """返回栈中元素个数"""
        return len(self.items)

# 使用示例
try:
    # 创建栈实例
    stack = Stack()
    
    # 入栈操作
    stack.push(1)
    stack.push(2)
    stack.push(3)
    print(f"栈大小: {stack.size()}")  # 输出: 栈大小: 3
    
    # 查看栈顶
    print(f"栈顶元素: {stack.peek()}")  # 输出: 栈顶元素: 3
    
    # 出栈操作
    print(f"出栈: {stack.pop()}")  # 输出: 出栈: 3
    print(f"出栈: {stack.pop()}")  # 输出: 出栈: 2
    
    # 检查是否为空
    print(f"栈是否为空: {stack.is_empty()}")  # 输出: 栈是否为空: False
    
except IndexError as e:
    print(f"错误: {e}")

使用 Python 的 list 实现栈非常简单高效,因为 append()pop() 操作的时间复杂度都是 O(1)。不过要注意的是,虽然技术上你可以通过索引访问栈中的任意元素(比如 stack[0]),但这违背了栈的抽象数据类型原则,真正的栈应该只允许访问栈顶元素。

4.2 队列的 FIFO 特性与 collections.deque 实现

队列是一种先进先出(First In First Out, FIFO)的数据结构,就像排队买票,先来的人先买到票。在 Python 中,虽然可以用 list 实现队列,但效率不高,因为从列表头部删除元素的时间复杂度是 O(n)。更好的选择是使用 collections.deque

功能名称实例调用方法具体功能、注意事项、必需参数/可选参数
入队queue.append(item)将元素添加到队尾,时间复杂度 O(1)
出队queue.popleft()移除并返回队首元素,如果队列为空会抛出 IndexError
查看队首queue[0]返回队首元素但不移除,需要先检查队列是否为空
判断空队列len(queue) == 0not queue检查队列是否为空

下面是使用 collections.deque 实现队列的示例:

python
from collections import deque

# 定义一个简单的队列类
class Queue:
    def __init__(self):
        """初始化空队列"""
        self.items = deque()
    
    def enqueue(self, item):
        """入队操作 - 将元素添加到队尾"""
        self.items.append(item)
    
    def dequeue(self):
        """出队操作 - 移除并返回队首元素"""
        if self.is_empty():
            raise IndexError("dequeue from empty queue")
        return self.items.popleft()
    
    def front(self):
        """查看队首元素但不移除"""
        if self.is_empty():
            raise IndexError("front from empty queue")
        return self.items[0]
    
    def is_empty(self):
        """判断队列是否为空"""
        return len(self.items) == 0
    
    def size(self):
        """返回队列中元素个数"""
        return len(self.items)

# 使用示例
try:
    # 创建队列实例
    queue = Queue()
    
    # 入队操作
    queue.enqueue("Alice")
    queue.enqueue("Bob")
    queue.enqueue("Charlie")
    print(f"队列大小: {queue.size()}")  # 输出: 队列大小: 3
    
    # 查看队首
    print(f"队首元素: {queue.front()}")  # 输出: 队首元素: Alice
    
    # 出队操作
    print(f"出队: {queue.dequeue()}")  # 输出: 出队: Alice
    print(f"出队: {queue.dequeue()}")  # 输出: 出队: Bob
    
    # 检查是否为空
    print(f"队列是否为空: {queue.is_empty()}")  # 输出: 队列是否为空: False
    
except IndexError as e:
    print(f"错误: {e}")

collections.deque 是专门为高效实现队列操作而设计的,它的 append()popleft() 操作都是 O(1) 时间复杂度,比使用普通 listinsert(0, item)pop(0) 要高效得多。

4.3 单调栈:维护递增/递减序列

单调栈是一种特殊的栈,其中的元素保持单调递增或单调递减的顺序。这种数据结构在解决"下一个更大元素"、"柱状图中最大矩形"等问题时非常有用。

功能名称实例调用方法具体功能、注意事项、必需参数/可选参数
维护单调递增栈循环中比较栈顶与当前元素当前元素小于等于栈顶时入栈,否则弹出栈顶直到满足条件
维护单调递减栈循环中比较栈顶与当前元素当前元素大于等于栈顶时入栈,否则弹出栈顶直到满足条件
获取每个元素的下一个更大元素遍历数组,维护单调递减栈栈中存储索引而非值,便于计算结果

下面是一个经典的"下一个更大元素"问题的解决方案:

python
def next_greater_element(nums):
    """
    找到数组中每个元素的下一个更大元素
    如果不存在则返回-1
    """
    n = len(nums)
    result = [-1] * n  # 初始化结果数组
    stack = []  # 单调递减栈,存储索引
    
    # 遍历数组两次处理循环情况(如果是循环数组)
    for i in range(n):
        # 当栈不为空且当前元素大于栈顶元素对应的值时
        while stack and nums[i] > nums[stack[-1]]:
            # 弹出栈顶索引,并设置其下一个更大元素为当前元素
            index = stack.pop()
            result[index] = nums[i]
        # 将当前索引入栈
        stack.append(i)
    
    return result

# 使用示例
try:
    nums = [2, 1, 2, 4, 3]
    result = next_greater_element(nums)
    print(f"原数组: {nums}")      # 输出: 原数组: [2, 1, 2, 4, 3]
    print(f"下一个更大元素: {result}")  # 输出: 下一个更大元素: [4, 2, 4, -1, -1]
    
    # 测试边界情况
    empty_nums = []
    empty_result = next_greater_element(empty_nums)
    print(f"空数组结果: {empty_result}")  # 输出: 空数组结果: []
    
except Exception as e:
    print(f"错误: {e}")

单调栈的核心思想是在遍历过程中维护栈的单调性。当我们遇到一个破坏单调性的元素时,就意味着找到了某些元素的"答案"。这种方法的时间复杂度是 O(n),因为每个元素最多入栈和出栈各一次。

4.4 循环队列与双端队列(deque)

循环队列是一种特殊的队列,它的尾部连接到头部形成一个环形结构,这样可以更有效地利用固定大小的数组空间。而双端队列(deque)则允许在两端都进行插入和删除操作。

功能名称实例调用方法具体功能、注意事项、必需参数/可选参数
循环队列入队(rear + 1) % capacity需要处理队列满的判断,通常牺牲一个位置
循环队列出队(front + 1) % capacity更新队首指针
双端队列左端插入deque.appendleft(item)在队列左端添加元素
双端队列右端插入deque.append(item)在队列右端添加元素
双端队列两端删除deque.popleft(), deque.pop()分别从左右两端删除元素

首先看循环队列的实现:

python
class CircularQueue:
    def __init__(self, capacity):
        """初始化循环队列,capacity 为队列容量"""
        self.capacity = capacity
        self.queue = [None] * capacity
        self.front = 0  # 队首指针
        self.rear = 0   # 队尾指针
        self.size = 0   # 当前元素个数
    
    def enqueue(self, item):
        """入队操作"""
        if self.is_full():
            raise OverflowError("Queue is full")
        self.queue[self.rear] = item
        self.rear = (self.rear + 1) % self.capacity
        self.size += 1
    
    def dequeue(self):
        """出队操作"""
        if self.is_empty():
            raise IndexError("Queue is empty")
        item = self.queue[self.front]
        self.front = (self.front + 1) % self.capacity
        self.size -= 1
        return item
    
    def is_empty(self):
        """判断队列是否为空"""
        return self.size == 0
    
    def is_full(self):
        """判断队列是否已满"""
        return self.size == self.capacity
    
    def get_size(self):
        """获取队列当前大小"""
        return self.size

# 使用示例
try:
    # 创建容量为3的循环队列
    cq = CircularQueue(3)
    
    # 入队操作
    cq.enqueue(1)
    cq.enqueue(2)
    cq.enqueue(3)
    print(f"队列大小: {cq.get_size()}")  # 输出: 队列大小: 3
    
    # 队列已满,再次入队会报错
    try:
        cq.enqueue(4)
    except OverflowError as e:
        print(f"入队错误: {e}")  # 输出: 入队错误: Queue is full
    
    # 出队操作
    print(f"出队: {cq.dequeue()}")  # 输出: 出队: 1
    print(f"出队: {cq.dequeue()}")  # 输出: 出队: 2
    
    # 现在可以再次入队
    cq.enqueue(4)
    print(f"队列大小: {cq.get_size()}")  # 输出: 队列大小: 2
    
except Exception as e:
    print(f"错误: {e}")

再看双端队列的使用示例:

python
from collections import deque

# 双端队列的完整操作示例
def demonstrate_deque():
    """演示双端队列的各种操作"""
    dq = deque()
    
    # 右端插入
    dq.append(1)
    dq.append(2)
    print(f"右端插入后: {list(dq)}")  # 输出: 右端插入后: [1, 2]
    
    # 左端插入
    dq.appendleft(0)
    dq.appendleft(-1)
    print(f"左端插入后: {list(dq)}")  # 输出: 左端插入后: [-1, 0, 1, 2]
    
    # 右端删除
    right_item = dq.pop()
    print(f"右端删除: {right_item}, 队列: {list(dq)}")  # 输出: 右端删除: 2, 队列: [-1, 0, 1]
    
    # 左端删除
    left_item = dq.popleft()
    print(f"左端删除: {left_item}, 队列: {list(dq)}")  # 输出: 左端删除: -1, 队列: [0, 1]
    
    # 访问两端元素
    if dq:
        print(f"左端元素: {dq[0]}, 右端元素: {dq[-1]}")  # 输出: 左端元素: 0, 右端元素: 1
    
    return dq

# 运行演示
try:
    result_deque = demonstrate_deque()
    print(f"最终队列: {list(result_deque)}")  # 输出: 最终队列: [0, 1]
    
except Exception as e:
    print(f"错误: {e}")

循环队列通过模运算实现了空间的循环利用,避免了普通队列在出队后前端空间浪费的问题。而双端队列则提供了更大的灵活性,可以在两端进行操作,在滑动窗口、回文检查等场景中非常有用。