很久以前就接触到了堆排, 但是一直没能学会, 究其原因就和今天一样, 很多作者或者伪作者都没有把其中的原理给讲清楚, 或者说代码设计的思路没有讲清楚, 这也就造成了一部分的教程, 根本看不懂在写些什么, 因为没有讲原理, 而一部分教程只讲了原理, 但是从原理到代码设计部分的思路又没讲清楚, 结果就导致了原理和代码二者分离,最后还是看不懂在写的什么。
首先贴代码, 这是从菜鸟教程上摘录的代码:
def buildMaxHeap(arr):
import math
for i in range(math.floor(len(arr)/2),-1,-1):
heapify(arr,i)
def heapify(arr, i):
left = 2*i+1
right = 2*i+2
largest = i
if left < arrLen and arr[left] > arr[largest]:
largest = left
if right < arrLen and arr[right] > arr[largest]:
largest = right
if largest != i:
swap(arr, i, largest)
heapify(arr, largest)
def swap(arr, i, j):
arr[i], arr[j] = arr[j], arr[i]
def heapSort(arr):
global arrLen
arrLen = len(arr)
buildMaxHeap(arr)
for i in range(len(arr)-1,0,-1):
swap(arr,0,i)
arrLen -=1
heapify(arr, 0)
return arr
if __name__ == '__main__':
li = gen_random_int_li(30)
print(hepSort(li))
单单这么看的话, 这段代码真的很晦涩, 一个对数组的操作, 感觉没办法直接和堆这个概念结合起来。
其实这里说的堆,就是一颗二叉树, 而设计者把一颗二叉树从顶端往各级子节点按层级一路排在数组中了, 也就是0是根节点的话, 那下面的1、2就是0的子节点, 而3、4是1的子节点, 5、6是2的子节点, 从这里也能发现父子节点的规律, 下标为i的节点, 它的子节点分别是2*i+1和2*i + 2, 比如7、8就是3的子节点。
如果一个满节点二叉树有n层, 那么一共的节点数根据等比公式可以知道是2**n - 1, 而非叶子节点的数量为2**(n-1) - 1, 可以看到非叶子节点数量乘以2, 依然少于叶子节点, 所以可以判定, 将二叉树的各节点按照上述方式排列到数组中, 以数组长度前一半为各级父节点的话, 子节点将会轮空, 也就不会导致有多余的元素缺失父节点, 也就能充分证明了这样去存储一个二叉树的合理性。
那么按照上述的理论, 首先从length/2的地方开始从后往前去构建一个大顶端, 所谓的大顶端就是看下父节点和它两个子节点中哪个数最大, 最大的作为父节点, 子节点因为是倒序去排树, 可以忽略大小比较, 这样排到0的时候, 第0个一定是整个树(数组)里面的最大值, 这样一个大顶端的树就建立好了。
其实这里有个疑问, 就是数组任意长度二分为什么会刚好建立一个以下标0为根节点建立的二叉树呢? 仔细看了一遍发现, 它是从二分长度到0, 依次去构建三元关系, 那么就是到了最后一步, 必定构建的是0、1、2三元的关系, 然后把最大的数存在0位置处就行了...其他各节点对应的关系也是一样的处理, 也就构建了一个个稳定的父节点为最大节点的树结构,虽然可能底层叶子接地那不是满的, 但整体结构不变。
然后开始循环重复一下的过程:
把树的根节点, 也就是第0个元素和树的最后一个子节点替换, 然后把记录数组长度的变量减去1, 在重新从根节点开始进行构建大顶端的过程(从根节点开始构建不用重新构建整颗树, 只需要沿着子节点中最大值的路径搜索下去就行了), 知道最后数组长度足够小, 即为排序完成。
这是动态图:
上面的代码和动态图来自菜鸟教程, 如有侵权请联系删除