推荐阅读
- 图算法专题入口:
/zh/dev/algorithm/graph/00-graph-algorithms-learning-path/ - 先掌握工程常用算法(排序/搜索/缓存)
- 再看复杂度与边界条件处理
- 最后看在系统中的落地与优化
/zh/dev/algorithm/graph/00-graph-algorithms-learning-path/图算法专题 学习路径:/zh/dev/algorithm/graph/00-graph-algorithms-learning-path/ 本目录收纳图遍历、可达性、最短路、连通性、中心性、社区发现、子图匹配、动态图、图分区与图计算模型相关文章。
副标题 / 摘要 生成不重复随机序列的标准方法是洗牌。本文说明原理并提供可运行实现。 目标读者 学习随机算法的开发者 需要抽样与随机化的工程师 算法面试准备者 背景 / 动机 随机且不重复是很多场景的基础能力,例如抽奖、打乱顺序、采样。 洗牌算法能保证均匀分布且复杂度可控。 核心概念 均匀随机:所有排列等概率 洗牌(Fisher-Yates):线性时间打乱 采样:在大规模集合中取子集 实践指南 / 步骤 初始化序列 从尾到头随机交换 保证每一步的随机性 输出最终序列 可运行示例 import random def shuffle(nums): for i in range(len(nums) - 1, 0, -1): j = random.randint(0, i) nums[i], nums[j] = nums[j], nums[i] return nums if __name__ == "__main__": nums = list(range(1, 11)) print(shuffle(nums)) 解释与原理 Fisher-Yates 在第 i 步随机选择 [0..i] 中的元素交换。 这样可保证所有排列等概率出现。 常见问题与注意事项 能否用 sort + random key? 不建议,分布不一定均匀。 是否需要固定随机种子? 测试时建议固定,生产可随机。 ...
副标题 / 摘要 尾递归可以把递归的“返回工作”提前完成,从而具备优化潜力。本文用阶乘示例说明概念与限制。 目标读者 学习递归与函数式思想的开发者 需要写可读性强的算法代码的人 关注性能的工程师 背景 / 动机 普通递归在返回阶段仍需要计算,导致栈帧无法复用。 尾递归把“结果累积”放在参数里,使返回阶段无需额外计算。 核心概念 尾递归:递归调用是函数的最后一步 累加器:把中间结果传入下一层 尾调用优化(TCO):编译器复用栈帧 实践指南 / 步骤 把中间结果写成累加器参数 让递归调用成为最后一步 确认语言是否支持尾调用优化 在不支持 TCO 的语言改为迭代 可运行示例 def fact_tail(n: int, acc: int = 1) -> int: if n <= 1: return acc return fact_tail(n - 1, acc * n) if __name__ == "__main__": print(fact_tail(5)) 解释与原理 尾递归把计算提前完成,返回阶段只需返回结果。 若语言支持 TCO,就能复用栈帧,避免栈溢出。 常见问题与注意事项 Python 支持 TCO 吗? 不支持,所以深度大仍会栈溢出。 尾递归一定快吗? 取决于语言与编译器优化。 何时改为迭代? 当递归深度不可控时。 最佳实践与建议 尾递归用于可读性与函数式风格 在生产中评估语言是否支持 TCO 大规模递归优先使用迭代 小结 / 结论 尾递归提供了优化潜力,但效果依赖语言支持。 理解其形式有助于写出更清晰的递归代码。 ...
副标题 / 摘要 从 socket 到 HTTP 响应,最小 Web 服务器可以帮助理解网络协议的关键流程。本文给出可运行示例。 目标读者 想理解 HTTP 与 socket 的开发者 学习网络编程的工程师 需要构建服务端基础的人 背景 / 动机 很多 Web 框架屏蔽了底层细节。 写一个最小服务器能帮助理解请求解析、响应构造与连接管理。 核心概念 Socket:网络通信的基础接口 HTTP 请求/响应:文本协议 监听/接受连接:服务端循环 实践指南 / 步骤 监听端口 接受连接并读取请求 构造 HTTP 响应并返回 关闭连接 可运行示例 import socket def run(host="127.0.0.1", port=8080): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind((host, port)) s.listen(1) print("listening on", port) conn, _ = s.accept() data = conn.recv(1024) if data: body = "Hello" resp = ( "HTTP/1.1 200 OK\r\n" f"Content-Length: {len(body)}\r\n" "Content-Type: text/plain\r\n\r\n" f"{body}" ) conn.sendall(resp.encode("utf-8")) conn.close() s.close() if __name__ == "__main__": run() 解释与原理 服务器需要:监听 → 接受连接 → 读取请求 → 返回响应。 HTTP 是文本协议,因此构造响应字符串即可。 ...
副标题 / 摘要 只有队列(FIFO)时,也能实现栈(LIFO)。本文展示单队列旋转法,并分析复杂度与边界。 目标读者 刷题与面试准备的开发者 需要理解数据结构转换的人 初中级算法学习者 背景 / 动机 栈的后进先出与队列的先进先出相反。 “旋转队列”可以把最新元素移动到队头,从而模拟栈顶。 核心概念 队列(FIFO):先进先出 栈(LIFO):后进先出 队列旋转:把新元素转到队首 实践指南 / 步骤 入栈:将元素入队 旋转队列:把队首依次出队再入队,直到新元素位于队首 出栈:直接出队 取栈顶:查看队首 可运行示例 from collections import deque class MyStack: def __init__(self): self.q = deque() def push(self, x: int) -> None: self.q.append(x) for _ in range(len(self.q) - 1): self.q.append(self.q.popleft()) def pop(self) -> int: return self.q.popleft() def top(self) -> int: return self.q[0] def empty(self) -> bool: return not self.q if __name__ == "__main__": s = MyStack() s.push(1) s.push(2) print(s.top()) print(s.pop()) print(s.empty()) 解释与原理 每次 push 后旋转队列,使新元素位于队首。 这样 pop 就相当于弹出“栈顶”。 ...
副标题 / 摘要 当系统只提供栈(LIFO)时,如何构建队列(FIFO)?本文用双栈法给出清晰实现与工程要点。 目标读者 刷题与面试准备的开发者 需要理解数据结构转换的人 希望掌握复杂度分析的初中级工程师 背景 / 动机 队列的先进先出与栈的后进先出相反。 双栈法通过“翻转顺序”实现队列语义,是经典的结构变换题。 核心概念 栈(LIFO):后进先出 队列(FIFO):先进先出 双栈翻转:把输入顺序倒置为输出顺序 实践指南 / 步骤 入队:压入 in 栈 出队/取队首:若 out 栈为空,将 in 栈全部弹出并压入 out 栈 从 out 栈弹出:即为队首 保持延迟搬运:只在 out 为空时搬运 可运行示例 class MyQueue: def __init__(self): self._in = [] self._out = [] def push(self, x: int) -> None: self._in.append(x) def _move(self) -> None: if not self._out: while self._in: self._out.append(self._in.pop()) def pop(self) -> int: self._move() return self._out.pop() def peek(self) -> int: self._move() return self._out[-1] def empty(self) -> bool: return not self._in and not self._out if __name__ == "__main__": q = MyQueue() q.push(1) q.push(2) print(q.peek()) print(q.pop()) print(q.empty()) 解释与原理 in 栈负责“输入顺序”,out 栈负责“输出顺序”。 当 out 为空时,把 in 全部倒入 out,就实现了 FIFO 的逆序输出。 ...
副标题 / 摘要 栈溢出通常由无限递归或过深调用导致。本文用可运行示例解释原因与规避策略。 目标读者 学习递归与算法的开发者 关注性能与稳定性的工程师 需要理解运行时限制的初学者 背景 / 动机 每次函数调用都会占用栈空间。 当调用深度超过上限,程序会抛出栈溢出错误或崩溃。 核心概念 调用栈:保存函数调用上下文 递归深度:递归层数过深会耗尽栈 尾递归优化:某些语言可复用栈帧 实践指南 / 步骤 确保递归有明确终止条件 控制递归深度或改为迭代 对深度递归设定保护阈值 在高风险路径做测试 可运行示例 import sys sys.setrecursionlimit(1000) def boom(n): return boom(n + 1) if __name__ == "__main__": try: boom(0) except RecursionError as e: print("stack overflow:", e) 解释与原理 每次递归都会压入新的栈帧。 当深度超过解释器或系统限制时,就会触发栈溢出。 常见问题与注意事项 提高递归深度就能解决吗? 只能延迟问题,不能根治。 尾递归一定不会溢出吗? 取决于语言是否支持尾递归优化。 迭代一定更好吗? 不一定,但在深度很大时更安全。 最佳实践与建议 用迭代替代深度递归 为递归函数加入深度保护 对递归路径做压力测试 小结 / 结论 栈溢出是递归深度过大导致的运行时问题。 通过终止条件、迭代替换与深度限制可以有效避免。 参考与延伸阅读 Python Recursion Limit The Art of Computer Programming 元信息 阅读时长:5~7 分钟 标签:递归、栈溢出 SEO 关键词:栈溢出, 递归深度 元描述:解释栈溢出的成因与规避方式。 行动号召(CTA) 检查你的递归函数,确认终止条件是否足够严格。