封面图片来源:やがて君になる:ポテトマッシャー
C++ STL 对应的 Python 实现
在算法竞赛(ACM/OI)中,Python 内置的数据结构和库函数完全可以覆盖 C++ STL 的绝大多数功能。
1. 线性容器与字符串
std::vector list
Python 的列表底层实现是动态数组。
push_back(x)a.append(x),时间复杂度 。pop_back()a.pop(),时间复杂度 。clear()a.clear()。- 初始化大小为 且全为 0 的数组:
a = [0] * N。
std::string str
Python 的字符串是 不可变(Immutable) 的。
- 避坑: 切勿在循环中用
s += "a"拼接,会导致 的时间复杂度。 - 正确做法: 使用列表收集字符,最后用
"".join(a)合并。
std::pair / std::tuple tuple
Python 原生支持元组,且自带字典序比较,非常适合打包坐标或状态。
make_pair(1, 2)(1, 2)。
2. 栈与队列
std::stack list
直接使用 list 模拟栈。
push(x)st.append(x)top()st[-1]pop()st.pop()
std::queue collections.deque
绝对不能用 list 模拟队列!(list.pop(0) 是 )。必须使用双端队列 deque。
from collections import dequeq = deque()q.append(x) # pushq[0] # frontq.popleft() # pop,时间复杂度 O(1)std::priority_queue heapq
Python 只有操作列表的堆算法库,默认是小根堆。
import heapqpq = []heapq.heappush(pq, x) # pushpq[0] # top (最小值)heapq.heappop(pq) # pop- 大根堆实现: 存入元素时乘
-1,取出时再乘回-1。 - 自定义排序: 存入元组
heapq.heappush(pq, (cost, node)),默认按第一个元素排序。
3. 关联容器
核心差异: C++
map/set底层是红黑树(增删改查 ,有序);Pythondict/set底层是哈希表(增删改查 ,无序)。
std::set set
insert(x)s.add(x)count(x)x in serase(x)s.discard(x)(不存在时不报错,比remove安全)。
std::map dict
1. 底层差异
- C++
map:红黑树,自带按 Key 排序,。 - Python
dict:哈希表,无序,(完全等价于unordered_map)。
2. 致命防坑 (KeyError)
- C++:
m[k]++(键不存在会自动建一个 0,不报错)。 - Python:
d[k] += 1(键不存在直接崩溃 RE)。
3. 竞赛防报错“三板斧”
- 统计次数 (默认 0):
from collections import defaultdict;d = defaultdict(int) - 图论建边 (默认 []):
from collections import defaultdict;adj = defaultdict(list) - 极速查词频:
from collections import Counter;cnt = Counter(arr)
4. 遍历与排序
- 遍历键和值:
for k, v in d.items(): - 按 Key 排序: 字典无法动态排序,只能全拿出来排:
sorted(d.items())
4. 常用算法
std::sort list.sort() 或 sorted()
-
降序:
a.sort(reverse=True)(纯 C 底层实现,比用 lambda 更快)。 -
多关键字排序: 利用 Python 元组自动“从左到右”字典序比较的特性,结合
lambda表达式,可以优雅解决 95% 的多关键字问题。# 假设 a 是一个包含多个元素的列表:[[id, score, time], ...]a = [[1, 90, 5], [2, 90, 3], [3, 80, 10]]# 需求:先按 score 降序,score 相同按 time 升序,time 相同按 id 升序a.sort(key=lambda x: (-x[1], x[2], x[0]))print(a) # 结果: [[2, 90, 3], [1, 90, 5], [3, 80, 10]] -
自定义比较器: 当排序规则无法简单地映射为几个权值(例如经典的“将数组拼接成最大的数字”问题),就需要引入
functools.cmp_to_key来完全模拟 C++ 里的cmp函数。from functools import cmp_to_keydef compare(x, y):# 返回负数 (-1): 代表 x 应该排在 y 前面# 返回正数 ( 1): 代表 y 应该排在 x 前面# 返回 0: 代表 x 和 y 相等str_x, str_y = str(x), str(y)if str_x + str_y > str_y + str_x:return -1else:return 1nums = [3, 30, 34, 5, 9]nums.sort(key=cmp_to_key(compare))print(nums) # 结果: [9, 5, 34, 3, 30]
std::lower_bound / std::upper_bound bisect
import bisecta = [1, 2, 2, 4, 5]idx1 = bisect.bisect_left(a, 2) # lower_bound: 返回 1idx2 = bisect.bisect_right(a, 2) # upper_bound: 返回 3std::next_permutation itertools.permutations
from itertools import permutationsfor p in permutations([1, 2, 3]): pass # p 是元组Python 字符串操作
1. 不可变性
Python 的字符串是不可变的! 你绝对不能像 C++ 那样直接写 s[0] = 'a'。任何对字符串的修改(替换、拼接)都不会在原地址操作,而是会在内存中生成一个全新的字符串。
2. 字符串切片
语法:s[start : end : step] (左闭右开区间 [start, end))
s[2:5]:截取索引 2 到 4 的子串。s[:3]:截取前 3 个字符(等价于s[0:3])。s[3:]:从索引 3 截取到末尾。s[-1]:获取最后一个字符(负数索引表示从右往左数)。s[::-1]:字符串翻转
3. 查、分、合
A. 查找与统计
sub in s:判断sub是否是s的子串,返回True/False。s.find(sub):返回子串第一次出现的起始索引,找不到返回-1
B. 分割与拼接
s.split(sep):按sep分割字符串。- 如果不传参数
s.split(),它会自动按空格、换行、制表符分割,并自动过滤掉中间连续的空白符。
- 如果不传参数
sep.join(iterable):将列表拼成字符串。- 🔴 在循环里用
s += "a"拼接百万级字符串,会导致每次都重新分配内存,复杂度退化为 ,必 TLE。 - 🟢
tmp = []; tmp.append("a"); res = "".join(tmp),复杂度为 。
- 🔴 在循环里用
C. 清理空白符
s.strip():去除首尾的所有空白字符(空格、\n、\r、\t)。s.rstrip('\r\n'):只去除右侧换行符(极其重要!在使用sys.stdin.readline读单个字符串防 WA 必备)。
4. 字符与 ASCII 码转换
对应 C++ 中的 s[i] - 'a' 逻辑。
ord(char):字符转 ASCII 数值。例如ord('A')返回65。chr(int):ASCII 数值转字符。例如chr(97)返回'a'。
5. 字符串的修改与排序
模拟 C++ 的单点修改
如果你需要频繁修改字符串中的某个字符(比如模拟题),必须先转成 list,修改完再拼回去:
lst = list(s) # "abc" -> ['a', 'b', 'c']lst[1] = 'x' # ['a', 'x', 'c']s = "".join(lst) # "axc"字符串排序
Python 的字符串没有自带的 .sort() 方法。
s = "dcba"# sorted(s) 会对可迭代对象排序,并返回一个列表: ['a', 'b', 'c', 'd']s = "".join(sorted(s)) # 重新拼回字符串: "abcd"6. 高效格式化输出
Python 3.6+ 引入,运行速度最快,写题调试必备。
ans = 123pi = 3.14159print(f"Result: {ans}") # 基础变量替换print(f"{ans:05d}") # 补零占位,输出: 00123print(f"{pi:.2f}") # 保留两位小数,输出: 3.14代码示例
Python 模板
import sysimport mathimport heapqfrom random import randintfrom functools import cmp_to_keyfrom collections import defaultdict, dequefrom itertools import permutations, combinations
input = sys.stdin.readlinesys.setrecursionlimit(10**7)
def I(): return input().strip()def II(): return int(I())def MII(): return map(int, I().split())def LII(): return list(MII())
def solve(): for line in sys.stdin: print(line) pass
if __name__ == "__main__": T = 1 # T = II() for _ in range(T): solve()set
def solve(): n = II() ans = sorted(set(MII())) print(len(ans)) print(*ans)dict
from collections import defaultdict
def solve(): n = II() cnt = defaultdict(int) for _ in range(n): x = II() cnt[x] += 1 ans = sorted(cnt.keys()) outs = [f"{k} {cnt[k]}" for k in ans] print("\n".join(outs))dijkstra
import heapqN = 10**5 + 1inf = 10**18d = [inf for _ in range(N)]vis = [False for _ in range(N)]g = [[] for _ in range(N)]
def dijkstra(s): heap = [(0, s)] d[s] = 0 while (heap): u = heapq.heappop(heap)[1] if vis[u]: continue vis[u] = True for v, w in g[u]: if not vis[v] and d[u] + w < d[v]: d[v] = d[u] + w heapq.heappush(heap, (d[v], v))
def solve(): n, m, s = MII() for _ in range(m): u, v, w = MII() g[u].append((v, w)) dijkstra(s) print(*d[1:n+1])combinations
from itertools import combinations
def solve(): n, k = MII() arr = range(1, n + 1) ans = [] for p in combinations(arr, k): line = [f"{x:3}" for x in p] ans.append("".join(line)) print("\n".join(ans))permutations
from itertools import permutations
def solve(): n = II() arr = range(1, n + 1) ans = [] for p in permutations(arr): s = [] for x in p: s.append(f"{x:5}") ans.append("".join(s)) print("\n".join(ans))