数据结构入门:栈、队列和哈希表
本文最后更新于 22 天前,如有失效请评论区留言。

在学编程的过程中,你一定听过「数据结构」这个词。简单来说,数据结构就是组织和存储数据的方式。选对了数据结构,代码写起来轻松,运行效率也高。今天我们来聊三个最基础、面试也最爱问的数据结构:Stack(栈)Queue(队列)Hash Table(哈希表)。都是 Python 里天天用到的东西,搞明白它们的原理和时间复杂度,对写代码帮助很大。

一、Stack(栈)

栈是一种 LIFO(Last In, First Out) 的数据结构,也就是「后进先出」。你可以把它想象成一摞盘子——最后放上去的盘子,是最先被拿走的。

基本操作

  • push:把元素压入栈顶,O(1)
  • pop:弹出栈顶元素,O(1)
  • peek / top:查看栈顶元素但不弹出,O(1)

Python 里没有专门的 Stack 类,直接用 list 就行:

# 用 Python list 实现栈
stack = []

# push - 压栈
stack.append(1)
stack.append(2)
stack.append(3)
print(stack)  # [1, 2, 3]

# pop - 弹栈
top = stack.pop()
print(top)     # 3
print(stack)   # [1, 2]

# peek - 查看栈顶
print(stack[-1])  # 2

如果追求更规范的写法,也可以用 collections.deque,性能更好一些:

from collections import deque

stack = deque()
stack.append(1)
stack.append(2)
stack.pop()  # 2

栈的应用场景

栈的应用非常广泛:

  • 函数调用栈:程序运行时,每次函数调用都会在调用栈上压入一个 frame,返回时弹出。递归爆栈就是因为栈太深了。
  • 括号匹配:判断 ([{}]) 是否合法,经典栈题。
  • 浏览器的后退功能:本质上就是一个栈。
  • DFS(深度优先搜索):递归版本隐式用栈,迭代版本显式用栈。
# 括号匹配 - LeetCode 20
def is_valid(s: str) -> bool:
    stack = []
    mapping = {')': '(', ']': '[', '}': '{'}
    
    for char in s:
        if char in mapping:
            # 遇到右括号,检查栈顶
            top = stack.pop() if stack else '#'
            if mapping[char] != top:
                return False
        else:
            # 左括号压栈
            stack.append(char)
    
    return len(stack) == 0

print(is_valid("([{}])"))  # True
print(is_valid("([)]"))    # False

二、Queue(队列)

队列和栈相反,是一种 FIFO(First In, First Out) 的数据结构,即「先进先出」。就像排队买奶茶——先来的人先拿到奶茶。

基本操作

  • enqueue:从队尾入队,O(1)
  • dequeue:从队头出队,O(1)
  • peek:查看队头元素,O(1)

⚠️ 不要用 list 当队列!虽然 list.pop(0) 能出队,但它是 O(n) 操作,因为要移动所有元素。推荐用 collections.deque

from collections import deque

queue = deque()

# enqueue - 入队
queue.append('A')
queue.append('B')
queue.append('C')
print(queue)  # deque(['A', 'B', 'C'])

# dequeue - 出队
first = queue.popleft()
print(first)    # 'A'
print(queue)    # deque(['B', 'C'])

队列的应用场景

  • BFS(广度优先搜索):层序遍历树、图的最短路径,核心就是队列。
  • 任务调度:操作系统进程调度、消息队列(如 RabbitMQ、Kafka)。
  • 缓存淘汰策略:LRU Cache 底层用的就是哈希表 + 双向链表(队列的变体)。
# BFS 示例:二叉树层序遍历
from collections import deque

def level_order(root):
    if not root:
        return []
    
    result = []
    queue = deque([root])
    
    while queue:
        level = []
        for _ in range(len(queue)):
            node = queue.popleft()
            level.append(node.val)
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        result.append(level)
    
    return result

Priority Queue(优先队列)

普通队列按入队顺序出队,优先队列则按优先级出队。Python 里用 heapq 实现:

import heapq

pq = []
heapq.heappush(pq, 3)
heapq.heappush(pq, 1)
heapq.heappush(pq, 2)

print(heapq.heappop(pq))  # 1(最小的先出)
print(heapq.heappop(pq))  # 2

三、Hash Table(哈希表)

哈希表可能是日常编程中用得最多的数据结构了。Python 的 dictset 底层都是哈希表。它通过 hash function 把 key 映射到数组的某个位置,从而实现极快的查找。

时间复杂度

  • 插入 / 删除 / 查找:平均 O(1),最坏 O(n)(哈希冲突严重时退化)
  • 空间复杂度:O(n)

用起来很简单,Python 的 dict 就是哈希表:

# Python dict 本质就是哈希表
student = {
    'name': 'Alice',
    'age': 20,
    'gpa': 3.8
}

# 查找 - O(1)
print(student['name'])  # 'Alice'

# 插入 - O(1)
student['major'] = 'CS'

# 删除 - O(1)
del student['gpa']

# 检查 key 是否存在 - O(1)
print('age' in student)  # True

哈希冲突(Collision)

不同的 key 经过 hash function 可能得到相同的 index,这就是 哈希冲突。常见的解决方法有两种:

  • Chaining(链地址法):每个 bucket 存一个链表,冲突的元素都挂在这个链表上。
  • Open Addressing(开放寻址法):冲突了就按某种规则探测下一个空位。

Python 的 dict 用的是开放寻址法的一种变体。

经典应用:Two Sum

哈希表最经典的面试题就是 Two Sum。暴力解法 O(n²),用哈希表优化到 O(n):

# LeetCode 1: Two Sum
def two_sum(nums: list, target: int) -> list:
    seen = {}  # val -> index
    
    for i, num in enumerate(nums):
        complement = target - num
        if complement in seen:  # O(1) 查找!
            return [seen[complement], i]
        seen[num] = i
    
    return []

print(two_sum([2, 7, 11, 15], 9))  # [0, 1]

Python dict 的内部机制

了解一些底层细节有助于理解性能。Python dict 从 3.6 开始是有序的(按插入顺序),底层用一个 dense array 存 key-value pairs,用一个 sparse array 存 hash indices。这样既省空间又保序。当 dict 的使用率超过 2/3 时会自动 resize。

四、三者对比总结

# 三种数据结构速查表
"""
数据结构 | 原理     | 插入   | 删除   | 查找   | Python 实现
---------|---------|--------|--------|--------|-------------
Stack    | LIFO    | O(1)   | O(1)   | O(n)   | list / deque
Queue    | FIFO    | O(1)   | O(1)   | O(n)   | deque
Hash Table| Hash映射| O(1)*  | O(1)*  | O(1)*  | dict / set

* 平均情况,最坏 O(n)
"""

数据结构的选择决定了算法的效率。写代码之前先想清楚:我的数据是先进后出还是先进先出?我需要快速查找吗?想明白了再选结构,事半功倍。

五、推荐练习

光看不练假把式,推荐几道经典 LeetCode 题目来巩固:

  • :#20 Valid Parentheses、#155 Min Stack、#84 Largest Rectangle in Histogram
  • 队列:#102 Binary Tree Level Order Traversal、#622 Design Circular Queue
  • 哈希表:#1 Two Sum、#49 Group Anagrams、#128 Longest Consecutive Sequence

这三个数据结构是后续学习 Tree、Graph、DP 的基础,务必打牢。下次我们来聊聊 Linked List 和 Binary Search Tree,敬请期待 🚀

版权声明:除特殊说明,博客文章均为原创,依据CC BY-SA 4.0许可证进行授权,转载请附上出处链接及本声明。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇