Python实现哈夫曼编码:贪心算法构建最优二叉树与压缩解压实战
开头如果你正在学数据结构或者已经上手了 Python 但总觉得“算法只会写、不会用”那么哈夫曼树Huffman Tree和哈夫曼编码Huffman Coding一定是你绕不过去的一个经典题目。别被名字吓到它的核心思想用一个词就能概括贪心——每次把权值最小的两棵树合并最终构造出一棵带权路径长度最小的二叉树。这个项目里我用 Python 完整实现了一遍哈夫曼树的构建、编码表的生成、字符串压缩与解压全程没有依赖任何第三方库只用标准库里的heapq、collections和struct。整个过程踩了不少坑尤其是“怎么把编码表写到压缩文件里”和“解压时如何正确读取比特流”这两个点网上很多教程都语焉不详我这次用自己的方案解决了。这篇文章会从“为什么需要哈夫曼编码”开始逐步拆解到最终代码的每一行适合正在学数据结构的学生、准备面试的开发者以及任何想用 Python 做压缩小工具的人。1. 内容整体设计与思路拆解1.1 从频率统计到变长编码哈夫曼在解决什么问题先想一个最基本的存储问题如果要把一串文本保存成二进制最朴素的方式是每个字符固定用 8 个比特一个字节表示。比如“hello world”这 11 个字符直接存就是 88 个比特。这种方式简单但显然不够聪明因为字母l出现了 3 次字母h只出现 1 次却花了同样多的比特去表示它们。哈夫曼编码的思路是出现频率高的字符用短编码出现频率低的字符用长编码这样总体长度就能降下来。听起来和摩斯电码很像但哈夫曼有一个摩斯电码没有的关键性质——前缀性任何一个字符的编码都不是另一个字符编码的前缀。这意味着编码可以无歧义地拼接和解码不需要额外的分隔符。为什么前缀性这么重要举个例子假设两个字符的编码分别是01和011那么当你读到011时你永远无法确定它是在表示第一个字符后面跟着1还是直接表示第二个字符。哈夫曼树天然规避了这个问题因为每个字符都对应树上的一个叶子节点从根到叶子的路径天然不会有其他字符的完整路径作为其前缀。1.2 为什么选 Python 来实现用 Python 做这个项目最大的优势是可读性极高且标准库刚好覆盖了核心需求。构建哈夫曼树需要反复取两个最小元素这正是heapq堆的拿手好戏统计字符频率collections.Counter一行就能搞定处理二进制文件struct和bytes也能满足需求。不过 Python 也带来一个明显短板性能。纯 Python 逐比特地拼接、写出在压缩大文件时会明显慢于 C 或 Rust 的实现。但作为教学项目这种“慢”反而是好事——每一步都能看清楚、能打断点、能打印中间变量这对于理解哈夫曼编码的流程至关重要。我在这个项目中刻意没有使用任何第三方库就是为了让你能完全掌握从原理到落地的每一个环节后续你完全可以把核心逻辑移植到其他语言。1.3 整体架构四个独立模块整个项目我拆成了四个功能明确的层级这样代码清晰也方便单独测试频次统计读取文本统计每个字符的出现次数。建树与编码表生成基于频次构建哈夫曼树再通过遍历树生成字符到比特串的映射表。压缩器根据编码表将原始文本逐字符转换为比特流写入二进制文件。解压器读取二进制文件中的比特流沿着哈夫曼树从根节点走到叶子恢复原始文本。这四个模块的依赖关系是单向的频次统计 - 建树 - 编码表 - 压缩/解压。这样的设计让每个环节都能独立验证调试时能快速定位问题。2. 核心细节解析与实操要点2.1 哈夫曼树的构建原理为什么总是合并最小的哈夫曼树的构建算法可以用一句话说明从所有节点中选出权值最小的两个合并成一个新节点新节点的权值为两者之和然后把这个新节点重新放回集合中重复这个过程直到只剩一个节点。这个算法的正确性本质上依赖于一个贪心选择性质最优前缀编码中频率最小的两个字符一定是最深且互为兄弟的叶子节点。这个性质可以通过交换论证来证明简单说就是如果两个最小频率的字符不在最深一层你总可以把它们换到最深一层并且总的带权路径长度不会增加反而可能减小。实操时要注意一个细节如果有多个权值相同的节点它们合并的先后顺序会影响最终生成的哈夫曼树的形状但不会影响带权路径长度。也就是说压缩率不变但编码表会变。这在实际解码时没有影响因为解码时用的是编码表或树结构而不是固定规则。但如果你期望两次运行得到完全一致的编码表就需要给相同权值的节点定义稳定的排序规则比如按字符的原始顺序作为次级排序键。2.2 二叉树节点用什么数据结构元组和对象的博弈实现哈夫曼树时一个关键决策是节点用什么数据结构表示。我尝试过两种方案方案 A使用普通类定义left、right、freq属性。代码直观但每次访问要写很多node.left而且放进堆里比较时还要额外写__lt__魔术方法。方案 B使用嵌套元组格式为(freq, 字符或节点, 左子树, 右子树)利用元组天然可比较的特性。代码简洁但可读性稍差。最终我选择了类方案因为它在后续压缩和解压的多次函数调用中更稳健尤其是在写__lt__时候需要注意如果堆中同时存在树节点和代表叶子节点的(freq, char)元组类型不一致会直接导致比较报错。这种错误在元组方案中极其隐蔽我在调试时花了不少时间才定位到。后来我的做法是统一节点格式叶子节点的左右子树都填None类型保持严格一致。2.3 编码表生成DFS 遍历树的递归套路得到哈夫曼树之后编码表就是树中每条根到叶子的路径向左记0向右记1。这个遍历用深度优先搜索DFS最自然核心代码量非常少def build_code_table(node, path, code_table): if node.left is None and node.right is None: code_table[node.char] path if path else 0 return build_code_table(node.left, path 0, code_table) build_code_table(node.right, path 1, code_table)注意有一个容易被忽略的边界情况如果文本中只有一种字符比如aaaa那么哈夫曼树只有一个叶子节点DFS 时path始终是空字符串。如果不做处理编码表会把该字符映射成压缩时就会出问题。我的处理是如果path为空则强制赋成0解压时如果哈夫曼树只有一个根节点解码逻辑也要做特殊判断。递归深度也是需要考虑的点。哈夫曼树理论上的最大深度可以达到字符种类数减 1比如输入 256 种字节最坏情况深度可能达到 255这远低于 Python 默认的递归深度限制1000所以这里用递归没问题。但如果你未来把字符集扩大到更大范围或者用多字节编码就要留意递归栈溢出的风险。3. 实操过程与核心环节实现3.1 频次统计与树的构建代码解析前面说了那么多概念这一步我们来写真正的代码。整个项目我分成了两个文件huffman_compress.py负责压缩huffman_decompress.py负责解压公共部分放在huffman_common.py里。先看公共部分的核心实现import heapq from collections import Counter, deque class HuffmanNode: __slots__ (char, freq, left, right) def __init__(self, charNone, freq0, leftNone, rightNone): self.char char self.freq freq self.left left self.right right def __lt__(self, other): return self.freq other.freq def build_freq_table(data: bytes) - dict: 统计字节频率返回 {字节值: 频次} counter Counter(data) return {b: c for b, c in counter.items() if c 0} def build_huffman_tree(freq_table: dict) - HuffmanNode: 根据频率表构建哈夫曼树返回根节点 heap [] for char, freq in freq_table.items(): node HuffmanNode(charchar, freqfreq) heap.append(node) heapq.heapify(heap) while len(heap) 1: left heapq.heappop(heap) right heapq.heappop(heap) parent HuffmanNode(freqleft.freq right.freq, leftleft, rightright) heapq.heappush(heap, parent) return heap[0]这里有几个技术细节值得展开使用__slots__这个优化很多人不知道它能为每个节点节省大量内存。哈夫曼树节点动辄几百上千个默认的__dict__字典会占据大量内存__slots__直接把属性固定在元组结构上内存占用能降低 30% 以上而且访问速度还更快。__lt__方法的陷阱如果两个节点的频率相同heapq会比较它们的其它信息此时会调用__lt__但频率相同无法决定顺序于是 Python 会尝试比较第二个属性。我曾经在没有__slots__的版本中遇到过一个诡异的 bug频率相同的两个节点__lt__返回False后堆的内部顺序完全依赖于对象内存地址导致同一输入在不同运行中产生了不同的哈夫曼树形状。这个问题在单一进程内不算致命但如果你要做结果对比或单元测试最好在__lt__中加入char的次级比较保证稳定排序。3.2 编码表生成与原始比特流的组装构建完树之后需要递归生成编码表同时把原始字节流转换为比特字符串再按 8 位一组存入字节数组。这里是最容易出现性能瓶颈和逻辑错误的地方。def generate_code_table(root: HuffmanNode) - dict: code_table {} def dfs(node, path): if node.left is None and node.right is None: code_table[node.char] path if path else 0 return if node.left: dfs(node.left, path 0) if node.right: dfs(node.right, path 1) dfs(root, ) return code_table def compress_data(data: bytes, code_table: dict) - bytes: 按编码表将原始数据压缩为字节 bit_string .join(code_table[b] for b in data) # 末尾补零到8的倍数 padding (8 - len(bit_string) % 8) % 8 bit_string 0 * padding # 按8位切分 byte_values [] for i in range(0, len(bit_string), 8): byte_values.append(int(bit_string[i:i8], 2)) return bytes(byte_values), padding这段代码逻辑不难但有一个地方极易出错编码表的键到底是整数还是字符串。因为data是bytes遍历得到的是int所以code_table的键必须也是int。如果你用code_table[b]查询而表里存的是str或chr就会直接KeyError。我建议在构建频率表时就用int作为键统一类型避免后面互相转换。另一个性能问题是字符串拼接。对于大文件.join(...)会把整个比特串先保存在内存里。比如一个 10MB 的文件如果平均压缩率在 60% 左右生成的比特串长度大约为 10_000_000 * 8 * 0.6 48_000_000 个字符这在内存中大概占 100MB 以上有点压力但通常还可以接受。但如果你想压缩超大文件建议改为流式处理每生成 8192 个字符就刷一次缓冲或者分批编码而不是一次性拼接全部。3.3 文件格式设计与头部信息写入压缩后的文件不能只存压缩数据因为解压时需要知道哈夫曼树的结构或编码表信息。这里有两种常见方案方案一存频率表。解压时用频率表重建哈夫曼树。文件体积略大但通用性强还方便调试。方案二存编码表。也可以但需要显式记录每个字符对应的码长而且构建树时还需要反转不如方案一自然。我选择了存频率表因为它和建树模块天然衔接。文件格式设计如下---------------------------------------------------------- | 频率表条目数(2B) | 字符字节(1B) | 频率值(4B) | ← 重复 N 次 ---------------------------------------------------------- | 填充位数(1B) | 原始未压缩长度(8B) | 压缩后的二进制数据 ... | ----------------------------------------------------------频率表条目数和压缩后数据总大小都需要在压缩时计算并写入。最让我头疼的是原始未压缩长度必须精确到字符数因为解压时最后几个字节可能存在没有意义的填充比特单纯依靠填充位数不足以安全地去除它们还必须知道原始数据到底有多少个字符。写入头部信息的代码大概如下def pack_header(freq_table: dict, padding: int, orig_len: int) - bytes: header b # 条目数最多65535种字符足以覆盖256种字节值 header len(freq_table).to_bytes(2, big) for char, freq in freq_table.items(): header bytes([char]) header freq.to_bytes(4, big) header bytes([padding]) header orig_len.to_bytes(8, big) return header注意字符范围因为处理的是原始bytes每个字符值在 0~255 之间因此bytes([char])恰好编码成一个字节。频率值理论上可能超过 4 字节表示范围40 亿但正常场景下不会所以用 4 字节是合理选择。3.4 解码流程沿着哈夫曼树走向叶子解压是整个流程中最考验耐心的一步。关键在于不能用编码表倒推而是用哈夫曼树本身进行状态机式的逐位解码。从根节点出发读入一个比特如果是0就走向左子树1就走向右子树遇到叶子节点就输出该字符然后回到根节点继续。def decompress_data(compressed: bytes, root: HuffmanNode, padding: int, orig_len: int) - bytes: # 先把压缩数据转成比特字符串 bit_string for byte in compressed: bit_string f{byte:08b} if padding: bit_string bit_string[:-padding] # 截掉末尾填充 result bytearray() node root bit_index 0 # 特殊处理树只有一个节点的情况 if node.left is None and node.right is None: return bytes([node.char]) * orig_len while len(result) orig_len and bit_index len(bit_string): bit bit_string[bit_index] if bit 0: node node.left else: node node.right bit_index 1 if node.left is None and node.right is None: result.append(node.char) node root # 重新回到根 return bytes(result)这条代码的核心是边解码边统计输出长度。只要输出字符数没有达到orig_len就继续推进这就不会受填充比特的干扰。很多人踩的坑是单纯依赖padding来结尾但如果你在读取压缩文件时把字节流末尾多读少读了一位整个解码就会错位所以用orig_len做终止条件是最稳妥的。3.5 完整压缩流程与压缩率观测把上述模块串起来压缩一个文本文件的完整流程如下以二进制模式读取文件。统计频率表。构建哈夫曼树。生成编码表。将原始数据编码为比特串计算填充位数。组装头部信息和压缩数据写入输出文件。下面是我的主程序代码def compress_file(input_path: str, output_path: str): with open(input_path, rb) as f: data f.read() freq_table build_freq_table(data) root build_huffman_tree(freq_table) code_table generate_code_table(root) compressed, padding compress_data(data, code_table) header pack_header(freq_table, padding, len(data)) with open(output_path, wb) as f: f.write(header compressed) ratio (len(header) len(compressed)) / len(data) * 100 if data else 0 print(f原始大小: {len(data)} 字节) print(f压缩后大小: {len(header) len(compressed)} 字节) print(f压缩率: {ratio:.2f}%)我拿一段典型的英文小说测试压缩率大约在 55%~65% 之间。如果是随机数据因为频率分布接近均匀哈夫曼编码基本无收益压缩率甚至会超过 100%因为头部信息本身也要占字节。这暴露了哈夫曼编码的局限性它本质上依赖符号分布的不均匀性如果源数据已经是被压缩过的比如 JPEG、MP3再跑哈夫曼基本没有意义。4. 常见问题与排查技巧实录4.1 编码表解码时 KeyError类型不一致症状压缩时code_table[b]报KeyError或者b明明是整数却在表里找不到。原因最常见的类型混淆是code_table的键用了chr(b)字符串而查表用的是b整数。另一个可能是在读取频率表时用int.from_bytes读出的结果和写入时的类型不一致。排查方法在压缩前打印前 20 个键值对确认类型和字符值更稳妥的办法是压缩后用编码表反向构建一个char到二进制串的映射先解压一小段测试数据看是否能还原。4.2 解压结果乱码或长度不对症状解压后能运行但结果明显错误或者字符数量对不上原始文件。原因多半出在比特流的拼接顺序上。写文件时按字节组织了compressed转回比特串时如果对每个字节用format(byte, 08b)高位补零是关键。如果写成bin(byte)直接转换bin(0x05)得到0b101长度不足、高位丢零整个比特流就错了。另一个隐蔽问题如果你的文本里有非 ASCII 字符你直接用str读取文件再编码可能产生多字节 UTF-8 序列。此时解压回字符串时如果不按 UTF-8 解码就会得到错误文本。我的建议是全程以bytes形式处理不要在中间层做字符串转换。4.3 单字符文件的边界处理症状输入文件只有一个字符比如z解压时报错或输出为空。原因哈夫曼树只有一个根节点没有左右孩子generate_code_table会给z分配编码0。但解压时从根节点出发读入第一个比特0后尝试访问node.left发现是None直接抛异常。解决方法在解压函数开头单独判断单节点情况if root.left is None and root.right is None: return bytes([root.char]) * orig_len这个分支看起来简单但它的存在意味着你要确保所有输入都经过频率表构建哪怕只有一种字符。4.4 大文件压缩慢怎么优化症状压缩一个 50MB 的文件耗时超过 30 秒且内存占用惊人。原因主要瓶颈在compress_data中一次性生成全量比特串。50MB 可以生成接近 3 亿个字符的字符串加上中间的编码表查询内存会非常高。优化思路使用自定义的比特缓冲写入器每凑满 8 个比特就写一个字节不需要保存完整比特串。使用memoryview或分段读取文件避免一次性读入全部内容。对较大的编码表预先把它转成元组数组用索引访问代替字典访问。我实测中使用分段读取和即时比特缓冲后内存占用降低了近 80%速度提升了一倍以上。4.5 一个问题排查实例文件尾部多出 1 字节这是我在实际测试中遇到最麻烦的 bug。解压后输出比原始文件多了 1 个字符而且只多出 1 个。后来我发现问题出在解压终止条件上——我用的是while bit_index len(bit_string)没有检查输出长度导致最后一个字节因为填充位被解压出一个多余的0x00字符。添加len(result) orig_len条件后问题立刻解决。这个坑值得强调解压的唯一可靠依据是原始长度而不是压缩数据本身的结束位置。5. 可扩展方向与更多应用思考5.1 从文本压缩到通用文件压缩这个项目如果止步于文本压缩其实是有点可惜的。哈夫曼编码本质上适用于任何“符号流”只要你能定义符号粒度的频率分布。你可以尝试把 8 位字节作为符号对图片、音频、视频等文件直接做压缩测试。大部分媒体文件的频率分布接近均匀压缩率可能很差但你可以观察分析哪些文件类型适合哈夫曼压缩。更有意思的方向是符号粒度升级。比如对文本文件你可以先做 BWT 变换Burrows-Wheeler Transform再进行 MTFMove-To-Front最后跑哈夫曼——这就是 bzip2 压缩工具的大致管线。符号之间从独立变成有前后文关联压缩率能大幅提升。5.2 结合 JSON 或 Pickle 做对象序列化如果需要序列化一个较大的字典结构或对象列表可以在序列化之后再做一层哈夫曼压缩。这个方案比直接用gzip.compress更灵活因为你可以调整频率表的粒度甚至针对特定字段做高频值合并。我自己写过一个配置文件的加载器把默认配置用哈夫曼压缩后嵌入到二进制资源中启动时再解压加载文件和可执行包体积都小了很多。5.3 构建自适应哈夫曼编码标准哈夫曼编码需要先扫描一遍数据以获得频率表这意味着数据必须可缓存无法应对流式场景。一种改进是自适应哈夫曼编码Adaptive Huffman Coding它一边读取数据一边更新频率表和树结构不必预扫描。这种技术在实时通信和数据流压缩中很重要比如某些网络协议中会用到类似的动态编码。实现自适应哈夫曼比静态版本复杂不少因为每次字符频率变化都可能导致树结构局部调整涉及节点的权重上浮、交换和父子关系更新。作为后续进阶项目它能让你的数据结构和算法能力再上一个台阶。5.4 引入更高效的数据结构用数组模拟哈夫曼树在某些场景下节点的left、right指针占用空间较大而且 Python 对象本身有较高的内存开销。如果追求性能可以用两个数组分别存储左右孩子索引再用一个数组存储权值用整数索引代替对象引用。这样整个哈夫曼树可以被保存为紧凑的数组结构而压缩后的头部信息也可以直接存储这些数组而无需重建。这个思路在移动端和嵌入式开发中很常见因为指针和对象引用在这些环境下代价高昂。6. 项目总结与经验心得这个项目虽然只做了一件事——用哈夫曼编码压缩数据但它串起来的知识点其实非常多优先队列、二叉树遍历、位运算、二进制文件格式设计、边界条件处理还有性能优化。我从一开始被“编码表转来转去”绕晕到最后能顺畅地写完压缩和解压两个完整模块中间最大的体会是不要试图一步到位写大而全的脚本而是像搭积木一样一个函数一个函数地测试。还有一点特别想分享对这类算法项目强烈建议在实现时保留足够的日志输出或调试模式比如打印每一步生成的编码表和树结构。因为出现问题时像“解压结果错了一个字符”这种 bug 很难靠肉眼定位只有把中间过程全部可视化才能快速缩小排查范围。如果你是按这篇文章从零开始写自己的哈夫曼项目我最想给你的建议是先实现只能处理单一字符集的简化版本确保压缩和解压逻辑正确再扩展通用文件类型。一次别求大跑通一遍“压缩-解压-对比”的最小闭环比直接写一个完整压缩工具带来的收益要高得多。

相关新闻

岐口潮汐表查询与赶海攻略:2026年1月28日潮时详解

岐口潮汐表查询与赶海攻略:2026年1月28日潮时详解

冬天聊赶海,很多人第一反应是“海边有什么好去的”。但住在渤海湾西岸的人知道,腊月里的岐口滩涂,反而是一年中海货最肥的时候。前几天一个朋友在群里甩了句“岐口潮汐表查询2026-01-28”,说想趁春节前带孩子去挖一波蛤蜊。我看着…

2026/10/10 4:53:21 阅读更多 →
BP神经网络函数逼近:Python实现、调参与故障排查

BP神经网络函数逼近:Python实现、调参与故障排查

简介:BP神经网络实现函数逼近是机器学习中的经典任务,常用于处理非线性映射问题。这份PDF围绕BP(Back Propagation)网络的核心原理展开,从拓扑结构、正向传播与反向传播推导,到权重更新和梯度下降策略均有清…

2026/10/10 4:52:21 阅读更多 →
PCA9422+PIC18F87J50双芯片电源管理方案设计

PCA9422+PIC18F87J50双芯片电源管理方案设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 4:52:21 阅读更多 →

最新新闻

【计算机毕业设计单片机案例】基于单片机的室内环境安全指标采集、屏幕闪烁告警与通风装置设计 基于单片机的物联网室内光照空气质量综合监测与智能调控系统设计(030112)

【计算机毕业设计单片机案例】基于单片机的室内环境安全指标采集、屏幕闪烁告警与通风装置设计 基于单片机的物联网室内光照空气质量综合监测与智能调控系统设计(030112)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

2026/10/10 5:37:37 阅读更多 →
MATLAB小波变换雷达信号时频分析实战:原理、代码与参数避坑

MATLAB小波变换雷达信号时频分析实战:原理、代码与参数避坑

1. 从一张"平均过的频谱"说起:雷达信号为什么非得换种看法接手雷达信号时频分析的头一个月,我曾在频谱分析仪前坐了整整半天:示波器上明明是两段完全不同的脉冲——一段频率从低频往高频扫,另一段干脆在高频和低频之间来…

2026/10/10 5:37:37 阅读更多 →
蔬菜定价与补货优化:从数据清洗到数学建模全流程解析

蔬菜定价与补货优化:从数据清洗到数学建模全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 5:37:37 阅读更多 →
AI+软件测试04(AI应用技巧)

AI+软件测试04(AI应用技巧)

文章目录说明AI介绍一、AI助力需求分析二、AI助力测试计划三、AI助力测试用例设计四、AI助力测试用例执行4.1 环境部署文档的生成4.2 生成shell脚本4.3 生成冒烟测试用例4.4 缺陷预测五、AI助力测试报告六、补充最新课程笔记01-AI工具基本应用(提示词)AI…

2026/10/10 5:37:37 阅读更多 →
大规模电动汽车随机充放电优化:局部求解策略与MATLAB实现

大规模电动汽车随机充放电优化:局部求解策略与MATLAB实现

最近帮一个园区做充电桩配套调度方案时,最头疼的问题就是晚间六点到九点,一两百辆车同时扎进电网。车主到达时间不确定、剩余电量不确定、第二天出发时间也不确定——这其实就是一个典型的大规模电动汽车随机充放电优化问题。起初我的想法比较天真&#…

2026/10/10 5:37:37 阅读更多 →
PP2719 搞笑世界杯

PP2719 搞笑世界杯

Part 1:题意:总共有 n 张 A 票,n 张 B 票。前面的人不断抛硬币选 A/B,只要对应票还有剩就拿走。求最后剩下两张是同一种票的概率(注意:输入给的是 2n,所以读入后要除以 2 得到 n)。Part 2:为什么…

2026/10/10 5:36:37 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 5:23:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 6:17:20 阅读更多 →