手写堆排序原理与Python实现:从完全二叉树到TopK实战

开门见山说一个我自己的感受:面试里让人手写堆排序,十个候选人至少有六个会卡壳。快排大家背得滚瓜烂熟,归并排序也多半能默出来,唯独堆排序,经常是写了半天,最后堆是堆了、序是没排出来。原因也简单,堆排序的逻辑链条比快排长——你得先建堆,再反复把堆顶扔到末尾,再调整剩余部分,每一步单独看不难,合在一起就绕。但换个角度看,这是好事:一旦你真把堆排序吃透了,你对“完全二叉树在数组里怎么存”“优先级队列为什么 O(log n)”“TopK 问题凭什么快”这些事的理解都会上一个台阶。这篇就拿 Python 为例,从原理一步步讲到代码,再把复杂度、稳定性、实际场景里的坑都聊透。

1. 先搞清楚堆排序到底在排什么

1.1 数组里藏着一棵完全二叉树

堆排序处理的数据结构叫“堆”,堆本质上是一棵完全二叉树。完全二叉树什么概念?就是除了最后一层,其他层都填满,最后一层的节点也尽量从左往右排。这个结构有一个特别适合计算机存储的性质:你可以用一个普通数组直接表示它,不用画任何指针。

假设数组是nums,长度是n,下标从 0 开始:

  • 下标为i的节点,左孩子下标是2 * i + 1;
  • 右孩子下标是2 * i + 2;
  • 它的父节点下标是(i - 1) // 2。

我当年第一次看到这个关系时觉得平平无奇,后来才意识到这是堆一切操作的根基。因为有了这种“按下标寻址”的方式,堆才能做到在数组内部原地调整,不需要额外开一棵树,也不需要递归地构建节点对象。你只需要把数组想象成小区里按固定规则排列的一排房子,父亲住哪间、孩子住哪间,全都能用公式算出来。

堆分两种:大根堆和小根堆。大根堆的意思是,任何一个父节点的值都不小于它的孩子节点,也就是说堆顶是整个堆里最大的元素。小根堆正好相反,父节点不大于孩子,堆顶是全局最小。Python 内置的heapq模块默认就是小根堆。堆排序用大根堆比较顺手,因为我们要把最大的元素不断“摘”出来放到数组末尾。

1.2 堆排序其实是一种聪明的选择排序

很多人没意识到,堆排序的思想源头是选择排序。选择排序每轮从剩余元素里找出最小值(或最大值),把它放到正确位置,时间复杂度是 O(n²),因为它每轮都要从头到尾扫一遍找最值。

堆排序做了一件什么事?它用一个堆来维护“剩余元素的最大值”,让“找最大值”这个操作从 O(n) 降到了 O(log n)。代价是排序前需要先花 O(n) 时间把整个数组整理成堆,之后每一轮取堆顶、调整堆,都是 O(log n)。所以整体是 O(n log n)。

打个比方:选择排序是每轮把整个仓库翻一遍找最大件货物;堆排序是先花点时间把货物码成金字塔形,最大的永远放在塔尖,之后每次搬走塔尖上的货,再花点功夫把下一件最大的货“拱”到塔尖。后者前期多花了一次整理的时间,但后续每次找货都快得多。

这种“数据组织方式的改变,换来后续操作效率提升”的思路,并不只属于堆排序。你之后学优先队列、学调度算法、学 TopK,都会反复遇到同样的模式。

2. 核心逻辑:先建堆,再反复“扔堆顶”

2.1 建堆的起点:为什么从最后一个非叶子节点开始

堆排序的第一步,是把一个无序数组整理成一个大根堆。这个操作叫build_heap,网上也常叫heapify。

直观地想,要从一个完全无序的数组得到“父节点不小于子节点”的全局约束,总不能真的从根节点开始往下检查吧?如果根节点很小、下面藏着大数,一次下沉根本不够。标准做法是从最后一个非叶子节点开始,从右往左、从下往上,逐个做下沉调整。

最后一个非叶子节点的下标是多少?是n // 2 - 1。因为最后一个叶子节点下标是n - 1,它的父节点就是(n - 1 - 1) // 2,也就是n // 2 - 1。从它开始,往前遍历到下标 0,每次对一个节点执行“下沉”操作。

这里有一个很多人初学时会困惑的点:为什么不是从下标 0 开始,或者为什么不能只对根节点做一次下沉?因为只调整根节点只能保证根节点的位置暂时正确,它下沉到某个子节点后,可能又破坏了那个子树里的堆结构。所以必须从底部开始,让每个子树先分别变成一个合法的堆,再往上一层合并调整。

从下往上的另一个原因是:叶子节点本身没有孩子,天然满足堆的性质,不需要处理。需要处理的节点只有非叶子节点,也就是前n // 2个节点。这就是为什么建堆的循环只需要从n // 2 - 1走到0,而不是从头走到尾。

2.2 单次下沉到底做了什么

下沉操作,也就是sift_down,做的事情可以用一句话概括:把某个节点往下挪,直到它不小于它的左右孩子,或者它已经到叶子层。

具体逻辑是这样:

  1. 把当前节点记为i,先认为最大值就在i;
  2. 看左孩子2 * i + 1,如果它存在且值比当前最大值还大,更新最大值为左孩子;
  3. 再看右孩子2 * i + 2,如果它存在且值更大,更新最大值为右孩子;
  4. 如果最大值已经不是原来的i,说明当前节点需要和孩子交换位置,交换后继续从新的位置往下重复这个过程;
  5. 一直到当前节点没有孩子,或者它已经比两个孩子都大,下沉结束。

注意一个细节:左右孩子比较的顺序并不重要,因为我们要找的是更大值,谁先谁后不影响结果。但如果你为了“顺便”做稳定排序而故意用左偏向或者右偏向,那也没用,堆排序本身的交换逻辑就会破坏稳定性,这一点后面专门说。

2.3 建堆的时间复杂度为什么是 O(n)

这是堆排序里最反直觉的地方。明明要把 n 个元素搭成一个堆,为什么不是 O(n log n)?答案是:大多数节点根本不需要下沉那么多层。

你想想,完全二叉树里,高度为 h 的节点,最多下沉 h 次。而高度越小(越靠近根)的节点数量越少,高度越大(越靠近叶子)的节点数量越多。具体地说,叶子层不需要下沉;叶子上面一层最多下沉 1 次,这一层大约有 n/4 个节点;再上面一层最多下沉 2 次,大约有 n/8 个节点。所有节点的下沉次数加起来是一个收敛的级数:

n/4 * 1 + n/8 * 2 + n/16 * 3 + ... ≈ n

所以建堆的总工作量是 O(n),不是 O(n log n)。这个结论我没少被面试官追问过,值得自己动手算一遍。很多人背下了“建堆 O(n)”,但让他解释原因就露馅了。

2.4 排序阶段:交换堆顶与末尾

建完堆之后,数组最大的元素一定在nums[0]。排序阶段就变得机械了:

  1. 把nums[0]和nums[n-1]交换,此时最大值到了数组末尾,相当于“安置好了”;
  2. 把堆的大小减 1,假装最后那个元素已经不属于堆;
  3. 对新的nums[0]做一次下沉,让它重新成为大小为 n-1 的堆的合法堆顶;
  4. 重复以上过程,直到堆大小缩到 1。

这里要特别强调“堆大小减 1”这一步。很多初版代码写错,就是因为在交换之后忘了把边界缩小,导致下沉时又把已经排好的末尾元素给算进堆里了。我当时写的时候也栽过一次,最后排查出来就是循环边界写成了for i in range(n-1, 0, -1)但内部调整时没有把“当前未排序长度”递减。

正确写法里,通常用一个循环变量end表示当前堆的末尾下标,每次交换后end -= 1,下沉时基于[0, end]这个区间操作。

3. Python 实现:一份不调库的完整代码

3.1 可运行的分步实现

如果面试要求手写堆排序,通常指的是实现上面说的那套逻辑,而不是直接用heapq。下面这份代码我建议你理解后自己盲写几遍,写完再对照:

def sift_down(nums, start, end): """在 nums[start:end+1] 范围内,将 start 位置下沉到合适位置""" i = start while True: left = 2 * i + 1 right = 2 * i + 2 largest = i if left <= end and nums[left] > nums[largest]: largest = left if right <= end and nums[right] > nums[largest]: largest = right if largest == i: break nums[i], nums[largest] = nums[largest], nums[i] i = largest def heap_sort(nums): n = len(nums) # 建堆:从最后一个非叶子节点开始,自下而上下沉 for i in range(n // 2 - 1, -1, -1): sift_down(nums, i, n - 1) # 排序:每次把堆顶(最大值)交换到末尾 for end in range(n - 1, 0, -1): nums[0], nums[end] = nums[end], nums[0] sift_down(nums, 0, end - 1) return nums if __name__ == "__main__": test = [6, 3, 8, 5, 2, 7, 1, 4] print(heap_sort(test))

我建议你盯着sift_down里的largest = i和break想一下:如果当前节点已经大于两个孩子,说明它到位了,直接退出循环。这是下沉的终止条件,也是最容易写漏的地方。有次我带某开发者一起过代码,他就是忘了在“无需交换”时退出,导致函数无限循环,程序直接卡死。

3.2 用具体数组走一遍流程

拿上面代码里的[6, 3, 8, 5, 2, 7, 1, 4]来推演,n = 8。

先建堆:从下标8 // 2 - 1 = 3开始,依次对下标 3、2、1、0 做下沉。

  • 下标 3 是节点 5,它的孩子是下标 7 的 4 和下标 7 越界的右孩子?这里左孩子2*3+1=7正好是最后一个元素 4,5 比 4 大,不用动。
  • 下标 2 是节点 8,孩子下标 5(7)和 6(1),8 最大,不用动。
  • 下标 1 是节点 3,孩子下标 3(5)和 4(2),5 更大,交换 3 和 5,此时下标 1 变成 5,下标 3 变成 3。
  • 下标 0 是节点 6,孩子下标 1(5)和 2(8),8 最大,交换 6 和 8。

建堆后数组变成[8, 5, 7, 3, 2, 6, 1, 4],堆顶是 8。

排序阶段:

  1. 交换 8 和 4,堆末尾为 8,对堆顶 4 下沉,得到[7, 5, 6, 3, 2, 4, 1, 8];
  2. 交换 7 和 1,对堆顶 1 下沉,得到[6, 5, 4, 3, 2, 1, 7, 8];
  3. 往下依次…最终得到[1, 2, 3, 4, 5, 6, 7, 8]。

这个过程建议亲手在纸上模拟一遍,每个交换都写出来。我学的时候就是靠这种“人工跑代码”,才真正理解为什么每次都要从堆顶重新下沉,而不是从别的位置开始。

3.3 如果允许调库,如何用 heapq 快速完成堆排序

Python 内置的heapq是小根堆,直接heappop会从小到大输出元素。所以最简实现反而是“用最小堆 + 依次弹出”:

import heapq def heap_sort_with_lib(nums): heapq.heapify(nums) return [heapq.heappop(nums) for _ in range(len(nums))]

这段代码很短,但它有一个问题:不是原地排序,需要额外的 O(n) 空间保存结果。而且你要明白,它跟手写的堆排序在排序阶段的做法不同——手写版是交换到原数组末尾,利用原有空间;这个版本是把堆顶弹到新列表里。如果面试官问“原地堆排序怎么写”,你还是得回到 3.1 节那份代码。

4. 复杂度、稳定性与“为什么常用排序不用堆”

4.1 时间和空间复杂度对照

堆排序的复杂度结论要记牢:

指标结果说明
最好时间复杂度O(n log n)无论数据是否有序,堆排序都要建堆、反复下沉
最坏时间复杂度O(n log n)不存在快排那样的退化到 O(n²) 的输入
平均时间复杂度O(n log n)稳定在 O(n log n)
空间复杂度O(1)原地排序,只用几个辅助变量
稳定性不稳定堆内交换可能把相等元素相对顺序打乱

这里有个对比让人印象深刻:快排平均也是 O(n log n),但最坏情况能到 O(n²),堆排序却在任何输入下都是 O(n log n)。那为什么工程上默认排序反而不选堆排序?因为常数项、缓存局部性和数据特征都不同。快排虽然理论最坏差,但实际数据下平均表现极好,而且 Python 的sorted()底层采用的是 Timsort——一种利用了数据中“已有序片段”的混合排序,在部分有序的数据上可以接近 O(n)。堆排序则完全不利用数据原有的有序性,不管数据长啥样都要老老实实建堆。

4.2 为什么说堆排序不稳定

稳定性指的是:如果两个元素相等,排序后它们的相对顺序能不能保持不变。不能保持就是不稳定。

堆排序在哪些环节破坏了稳定性?在建堆和下沉过程中,父节点会和孩子节点交换位置,而完全二叉树的存储方式又决定了“相等的数字不一定相邻”。举个例子,数组[5a, 5b, 4]中5a和5b都等于 5,建大根堆时,如果5b在某个子树里被换到上面,5a可能落到它后面,最终排序结果中5a和5b的相对顺序就变了。这个破坏不是靠“比较时用<而不是<=”能救回来的,因为它本质上是结构性的交换问题。

这个特性决定了堆排序不适合那种依赖稳定性的场景,比如按多个字段排序时要求先按第一字段排、再保持内部顺序。K 路归并、数据库排序这类场景,稳定性往往很重要,所以它们更偏爱归并排序这类稳定算法。

4.3 那堆排序到底强在哪

既然堆排序不稳定、常数大,为什么还要学它、还要用它?因为它的优势不在“通用排序”,而在优先级队列和部分排序。

堆是一种天然的优先级队列实现。往堆里插入元素 O(log n),取最大/最小元素 O(log n),这些操作在任务调度、图算法(比如 Dijkstra 求最短路)、事件驱动模拟里到处都是。

部分排序的典型例子就是 TopK:从一个很大的数组里找最大的 K 个元素,用堆可以把时间复杂度做到 O(n log K),而且不需要一次性把所有数据读进内存。这个场景后面会展开说。

5. 实战中的常见问题与排查记录

5.1 排序结果差一位,通常错在边界

我自己踩过、也看别人踩过最多的坑,就是sift_down里孩子下标越界。比如一个节点有左孩子但没有右孩子,代码里如果直接拿right去和end比,而后right > end,就会比较到一个已经被换到末尾的“已排序区”元素,结果堆结构被污染。

记住口诀:每次比较孩子前,先确认下标<= end。这里的end是当前堆的最后一个有效下标,不是整个数组的长度。为什么强调这个?因为在排序阶段,end每轮都在缩小,你写死成n-1的话,排序后半段就会把已经排好的元素重新拉进堆里,导致结果错乱。

排查技巧也很简单:打印出每轮下沉后的数组和当前的end,一眼就能看出问题。

5.2heapq默认是小根堆,想排“前 K 大”要取负

很多人在处理“最大堆”需求时,习惯性地去找heapq里的_max之类的函数。其实 Python 标准库没有直接提供最大堆,但有一个非常常见的写法:存入-x,取出时再取反。比如找“最大的 K 个”,用最小堆维护“当前最小的 K 个候选”,最后留在堆里的就是最大的 K 个。

import heapq def top_k_largest(nums, k): if k <= 0: return [] heap = [] for x in nums: if len(heap) < k: heapq.heappush(heap, x) elif x > heap[0]: heapq.heapreplace(heap, x) return heap

这里用heapreplace而不是先pop再push,因为它能做到原子替换,效率更高。它的语义是:弹出堆顶最小值,再把新元素入堆。由于堆顶是当前候选里最小的,如果新元素比它大,说明新元素比这个“最差候选”更有资格留在 TopK 里。

如果用取负法做“前 K 大”,写法是:

def top_k_largest_neg(nums, k): heap = [] for x in nums: if len(heap) < k: heapq.heappush(heap, -x) elif -x > heap[0]: heapq.heapreplace(heap, -x) return [-v for v in heap]

两种思路效果一样,第二种对新手更直观,第一种避免了反复取负的思维负担。工程上我更喜欢第一种,因为可读性好。

5.3 自定义对象排序时怎么用堆

堆排序比较的是元素大小,但 Python 内置堆只能比较对象的大小。如果你要对一个自定义类排序,要么给类实现__lt__方法,要么用元组把排序键放在第一位。

import heapq class Task: def __init__(self, priority, name): self.priority = priority self.name = name def __lt__(self, other): return self.priority < other.priority tasks = [Task(3, "b"), Task(1, "a"), Task(2, "c")] heapq.heapify(tasks) print(heapq.heappop(tasks).name) # a

如果不想改类,就存元组(priority, name),堆会自动按元组第一项排序,第一项相同再比第二项。这个技巧在处理(score, id)、(timestamp, event)这类结构时特别方便。

5.4 调试堆排序的“最小复现”思路

如果你在自己写堆排序的时候结果不对,别盯着完整数组硬看,先拿一个长度为 4 或 5 的小数组,一步步打印:

def debug_sift_down(nums, start, end): print("before:", nums, "start=", start, "end=", end) ...

重点检查:

  • 建堆结束后,数组第一个元素是不是最大值;
  • 每轮交换nums[0]和nums[end]之后,堆顶是不是剩余堆里的最大值;
  • 最后几个元素是不是从小到大排列。

只要这三个点都对了,代码基本没问题。我不止一次在排查结束后发现,写错的原因不是思路,而是某个下标加减一的问题。所以认真对待边界下标,比记住整个算法的套路更重要。

6. 面试与工程场景里,堆排序怎么用才对

6.1 面试官拿起这道题时想考察什么

我参与过几次技术面试的模拟打分,发现面试官让候选人写堆排序,重点看的不是能不能“背”出来,而是:

  • 能不能解释建堆为什么从最后一个非叶子节点开始;
  • 能不能说清sift_down的终止条件;
  • 知不知道堆排序不稳定、为什么不稳定;
  • 能不能快速改造成 TopK 问题;
  • 遇到“堆排序 vs 快排怎么选”这种开放问题时,有没有自己的判断。

所以如果你只是默写代码,最多拿基础分。想拿到高分,得把“为什么”也讲清楚。这篇文章前面这些原理部分,其实就是按照我理想的面试回答路径写的。

6.2 实际工程中,什么时候我会优先选堆

具体到我的开发经验,遇到下面几种情况,会优先考虑堆,而不是直接sorted():

第一,数据流场景。数据是一个个来的,你不知道后续会来多少,但想随时能取到当前最大/最小的若干个。用堆可以在线维护,不用存全量数据再排序。比如一个实时监控系统要看“最近到达的一万条日志里耗时最高的 10 条”,每来一条日志只要跟堆顶比一次就能决定去留。

第二,大规模数据的 TopK。假设数据有几十亿条,不可能全部装进内存做排序。用大小为 K 的堆,内存只需要 O(K),这是堆在这种场景下的核心优势。

第三,合并 K 个有序序列。多路归并排序里,取下一个最小元素这个操作如果用数组做,每次要扫 K 个序列,总复杂度是 O(KN);换成堆,每个新元素调整一次 O(log K),总复杂度降到 O(N log K),差别非常大。

6.3 一点私人的选择建议

在 Python 里,如果数据规模适中,我会直接用sorted()或.sort(),因为底层是 C 语言实现,常数极小,堆排序的“理论 O(n log n)”在真实速度上往往比不过高度优化的 Timsort。但如果你在面试、竞赛或写框架代码,需要手写排序或对复杂度有硬性要求时,堆排序依然是越过不过去的技能点。

我个人实际写代码时,最常用的其实是heapq的nlargest和nsmallest。它们内部就是基于堆实现的,而且参数k很小的时候效率极高。比如找一个列表里最大的 3 个元素,heapq.nlargest(3, data)比手动排序再切片要优雅得多。

6.4 堆排序代码之后还可以怎么扩展

学完堆排序,后续可以沿着几个方向继续深挖:基于堆实现优先队列;用优先队列解决合并 K 个有序链表的问题;在无向图中用堆优化 Dijkstra 算法;做流式数据的中位数维护(双堆法)。这些场景里堆都是核心的数据组织方式。

我个人在实践中有一个建议:你不需要把堆排序代码背得一字不差,但一定要能把建堆、下沉、排序三个阶段分别拆开讲清楚,能随手写出来,并且知道每一步在用什么数据结构、在维护什么不变量。做到这个程度,任何考堆的变体题——TopK、带权调度、找中位数——你都能很快迁移过去。堆排序只是一个起点,它背后的“堆”这个数据结构,才是真正值得长期持有的工具。

相关新闻

卓越嵌入式工程师培养计划:从C语言到RTOS实战全解析

卓越嵌入式工程师培养计划:从C语言到RTOS实战全解析

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

2026/10/20 1:23:58 阅读更多 →
基于NVIDIA Jetson的自主轮椅开发:从SLAM到安全控制的工程实践

基于NVIDIA Jetson的自主轮椅开发:从SLAM到安全控制的工程实践

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

2026/10/20 1:23:58 阅读更多 →
AI视觉联合ICT方案打造智能验布系统,效率提升3-5倍

AI视觉联合ICT方案打造智能验布系统,效率提升3-5倍

1. 纺织验布行业的痛点与需求拆解1.1 人工验布的瓶颈&#xff1a;不止是慢&#xff0c;更是稳不住纺织行业的验布环节&#xff0c;历来是产线上最依赖“人”的工序。我早期走访过不少纺织厂&#xff0c;验布车间里一排排老师傅坐在灯箱前&#xff0c;目光跟着布面走&#xff0c…

2026/10/20 1:22:58 阅读更多 →

最新新闻

企业IM接入AI的架构设计:从消息通信到业务调用与人机协同

企业IM接入AI的架构设计:从消息通信到业务调用与人机协同

企业IM接入AI后&#xff0c;技术变化涉及三条链路的衔接&#xff1a;人员之间的消息通信、人员与业务系统之间的数据和操作&#xff0c;以及AI辅助理解、工具调用与人工确认。我们可以将连接对象的演进归纳为&#xff1a;人↔人→人↔系统→人↔系统↔AI。 小天互连已经具备企业…

2026/10/20 2:13:24 阅读更多 →
PPT设计从基础到创意实战:31个视频+进阶技巧一网打尽

PPT设计从基础到创意实战:31个视频+进阶技巧一网打尽

PPT设计为什么值得系统学习 演示文稿是职场与学习中不可或缺的表达工具&#xff0c;但很多人制作PPT时往往停留在"能看就行"的水平——排版随意、配色混乱、缺乏设计感。一套优秀的PPT不仅能清晰传递信息&#xff0c;更能提升演讲者的专业形象与说服力。如果你想从&…

2026/10/20 2:13:24 阅读更多 →
让有限显存扛住更多并发:KV Cache 调度与加速引擎详解

让有限显存扛住更多并发:KV Cache 调度与加速引擎详解

最近一直在折腾多智能体框架&#xff0c;组里有人用那个叫 OpenClaw 的开源项目跑了一周&#xff0c;GPU 显存肉眼可见地被吃掉&#xff0c;直接把一台 80G 的卡干到 OOM。群里有人嘴快&#xff0c;说 OpenClaw 这下真成“吞金龙虾”了——又贵又能吃&#xff0c;还得给它剥壳。…

2026/10/20 2:13:24 阅读更多 →
学了3个月Agent还只会Demo?用这5项工程能力做一次自测

学了3个月Agent还只会Demo?用这5项工程能力做一次自测

学了3个月Agent还只会Demo&#xff1f;用这5项工程能力做一次自测 一个Agent Demo可以很精彩&#xff1a;输入目标&#xff0c;自动搜索&#xff0c;最后生成一份报告。但让另一个人使用时&#xff0c;换了问题就报错&#xff0c;重启进程任务消失&#xff0c;超时后不知道执行…

2026/10/20 2:13:24 阅读更多 →
2024上教资备考资料包:科一综合素养+科二教育知识,中学小学双版本覆盖

2024上教资备考资料包:科一综合素养+科二教育知识,中学小学双版本覆盖

资料概述 这是一套面向2024年上半年中小学教师资格证考试笔试的备考资料&#xff0c;涵盖科目一"综合素质"和科目二"教育知识与能力"两部分内容。资料以Polaris整理的个人学习笔记为基础&#xff0c;分为中小学通用版本&#xff0c;其中科二针对中学和小学…

2026/10/20 2:13:24 阅读更多 →
Day90-Java后端工程师的AI时代竞争力地图

Day90-Java后端工程师的AI时代竞争力地图

这套专栏走到今天&#xff1a;正篇 90 篇打底&#xff0c;续集《AI 工程化深水区》60 篇接着往生产级走&#xff0c;加起来 150 篇、22 个阶段。你现在的位置和要去的地方&#xff0c;都在这张图里&#xff1a;上半部分是正篇——前 8 个阶段&#xff08;Day 1-56&#xff09;把…

2026/10/20 2:12:24 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

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

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

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

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

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

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 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/19 2:54: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/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练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/18 12:16:16 阅读更多 →