数据结构学习笔记:Python 实现
从线性表到图论——用 Python 手写实现常用数据结构及其核心算法。包含顺序存储与链式存储对比、栈与队列的工程应用、二叉树的遍历与递归思维、图的最小生成树算法。
线性表
线性表是最基础的数据结构——元素之间存在一对一的前驱后继关系。两种存储方式代表了时空权衡的核心思想。
顺序存储 · SeqList
用 Python 列表模拟连续内存。支持任意位置插入(insert)、追加(append)、按位置/按值删除(delete/remove)、按位置/按值查找(get_item/locate)。核心权衡:随机访问 O(1),但插入删除需移动元素 O(n)。
if self.length>=self.maxsize:
raise Exception ("full")
if pos > self.length or pos < -self.length:
raise Exception ("out of range!")
self.elem.insert(pos,item)
self.length += 1
链式存储 · LinkList
带头节点的单链表。head.data 存储链表长度——将元数据写进头节点是个巧妙的时间换空间技巧。核心权衡:插入删除 O(1)(已知位置),但遍历查找 O(n)。
if self.is_empty():
self.head.next=Node(item)
self.head.value+=1
elif pos
cursor=self.head
while count
node.next=cursor.next
cursor.next=node
self.head.value+=1
栈
后进先出(LIFO)的线性结构。push/pop 的时间复杂度均为 O(1)。栈是递归的底层实现机制——每个函数调用就是一个栈帧。
def __init__(self,MAXSIZE=10):
self.elem=[None]*MAXSIZE
self.top=-1
def push(self,item):
self.top+=1
self.elem[self.top]=item
def pop(self):
self.top-=1
return self.elem[self.top+1]
def push(self,item):
node=Node(item)
node.next=self.head.next
self.head.next=node
def pop(self):
item=self.head.next.value
self.head.next=self.head.next.next
return item
stack=SeqStack()
for i in s:
if i in '{[(': stack.push(i)
if i in '}':
if stack.pop()!='{': return False
# 同理处理 ] 和 )
return stack.is_empty()
栈与链表 · 经典算法题
merged = Linklist()
# 双指针归并:同时遍历两个链表
while cursor_a and cursor_b:
if cursor_a.value < cursor_b.value:
merged.append(cursor_a.value)
cursor_a = cursor_a.next
elif cursor_a.value > cursor_b.value:
merged.append(cursor_b.value)
cursor_b = cursor_b.next
else: # 相等时跳过重复
merged.append(cursor_a.value)
cursor_a = cursor_a.next
cursor_b = cursor_b.next
return merged
seen = set() # 用哈希表记录已出现的绝对值
cursor = linklist.head
while cursor.next:
val = abs(cursor.next.data)
if val in seen:
cursor.next = cursor.next.next
else:
seen.add(val)
cursor = cursor.next
def find_max(head):
if not head.next: return head.data
current_max = find_max(head.next)
return max(head.data, current_max)
# 求结点个数 — 递归版
def node_num(head):
if not head: return 0
return 1 + node_num(head.next)
# 求平均值 = sum / count
def average(head):
return sum_nodes(head) / node_num(head)
stack = [] # 模拟栈中元素数量
for op in seq:
if op == 'I': stack.append(1)
else:
if not stack: return False
stack.pop()
return True
# IOIIOIOO → True IIIOIOIO → True
# IOOIOIIO → False IIIOOIOO → True
队列
先进先出(FIFO)。循环队列用取模实现 rear/front 指针循环利用数组空间。
def __init__(self,MAXSIZE=10):
self.elem=[None]*MAXSIZE
self.front,self.rear=0,0
def en_queue(self,item):
self.elem[self.rear]=item
self.rear=(self.rear+1)%self.capacity
def de_queue(self):
item=self.elem[self.front]
self.front=(self.front+1)%self.capacity
return item
def length(self):
return (self.rear+self.capacity-self.front)%self.capacity
二叉树
非线性结构的起点。前序/中序/后序三种深度遍历对应递归的三种处理顺序,层序遍历用队列实现。
if root==None: return
print(root.data,end=' ')
self.PreOrder(root.lchild)
self.PreOrder(root.rchild)
def InOrder(self,root): # 左→根→右
if root==None: return
self.InOrder(root.lchild)
print(root.data,end=' ')
self.InOrder(root.rchild)
def PostOrder(self,root): # 左→右→根
if root==None: return
self.PostOrder(root.lchild)
self.PostOrder(root.rchild)
print(root.data,end=' ')
def Traverse(self):
queue=[self.root]
while len(queue)>0:
node=queue.pop(0)
print(node.data,end=' ')
if node.lchild: queue.append(node.lchild)
if node.rchild: queue.append(node.rchild)
# 二叉树深度 — 递归
def Depth(self,root):
if root==None: return 0
return 1+max(self.Depth(root.lchild),
self.Depth(root.rchild))
if root == None: return 0
if root.lchild == None and root.rchild == None:
return 1
return count_leaf(root.lchild) + count_leaf(root.rchild)
queue = [root]
while queue:
node = queue.pop(0)
degree = bool(node.lchild) + bool(node.rchild)
if degree == 1: count += 1
if node.lchild: queue.append(node.lchild)
if node.rchild: queue.append(node.rchild)
if not root: return
path.append(root.data)
if not root.lchild and not root.rchild:
print(path) # 到达叶子,输出路径
leaf_paths(root.lchild, path)
leaf_paths(root.rchild, path)
path.pop() # 回溯:移除当前节点
图 · 最小生成树
图论的两个经典贪心算法——Kruskal 对边排序后贪心选边,Prim 从顶点出发逐步扩张。两者都能在 O(E log E) 内求出 MST。
Kruskal · 克鲁斯卡尔算法
核心思想:对所有边按权重升序排序,依次检查每条边——如果加入该边不会形成环(并查集判定),则将其加入 MST。本质是"边贪心"。
edges.sort(key=lambda x: x[2]) # 按权重排序
parent = list(range(n))
def find(x):
if parent[x]!=x: parent[x]=find(parent[x])
return parent[x]
mst = []
for u,v,w in edges:
if find(u)!=find(v):
parent[find(u)]=find(v)
mst.append((u,v,w))
return mst
Prim · 普里姆算法
核心思想:从任意顶点出发,维护已选顶点集合 S。每次从 S 到 V-S 的横切边中选权重最小的加入 MST。本质是"点贪心"。
n = len(graph)
visited = [False]*n
visited[start] = True
for _ in range(n-1):
min_w, u, v = float('inf'), -1, -1
# 遍历所有横切边,选最小的
for i in range(n):
if visited[i]:
for j in range(n):
if not visited[j] and graph[i][j] < min_w:
min_w = graph[i][j]
u, v = i, j
visited[v] = True
mst.append((u, v, min_w))
return mst
5 Chapters · 8 Algorithms · Pure Python
从线性表到图论,每次 Push/Pop 都是对计算机底层的一次致敬。