Python 数据结构(线性表):从理论到实践
一、数组与列表:Python的基础线性结构
1.1 概念详解
数组 是一种线性数据结构,使用连续的内存空间存储相同类型的元素。在Python中,虽然没有传统意义上的静态数组,但列表(list)可以看作是动态数组的实现。
Python列表的核心特性:
-
动态大小:列表可以自动调整大小,无需手动管理内存
-
异构存储:可以存储不同类型的元素
-
连续内存:元素在内存中是连续存储的,支持快速随机访问
-
引用存储:列表存储的是对象的引用,而非对象本身
1.2 Python列表的底层实现
"""
Python列表的底层实现原理:
1. 列表对象包含三个主要部分:
- 指向数组的指针
- 列表长度(当前元素数量)
- 列表容量(已分配的内存空间)
2. 当列表空间不足时,Python会分配一个更大的数组,并将原有元素复制过去
3. 扩容策略通常是按比例增长(如增加当前大小的1/8)
"""
# 演示列表扩容过程
import sys
def list_growth_demo():
"""展示列表扩容时内存的变化"""
lst = []
prev_capacity = 0
print("列表扩容过程演示:")
print("-" * 50)
print(f"{'元素数量':<10} {'内存大小(字节)':<15} {'容量估计':<10}")
print("-" * 50)
for i in range(30):
lst.append(i)
current_size = sys.getsizeof(lst)
# 估算容量(内存大小减去固定开销)
if i == 0:
capacity = 0
else:
# Python列表的固定开销大约是56字节
capacity = (current_size - 56) // 8 # 每个引用8字节
if capacity != prev_capacity:
print(f"{i+1:<10} {current_size:<15} {capacity:<10} ← 扩容")
prev_capacity = capacity
else:
print(f"{i+1:<10} {current_size:<15} {capacity:<10}")
list_growth_demo()
print("\n" + "="*60)
print("列表操作时间复杂度总结:")
print("="*60)
print("| 操作 | 时间复杂度 | 说明 |")
print("|-------------------|------------|------------------------|")
print("| 索引访问 lst[i] | O(1) | 直接计算内存地址 |")
print("| 尾部追加 append() | O(1) | 平均时间复杂度 |")
print("| 头部插入 insert(0)| O(n) | 需要移动所有元素 |")
print("| 中间插入 insert(i)| O(n) | 需要移动i之后的元素 |")
print("| 删除尾部 pop() | O(1) | |")
print("| 删除头部 pop(0) | O(n) | 需要移动所有元素 |")
print("| 查找元素 in | O(n) | 线性查找 |")
print("| 切片 lst[i:j] | O(k) | k=j-i个元素 |")
1.3 例题1:询问学号
问题概述:有 n名同学陆陆续续进入教室。我们知道每名同学的学号(在 1 到 109 之间),按进教室的顺序给出。上课了,老师想知道第 i 个进入教室的同学的学号是什么。
import sys
def solve():
"""
优化版解决方案
核心优化点:
1. 一次性读取所有输入
2. 一次性输出所有结果
3. 避免频繁的print调用
"""
# 一次性读取所有数据
data = sys.stdin.buffer.read().split()
# 解析数据
n = int(data[0])
m = int(data[1])
# 学号列表
students = data[2:2+n]
# 处理查询并构建输出
out_lines = []
# 使用索引直接访问,O(1)时间复杂度
for i in data[2+n:2+n+m]:
# i是bytes类型,转换为int索引
idx = int(i) - 1
# 直接使用bytes避免编码转换
out_lines.append(students[idx])
# 一次性输出
sys.stdout.buffer.write(b'\n'.join(out_lines))
if __name__ == "__main__":
solve()
二、栈(Stack)的深度解析与应用
2.1 栈的概念与原理
栈 是一种后进先出(LIFO - Last In First Out)的线性数据结构。想象一下一叠盘子,你只能从最上面取放盘子。
栈的核心操作:
-
push - 入栈:将元素添加到栈顶
-
pop - 出栈:移除并返回栈顶元素
-
peek/top - 查看栈顶元素但不移除
-
is_empty - 检查栈是否为空
-
size - 获取栈中元素数量
栈的应用场景:
-
函数调用栈
-
表达式求值
-
括号匹配
-
浏览器前进后退
-
撤销/重做功能
2.2 Python中栈的实现对比
"""
Python中实现栈的几种方式对比:
1. 使用列表:最简单,但头部操作效率低
2. 使用collections.deque:两端操作都高效
3. 使用queue.LifoQueue:线程安全版本
"""
# 方法1:使用Python列表实现栈
class ListStack:
"""使用列表实现的栈"""
def __init__(self):
self._items = []
def push(self, item):
"""入栈 - O(1)"""
self._items.append(item)
def pop(self):
"""出栈 - O(1)"""
if not self.is_empty():
return self._items.pop()
raise IndexError("栈为空")
def peek(self):
"""查看栈顶 - O(1)"""
if not self.is_empty():
return self._items[-1]
raise IndexError("栈为空")
def is_empty(self):
return len(self._items) == 0
def size(self):
return len(self._items)
def __str__(self):
return f"Stack({self._items})"
# 方法2:使用deque实现栈(推荐)
from collections import deque
class DequeStack:
"""使用deque实现的栈"""
def __init__(self):
self._items = deque()
def push(self, item):
"""入栈 - O(1)"""
self._items.append(item)
def pop(self):
"""出栈 - O(1)"""
if not self.is_empty():
return self._items.pop()
raise IndexError("栈为空")
def peek(self):
"""查看栈顶 - O(1)"""
if not self.is_empty():
return self._items[-1]
raise IndexError("栈为空")
def is_empty(self):
return len(self._items) == 0
def size(self):
return len(self._items)
def __str__(self):
return f"Stack({list(self._items)})"
# 方法3:使用链表实现栈
class Node:
"""链表节点"""
def __init__(self, data):
self.data = data
self.next = None
class LinkedListStack:
"""使用链表实现的栈"""
def __init__(self):
self.top = None
self._size = 0
def push(self, item):
"""入栈 - O(1)"""
new_node = Node(item)
new_node.next = self.top
self.top = new_node
self._size += 1
def pop(self):
"""出栈 - O(1)"""
if self.is_empty():
raise IndexError("栈为空")
popped = self.top.data
self.top = self.top.next
self._size -= 1
return popped
def peek(self):
"""查看栈顶 - O(1)"""
if self.is_empty():
raise IndexError("栈为空")
return self.top.data
def is_empty(self):
return self.top is None
def size(self):
return self._size
def __str__(self):
result = []
current = self.top
while current:
result.append(str(current.data))
current = current.next
return " -> ".join(result) + " -> None"
# 性能对比测试
import time
def benchmark_stacks():
"""对比不同栈实现的性能"""
print("栈实现性能对比测试")
print("="*60)
implementations = {
"ListStack": ListStack,
"DequeStack": DequeStack,
"LinkedListStack": LinkedListStack
}
operations = 100000
for name, StackClass in implementations.items():
print(f"\n测试 {name}:")
stack = StackClass()
# 测试push性能
start = time.time()
for i in range(operations):
stack.push(i)
push_time = time.time() - start
# 测试pop性能
start = time.time()
for i in range(operations):
stack.pop()
pop_time = time.time() - start
print(f" Push {operations}次: {push_time:.4f}秒")
print(f" Pop {operations}次: {pop_time:.4f}秒")
benchmark_stacks()
print("\n" + "="*60)
print("结论:在Python中,使用deque实现栈是最佳选择")
print("="*60)
2.3 栈的经典应用例题
例题2:括号匹配验证器
问题概述:编写一个程序,检查字符串中的括号是否匹配。需要支持多种括号类型:圆括号()、方括号[]。
s = input().strip() # 读取输入的字符串并去除首尾空格
n = len(s) # 获取字符串长度
matched = [False] * n # 创建一个布尔数组,标记每个位置的括号是否匹配成功
stack = [] # 存放 (位置, 字符) 的栈,用于跟踪未匹配的左括号
# 第一遍扫描:匹配括号
for i, ch in enumerate(s):
# 如果是左括号,压入栈中
if ch == '(' or ch == '[':
stack.append((i, ch))
# 如果是右括号
elif ch == ')' or ch == ']':
if stack: # 如果栈不为空(存在未匹配的左括号)
last_pos, last_ch = stack[-1] # 获取栈顶元素(最近的未匹配左括号)
# 检查括号类型是否匹配:小括号配小括号,中括号配中括号
if (ch == ')' and last_ch == '(') or (ch == ']' and last_ch == '['):
matched[last_pos] = True # 标记左括号已匹配
matched[i] = True # 标记当前右括号已匹配
stack.pop() # 弹出栈顶(已匹配的左括号)
# 如果栈为空或不匹配,右括号保持未匹配状态(matched[i]保持False)
# 第二遍扫描:构建结果字符串
result = []
for i, ch in enumerate(s):
if not matched[i]: # 如果该括号未匹配
# 未匹配的括号可能是 '('、')'、'[' 或 ']'
# 根据题目要求,在它旁边添加一个字符使其匹配
if ch == '(' or ch == ')':
# 对于小括号,无论原来是左括号还是右括号,都变成"()"
result.append('(')
result.append(')')
else: # '[' or ']'
# 对于中括号,无论原来是左括号还是右括号,都变成"[]"
result.append('[')
result.append(']')
else: # 如果该括号已匹配,直接保留原字符
result.append(ch)
# 输出结果
print(''.join(result))
例题3:后缀表达式计算器(逆波兰表达式)
问题概述:实现一个后缀表达式(逆波兰表达式)计算器。后缀表达式是一种不需要括号就能明确运算顺序的表达式表示法。
后缀表达式特点:
-
操作符在操作数之后
-
不需要括号来指定优先级
-
计算顺序明确,易于计算机处理
示例:
-
中缀表达式:
(3 + 4) * 5 -
后缀表达式:
3 4 + 5 *
def e_p(exp):
"""计算后缀表达式(逆波兰表达式)的值"""
stack = [] # 用于存放操作数的栈
i = 0 # 字符串索引
length = len(exp) # 表达式长度
while i < length:
# 遇到 '@' 表示表达式结束
if exp[i] == '@':
break
# 遇到 '.' 是分隔符,跳过
if exp[i] == '.':
i += 1
continue
# 处理数字(可能是多位数)
if exp[i].isdigit():
num_str = '' # 构建数字字符串
# 连续读取数字字符,直到遇到非数字
while i < length and exp[i].isdigit():
num_str += exp[i]
i += 1
# 将数字字符串转换为整数并压入栈中
stack.append(int(num_str))
continue # 已经更新了i,继续下一次循环
# 处理运算符
if exp[i] in '+-*/':
# 弹出栈顶两个操作数(注意顺序:先弹出的是右操作数)
right = stack.pop() # 右操作数
left = stack.pop() # 左操作数
# 根据运算符进行计算
if exp[i] == '+':
result = left + right
elif exp[i] == '-':
result = left - right
elif exp[i] == '*':
result = left * right
elif exp[i] == '/':
# 特殊处理除法:C++风格的整数除法(向零取整)
# 当两个数同号时,正常整除
if left * right >= 0:
result = left // right
else:
# 异号时,确保结果向零取整
if left < 0:
result = -((-left) // right)
else:
result = -(left // (-right))
# 计算结果压入栈中
stack.append(result)
i += 1
continue
# 如果不是以上情况,移动到下一个字符
i += 1
# 栈中剩下的唯一元素就是表达式的结果
return stack[0]
# 主程序
exp = input() # 读取后缀表达式
result = e_p(exp) # 计算表达式值
print(result) # 输出结果
三、队列(Queue)的深度解析与应用
3.1 队列的概念与原理
队列 是一种先进先出(FIFO - First In First Out)的线性数据结构。想象一下排队购票,先来的人先服务。
队列的核心操作:
-
enqueue - 入队:将元素添加到队尾
-
dequeue - 出队:移除并返回队首元素
-
front/peek - 查看队首元素但不移除
-
is_empty - 检查队列是否为空
-
size - 获取队列中元素数量
队列的类型:
-
普通队列:基本FIFO队列
-
双端队列(deque):两端都可以入队和出队
-
优先队列:元素按优先级出队
-
循环队列:使用固定大小的数组实现
队列的应用场景:
-
消息队列
-
任务调度
-
广度优先搜索
-
打印机任务队列
-
网络数据包缓冲
3.2 Python中队列的实现
"""
Python中队列实现的几种方式:
1. 使用列表:简单但不高效(pop(0)是O(n)操作)
2. 使用collections.deque:推荐,两端操作都是O(1)
"""
from collections import deque
import queue
# 1. 使用列表实现队列(不推荐用于生产环境)
class ListQueue:
"""使用列表实现的队列(教学用途)"""
def __init__(self):
self._items = []
def enqueue(self, item):
"""入队 - O(1)"""
self._items.append(item)
def dequeue(self):
"""出队 - O(n)!效率低"""
if not self.is_empty():
return self._items.pop(0)
raise IndexError("队列为空")
def front(self):
"""查看队首 - O(1)"""
if not self.is_empty():
return self._items[0]
raise IndexError("队列为空")
def is_empty(self):
return len(self._items) == 0
def size(self):
return len(self._items)
def __str__(self):
return f"Queue({self._items})"
# 2. 使用deque实现队列(推荐)
class DequeQueue:
"""使用deque实现的高效队列"""
def __init__(self):
self._items = deque()
def enqueue(self, item):
"""入队 - O(1)"""
self._items.append(item)
def dequeue(self):
"""出队 - O(1)"""
if not self.is_empty():
return self._items.popleft()
raise IndexError("队列为空")
def front(self):
"""查看队首 - O(1)"""
if not self.is_empty():
return self._items[0]
raise IndexError("队列为空")
def is_empty(self):
return len(self._items) == 0
def size(self):
return len(self._items)
def __str__(self):
return f"Queue({list(self._items)})"
def clear(self):
"""清空队列"""
self._items.clear()
def rotate(self, n=1):
"""旋转队列"""
self._items.rotate(n)
print("\n" + "="*60)
print("队列实现选择指南:")
print("="*60)
print("1. 普通队列需求 → collections.deque")
print("2. 线程安全需求 → queue.Queue")
print("3. 异步编程需求 → asyncio.Queue")
print("4. 优先级调度需求 → heapq 或 queue.PriorityQueue")
print("5. 固定内存需求 → 自定义循环队列")
print("6. 教学/演示用途 → 列表实现(理解原理)")
3.3 队列的经典应用例题
例题4:约瑟夫问题
n 个人围成一圈,从第一个人开始报数,数到 m 的人出列,再由下一个人重新从 1 开始报数,数到 m 的人再出圈,依次类推,直到所有的人都出圈,请输出依次出圈人的编号。
# 约瑟夫环问题
# 输入总人数n和报数间隔m
n, m = map(int, input().split())
# 创建初始人员列表 [1, 2, 3, ..., n]
people = list(range(1, n + 1))
i = 1 # 当前报数计数器
index = 0 # 当前人员索引
result = [] # 存储出列顺序的结果列表
# 当还有人员未出列时继续循环
while len(result) < n:
# 如果当前报数等于m
if i == m:
# 将当前人员加入结果列表
result.append(people[index])
# 从人员列表中移除该人员
people.pop(index)
# 重置报数计数器
i = 1
# 如果人员列表为空,结束循环
if not people:
break
# 如果索引超出列表范围,回到开头(处理边界情况)
if index >= len(people):
index = 0
else:
# 报数加1
i += 1
# 移动到下一个人,使用取模运算实现循环
index = (index + 1) % len(people)
# 输出结果,用空格分隔
print(' '.join(map(str, result)))
# 算法说明:
# 1. 这是一个约瑟夫环问题的模拟解法
# 2. 每次报数到m的人出列,然后从下一个人继续报数
# 3. 使用取模运算实现环形遍历
# 4. 时间复杂度:O(n*m),空间复杂度:O(n)
四、链表的深度解析与应用
4.1 链表的核心概念与原理
链表 是一种非连续存储的线性数据结构,通过节点和指针(引用)实现。每个节点包含数据和指向下一个节点的指针。
链表的优缺点:
优点:
1. 动态大小,不需要预先分配内存
2. 插入和删除操作高效(O(1)时间复杂度,如果知道位置)
3. 不需要连续内存空间
4. 内存利用率高(按需分配)
缺点:
1. 随机访问低效(O(n)时间复杂度)
2. 需要额外的内存存储指针
3. 缓存不友好(节点分散在内存中)
4. 反向遍历困难(单向链表)
4.1.1 链表的类型与特点
"""
链表的主要类型:
1. 单向链表:每个节点只包含指向下一个节点的引用
2. 双向链表:每个节点包含指向前一个和后一个节点的引用
"""
# 节点基类
class BaseNode:
"""链表节点基类"""
def __init__(self, data):
self.data = data
self._id = id(self) # 用于调试
def __repr__(self):
return f"Node(data={self.data}, id={self._id % 1000:03d})"
# 单向链表节点
class SinglyNode(BaseNode):
def __init__(self, data):
super().__init__(data)
self.next = None
def __str__(self):
next_id = id(self.next) % 1000 if self.next else "None"
return f"SNode(data={self.data}, next={next_id:03d})"
# 双向链表节点
class DoublyNode(BaseNode):
def __init__(self, data):
super().__init__(data)
self.prev = None
self.next = None
def __str__(self):
prev_id = id(self.prev) % 1000 if self.prev else "None"
next_id = id(self.next) % 1000 if self.next else "None"
return f"DNode(data={self.data}, prev={prev_id:03d}, next={next_id:03d})"
4.2 链表的完整实现
4.2.1 单向链表完整实现
class SinglyLinkedList:
"""
单向链表完整实现
包含完整的增删改查操作和实用方法
"""
class Node:
"""单向链表节点"""
__slots__ = ('data', 'next') # 减少内存占用
def __init__(self, data):
self.data = data
self.next = None
def __repr__(self):
return f"Node({self.data})"
def __init__(self, iterable=None):
"""
初始化链表
Args:
iterable: 可迭代对象,用于初始化链表
"""
self.head = None
self.tail = None
self.length = 0
self._current = None # 用于迭代
if iterable:
for item in iterable:
self.append(item)
def is_empty(self):
"""检查链表是否为空"""
return self.head is None
def insert(self, data, position):
"""
在指定位置插入元素
Args:
data: 要插入的数据
position: 插入位置(0-based索引)
时间复杂度: O(n)
"""
if position < 0 or position > self.length:
raise IndexError(f"位置 {position} 超出范围 [0, {self.length}]")
if position == 0:
return self.prepend(data)
if position == self.length:
return self.append(data)
new_node = self.Node(data)
current = self.head
# 移动到插入位置的前一个节点
for _ in range(position - 1):
current = current.next
new_node.next = current.next
current.next = new_node
self.length += 1
return new_node
def delete(self, position):
"""
删除指定位置的元素
Args:
position: 要删除的位置(0-based索引)
Returns:
被删除节点的数据
时间复杂度: O(n)
"""
if self.is_empty():
raise IndexError("链表为空")
if position < 0 or position >= self.length:
raise IndexError(f"位置 {position} 超出范围 [0, {self.length-1}]")
# 删除头部节点
if position == 0:
data = self.head.data
self.head = self.head.next
if self.head is None: # 如果链表为空
self.tail = None
self.length -= 1
return data
# 查找要删除节点的前一个节点
current = self.head
for _ in range(position - 1):
current = current.next
# 删除节点
data = current.next.data
current.next = current.next.next
# 如果删除的是尾部节点,更新尾指针
if current.next is None:
self.tail = current
self.length -= 1
return data
def get(self, position):
"""
获取指定位置的元素
时间复杂度: O(n)
"""
if position < 0 or position >= self.length:
raise IndexError(f"位置 {position} 超出范围 [0, {self.length-1}]")
current = self.head
for _ in range(position):
current = current.next
return current.data
def index(self, data):
"""
查找元素的第一个位置
Returns:
元素的索引,如果未找到则返回-1
时间复杂度: O(n)
"""
current = self.head
index = 0
while current:
if current.data == data:
return index
current = current.next
index += 1
return -1
def contains(self, data):
"""检查是否包含元素"""
return self.index(data) != -1
def to_list(self):
"""将链表转换为Python列表"""
result = []
current = self.head
while current:
result.append(current.data)
current = current.next
return result
def clear(self):
"""清空链表"""
self.head = None
self.tail = None
self.length = 0
def __delitem__(self, position):
self.delete(position)
def __contains__(self, data):
return self.contains(data)
def __iter__(self):
self._current = self.head
return self
def __next__(self):
if self._current is None:
raise StopIteration
data = self._current.data
self._current = self._current.next
return data
def __repr__(self):
if self.is_empty():
return "SinglyLinkedList([])"
elements = []
current = self.head
return f"SinglyLinkedList([{', '.join(elements)}])"
# 单向链表使用示例
def demo_singly_linked_list():
"""演示单向链表的使用"""
print("单向链表演示")
print("="*80)
# 1. 创建和初始化
print("1. 创建和初始化链表:")
lst = SinglyLinkedList([1, 2, 3, 4, 5])
print(f" 初始链表: {lst}")
print(f" 长度: {len(lst)}")
# 2. 基本操作
print("\n2. 基本操作:")
# 添加元素
lst.insert(3.5, 4) # 在位置4插入3.5
print(f" 添加元素后: {lst}")
# 访问元素
print(f" 第二个元素: {lst[1]}")
print(f" 最后一个元素: {lst[len(lst)-1]}")
print(f" 中间元素: {lst.get_middle()}")
# 3. 删除操作
print("\n3. 删除操作:")
deleted = lst.delete(4) # 删除位置4的元素
print(f" 删除位置4的元素 ({deleted}) 后: {lst}")
lst.remove(0) # 删除值为0的元素
print(f" 删除值为0的元素后: {lst}")
# 4. 查找和包含检查
print("\n4. 查找和包含检查:")
print(f" 元素3的位置: {lst.index(3)}")
print(f" 是否包含10: {10 in lst}")
print(f" 是否包含5: {5 in lst}")
demo_singly_linked_list()
4.2.2 双向链表完整实现
class DoublyLinkedList:
"""
双向链表完整实现
支持正向和反向遍历
"""
class Node:
"""双向链表节点"""
__slots__ = ('data', 'prev', 'next')
def __init__(self, data):
self.data = data
self.prev = None
self.next = None
def __repr__(self):
return f"DNode({self.data})"
def __init__(self, iterable=None):
"""初始化双向链表"""
self.head = None
self.tail = None
self.length = 0
if iterable:
for item in iterable:
self.append(item)
def is_empty(self):
"""检查链表是否为空"""
return self.head is None
def insert(self, data, position):
"""在指定位置插入元素"""
if position < 0 or position > self.length:
raise IndexError(f"位置 {position} 超出范围")
if position == 0:
return self.prepend(data)
if position == self.length:
return self.append(data)
# 找到插入位置的节点
if position < self.length // 2:
# 从前向后找
current = self.head
for _ in range(position):
current = current.next
else:
# 从后向前找
current = self.tail
for _ in range(self.length - position - 1):
current = current.prev
# 插入新节点
new_node = self.Node(data)
new_node.prev = current.prev
new_node.next = current
current.prev.next = new_node
current.prev = new_node
self.length += 1
return new_node
def delete(self, position):
"""删除指定位置的元素"""
if self.is_empty():
raise IndexError("链表为空")
if position < 0 or position >= self.length:
raise IndexError(f"位置 {position} 超出范围")
# 找到要删除的节点
if position < self.length // 2:
current = self.head
for _ in range(position):
current = current.next
else:
current = self.tail
for _ in range(self.length - position - 1):
current = current.prev
# 删除节点
data = current.data
if current.prev:
current.prev.next = current.next
else: # 删除的是头节点
self.head = current.next
if current.next:
current.next.prev = current.prev
else: # 删除的是尾节点
self.tail = current.prev
# 清理引用
current.prev = None
current.next = None
self.length -= 1
return data
def get(self, position):
"""获取指定位置的元素"""
if position < 0 or position >= self.length:
raise IndexError(f"位置 {position} 超出范围")
# 根据位置选择遍历方向
if position < self.length // 2:
current = self.head
for _ in range(position):
current = current.next
else:
current = self.tail
for _ in range(self.length - position - 1):
current = current.prev
return current.data
def forward_traversal(self):
"""正向遍历生成器"""
current = self.head
while current:
yield current.data
current = current.next
def backward_traversal(self):
"""反向遍历生成器"""
current = self.tail
while current:
yield current.data
current = current.prev
def to_list(self, reverse=False):
"""转换为Python列表"""
if reverse:
return list(self.backward_traversal())
return list(self.forward_traversal())
def clear(self):
"""清空链表"""
# 断开所有节点的引用,帮助垃圾回收
current = self.head
while current:
next_node = current.next
current.prev = None
current.next = None
current = next_node
self.head = None
self.tail = None
self.length = 0
def __len__(self):
return self.length
def __getitem__(self, position):
return self.get(position)
def __iter__(self):
return self.forward_traversal()
def __repr__(self):
if self.is_empty():
return "DoublyLinkedList([])"
elements = []
current = self.head
while current:
elements.append(repr(current.data))
current = current.next
return f"DoublyLinkedList([{', '.join(elements)}])"
# 双向链表使用示例
def demo_doubly_linked_list():
"""演示双向链表的使用"""
print("\n" + "="*80)
print("双向链表演示")
print("="*80)
# 1. 创建和初始化
dll = DoublyLinkedList([1, 2, 3, 4, 5])
print(f"初始双向链表: {dll}")
print(f"长度: {len(dll)}")
# 2. 正向和反向遍历
print("\n正向遍历:")
for item in dll:
print(f" {item}", end="")
print()
print("反向遍历:")
for item in dll.backward_traversal():
print(f" {item}", end="")
print()
# 3. 插入和删除
dll.insert(2.5, 2)
print(f"\n在位置2插入2.5后: {dll}")
dll.delete(3)
print(f"删除位置3的元素后: {dll}")
# 4. 转换为列表
print(f"\n正向列表: {dll.to_list()}")
print(f"反向列表: {dll.to_list(reverse=True)}")
demo_doubly_linked_list()
4.3 链表的经典应用例题
例题5:队列安排
一个学校里老师要将班上 N 个同学排成一列,同学被编号为 1∼N,他采取如下的方法:
-
先将 1 号同学安排进队列,这时队列中只有他一个人;
-
2∼N 号同学依次入列,编号为 i 的同学入列方式为:老师指定编号为 i 的同学站在编号为 1∼(i−1) 中某位同学(即之前已经入列的同学)的左边或右边;
-
从队列中去掉 M 个同学,其他同学位置顺序不变。
在所有同学按照上述方法队列排列完毕后,老师想知道从左到右所有同学的编号。
class Node:
"""双向链表节点"""
def __init__(self, value):
self.value = value
self.prev = None # 前驱指针
self.next = None # 后继指针
class DoublyLinkedList:
"""双向链表"""
def __init__(self):
self.head = None # 链表头节点
self.tail = None # 链表尾节点
self.node_map = {} # 值到节点的映射,用于快速查找
def insert_after(self, target_value, new_value):
"""在目标节点后插入新节点"""
# 通过映射快速找到目标节点
target_node = self.node_map[target_value]
# 创建新节点
new_node = Node(new_value)
# 设置新节点的前驱和后继
new_node.prev = target_node
new_node.next = target_node.next
# 如果目标节点不是尾节点
if target_node.next:
target_node.next.prev = new_node
else:
# 如果目标节点是尾节点,更新尾节点为新节点
self.tail = new_node
# 更新目标节点的后继
target_node.next = new_node
# 更新映射
self.node_map[new_value] = new_node
def insert_before(self, target_value, new_value):
"""在目标节点前插入新节点"""
target_node = self.node_map[target_value]
new_node = Node(new_value)
# 设置新节点的前驱和后继
new_node.next = target_node
new_node.prev = target_node.prev
# 如果目标节点不是头节点
if target_node.prev:
target_node.prev.next = new_node
else:
# 如果目标节点是头节点,更新头节点为新节点
self.head = new_node
# 更新目标节点的前驱
target_node.prev = new_node
# 更新映射
self.node_map[new_value] = new_node
def remove(self, value):
"""删除节点"""
# 如果值不在映射中,直接返回
if value not in self.node_map:
return
node = self.node_map[value]
# 处理前驱节点
if node.prev:
node.prev.next = node.next
else:
# 如果删除的是头节点,更新头节点
self.head = node.next
# 处理后继节点
if node.next:
node.next.prev = node.prev
else:
# 如果删除的是尾节点,更新尾节点
self.tail = node.prev
# 从映射中删除
del self.node_map[value]
def to_list(self):
"""转换为列表"""
result = []
current = self.head
# 遍历链表
while current:
result.append(current.value)
current = current.next
return result
# 主程序开始
# 读取学生总数
n = int(input())
# 创建双向链表
dll = DoublyLinkedList()
# 插入第一个学生
first_node = Node(1)
dll.head = first_node
dll.tail = first_node
dll.node_map[1] = first_node
# 读取并执行插入操作
operations = []
# 从第2个学生开始到第n个学生
for i in range(2, n + 1):
# k: 基准学生编号,p: 插入位置(0表示左边,1表示右边)
k, p = map(int, input().split())
operations.append((i, k, p))
# 执行所有插入操作
for i, k, p in operations:
if p == 0: # 插入到左边
dll.insert_before(k, i)
else: # 插入到右边
dll.insert_after(k, i)
# 读取删除操作
m = int(input())
to_delete = set() # 使用集合提高查找效率
for _ in range(m):
to_delete.add(int(input()))
# 执行删除
for x in to_delete:
dll.remove(x)
# 输出结果
result = dll.to_list()
print(' '.join(map(str, result)))
# 算法说明:
# 1. 使用双向链表模拟排队过程
# 2. 通过node_map实现O(1)时间复杂度的节点查找
# 3. 插入操作:
# - insert_after: 在目标节点后插入,时间复杂度O(1)
# - insert_before: 在目标节点前插入,时间复杂度O(1)
# 4. 删除操作:时间复杂度O(1)
# 5. 整体时间复杂度:O(n+m),其中n是学生总数,m是删除操作数
# 6. 空间复杂度:O(n)
# 应用场景:
# 1. 学生排队问题
# 2. 需要频繁在指定位置插入和删除的场景
# 3. 需要保持元素顺序的数据结构
总结
通过以上详细讲解和实现,我们深入探讨了Python中线性数据结构(数组/列表、栈、队列、链表)的各个方面:
核心要点总结:
-
数组/列表:Python的基石,动态数组实现,随机访问高效
-
栈:后进先出,适合函数调用、括号匹配、撤销操作
-
队列:先进先出,适合任务调度、消息队列、广度优先搜索
-
链表:动态存储,插入删除高效,适合LRU缓存、多项式运算
选择指南:
-
需要随机访问 → 使用列表
-
需要后进先出 → 使用栈(deque实现)
-
需要先进先出 → 使用队列(deque实现)
-
需要频繁插入删除 → 考虑链表
-
需要线程安全 → 使用queue模块
-
需要优先级调度 → 使用优先队列
更多推荐


所有评论(0)