图解堆排序及其Python实现
什么是堆?
堆(heap,也称优先队列)是一种数据结构,通常可以看做是一棵完全二叉树的广度优先遍历数组对象。
分类:
-
根节点的值总是不小于子节点值的堆称为最大堆、大顶堆或大根堆。
-
根节点的值总是不大于子节点值的堆称为最小堆、小顶堆或小根堆。
其满足以下性质:- 堆总是一棵完全二叉树;
- 堆中的某个节点的值总是不大于或不小于其父节点的值;
- 下标为 i 的节点的父节点下标为 (i-1)// 2 [整数除法]
- 下标为 i 的节点的左孩子下标为 i * 2 + 1
- 下标为 i 的节点的右孩子下标为 i * 2 + 2
如何构建一个堆?
以最小堆为例。
对于输入的数组,首先将其构建为无序的完全二叉树,然后从末尾节点开始调整(从下往上操作),根据最小堆的性质,比较子节点和其父节点的值大小并进行移动,即如果子节点中有值小于父节点的就选择子节点中值最小的节点与父节点交换位置。注意,被调整的节点如果有子节点,则需要进行递归调整。
-
将输入数组构建为完全二叉树:
-
从末尾开始调整二叉树,对有子节点的节点进行调整时需要进行递归:
注意,对于含有子节点的节点调整时要进行递归操作。
构造完成。
插入:当有新的元素插入数组时,将新元素放在数组结尾,然后重新调整排列二叉树。
删除:删除时每次都删除0号元素,即堆顶元素。为了便于重建,实际的操作是:每次将堆顶元素替换为数组中最后一个元素,然后从堆顶开始由上往下进行调整。
由于一个完全二叉树的高度不会超过 l o g 2 n log{2^n} log2n ,而堆化操作其实就是沿着节点路径进行对比、交换。因此它的时间复杂度与树的高度成正比,所以往堆里面插入一个元素、删除堆顶元素的时间复杂度是 l o g n log{n} logn 。
什么是堆排序?
堆排序(heap sort)是利用堆这种数据结构设计的一种原地排序算法,时间复杂度为 O ( n l o g n ) O(nlog{n}) O(nlogn)。它可以利用数组的特点快速定位指定索引的元素。
由堆的分类及性质可知:对于大顶堆,根节点是数组中的最大值;对于小顶堆,根节点是数组中的最小值。
根据这个特点,我们每次从排好序的堆中取出堆顶元素并将最后一个元素放到堆顶,然后重排堆,直到元素全部取出,就可以得到这个数组从大到小(大顶堆)或从小到大(小顶堆)的排序。
代码实现:
堆排序可以实现原地操作,不过下面的代码中创建了新的对象来存储排序后的数组。
-
手动实现:实现大顶堆即其排序
import copy class Heap(object): """实现大顶堆及堆排序""" def __init__(self, arr: list): """arr: 用户输入的待排序数组""" self.arr = arr self.len = len(arr) self.sorted_arr = [] self.create_heap() def heapify(self, parent_index): """维护堆的性质""" # 假设当前父节点是子树中最大的值下标 largest = parent_index # 左右孩子节点下标,有可能不存在 left_child_index = parent_index * 2 + 1 right_child_index = parent_index * 2 + 2 # 判断左右子节点是否存在,并找出其中最大的值下标 if left_child_index < self.len and self.arr[largest] < self.arr[left_child_index]: largest = left_child_index if right_child_index < self.len and self.arr[largest] < self.arr[right_child_index]: largest = right_child_index # 需要进行位置调整,并进行递归调整 if not (largest == parent_index): self.arr[parent_index], self.arr[largest] = self.arr[largest], self.arr[parent_index] self.heapify(largest) def create_heap(self): """初始化堆""" last_parent_index = (self.len - 1) // 2 # 最后一个包含子节点的父节点下标 for i in range(last_parent_index, -1 , -1): self.heapify(i) def pop(self): peak = self.arr[0] # 交换堆顶与最后一个元素,然后重新建堆 self.arr[0] = self.arr[-1] self.arr.pop(-1) self.len -= 1 self.heapify(0) # 从上到下维护堆 return peak def heap_sort(self): """堆排序,输出排序后的数组""" self.arr2 = copy.deepcopy(self.arr) for i in range(len(self.arr)): self.sorted_arr.insert(0, self.pop()) self.arr = self.arr2 h = Heap([18, 34, 26, 25, 30, 8, 28, 13]) print(h.arr) h.heap_sort() print(h.sorted_arr)
执行结果:
[34, 30, 28, 25, 18, 8, 26, 13] [8, 13, 18, 25, 26, 28, 30, 34]
-
使用heapq库调用:
heapq默认的实现是小顶堆,使用其提供的heapq.nsmallest(*n*, *iterable*, *key=None*)
方法即可返回iterable中前n个最小的元素。import heapq def heap_sort(arr): n = len(arr) return heapq.nsmallest(n, arr) arr = [18, 34, 26, 25, 30, 8, 28, 13] print(arr) heapq.heapify(arr) print(arr) print(heap_sort(arr))
执行结果:
[18, 34, 26, 25, 30, 8, 28, 13] [8, 13, 18, 25, 30, 26, 28, 34] [8, 13, 18, 25, 26, 28, 30, 34]