堆排序是一种全新的排序方法,时间复杂度是O(lgn),与归并排序相同。但是本身的排序方式更像插入排序,那就是空间原址性(任何时候,只需要常数个额外的元素空间存储临时数据,也就是说只需要有数的几个变量来实现诸如正在处理的位置的指针、交换中间量等等就可以)。
1,堆的定义
要了解 堆 的定义,我们应该首先明白的是 树 的定义。树是一种数据结构,从一种简单的理解上来说,所有的节点都有且仅有一条路径能够到达的数据结构,它和名字也是非常贴切的。显然对于一棵树来说,从根部到达它的叶子有且仅有一条路径(一片叶子不能长在两个树杈上)。
树的每一个分叉的地方和最后结尾的地方被我们称作节点。节点可以向根部连到另一个节点,另一个节点成为这个节点的父节点。节点向叶的方向会连到另外的零个、一个或者多个节点,如果存在的话,这些节点成为该节点的子节点。
接下来我们要介绍树的一种特例,二叉树。对于这棵树上的所有节点,如果每个节点的子节点的个数是0、1或者2,那么我们就认为这棵树是二叉树。而如果除了最底层那一些没有子节点的节点(叶节点)之外的所有位置,都排满了元素,就称这一棵二叉树是“满树”。
堆,在堆排序中可以理解为一棵满的二叉树。
例如:数组[1 ,2, 3, 4, 5, 6, 7, 8, 9]可以排成:
虽然在普适性的定义中,并不是说这样一个数列一定要排列成这样的形式,但是在堆排序中,我们默认将数组按照这样的形式填入堆中,这样一个含有n个元素的数组就会变成一个高度为log(2,n+1)向上取整的二叉树。
最大堆和最小堆:最大堆指的是对于堆中的任意一个元素,它的子节点的值都小于它本身,我们可以递推得到最终最大的值就是整个堆的根节点,最顶上那个值,反之亦然。
值得注意的是,即使我们将一个堆调整为最大堆和最小堆,并不意味着它被重新写成数列或者数组的形式之后,整个列就会按照大小顺序排列,因为最大堆的性质只要求了顶部的大小关系,并没有要求同一行中,元素的大小关系(换句话说,一个节点的两个子节点,可能左边的大,可能右边的大,但是排回数组的时候,左边的会排到前面,它却不一定大),例如一个只有三个元素的堆:
3--1、2(3是父节点,1是左子节点,2是右子节点)
重新排成数组是[3, 1, 2],显然不是从大到小的关系。
但是显然我们可以利用排列成最大堆的方式来筛选出未排序的部分的最大元素。随后取出最大元素,然后重新建成最大堆。这样就可以通过反复操作将剩余数据中最大堆选出来,从而完成排序的过程,python的源码如下:
# 算法导论第六章 堆排序
import math
def get_father(i):
"""
返回i位置的父节点的位置
"""
return (i + 1) // 2 - 1
def get_left_son(i):
"""
返回i位置的左下角的子节点的位置
"""
return 2 * i + 1
def get_right_son(i):
"""
返回i位置的右下角的子节点的位置
"""
return 2 * i + 2
def build_max_heap(arr, i):
"""
将数组的i位置之前的所有元素建成最大堆
:param arr: 待排序的数组
:param i: 位置i的标记
:return: 建成最大堆之后的数组
"""
l = get_left_son(i)
r = get_right_son(i)
if l < length and arr[l] > arr[i]: # 如果左子节点没有溢出界限,而且左子节点对应的那个值更大,就给largest,否则给父亲
largest = l
else:
largest = i
if r < length and arr[r] > arr[largest]: # 对右子节点类似操作。
largest = r
if largest != i:
heap_print(arr, largest, i) # 如果这三个元素顺序不对,就交换一下。
j = arr[i]
arr[i] = arr[largest]
arr[largest] = j
print('\n', arr)
build_max_heap(arr, largest)
def heap_print(arr, largest, i):
"""
打印数组和堆对应的二叉树的函数,打印二叉树部分只表示层级结构(满树不会引起歧义)
:param arr: 数组
:param largest: 本次循环筛选的最大值
:param i: 操作的i
"""
print('-' * 40)
print('exchange:', largest, i)
for j in range(0, len(arr)):
print(' ' * (6 - len(str(arr[j]))) + str(arr[j]), end=' ')
if math.log(j + 2, 2) % 1 == 0:
print()
arr = [4, 1, 3, 2, 16, 9, 10, 14, 8, 7]
leng = len(arr)
length = len(arr)
for i in range(leng // 2, -1, -1):
build_max_heap(arr, i)
print('初始化完毕')
for length in range(leng-1, 0, -1):
k = arr[length]
arr[length] = arr[0]
arr[0] = k
print(arr, length)
build_max_heap(arr, 0)
后附测试样例的输出:
----------------------------------------
exchange: 7 3
4
1 3
2 16 9 10
14 8 7
[4, 1, 3, 14, 16, 9, 10, 2, 8, 7]
----------------------------------------
exchange: 6 2
4
1 3
14 16 9 10
2 8 7
[4, 1, 10, 14, 16, 9, 3, 2, 8, 7]
----------------------------------------
exchange: 4 1
4
1 10
14 16 9 3
2 8 7
[4, 16, 10, 14, 1, 9, 3, 2, 8, 7]
----------------------------------------
exchange: 9 4
4
16 10
14 1 9 3
2 8 7
[4, 16, 10, 14, 7, 9, 3, 2, 8, 1]
----------------------------------------
exchange: 1 0
4
16 10
14 7 9 3
2 8 1
[16, 4, 10, 14, 7, 9, 3, 2, 8, 1]
----------------------------------------
exchange: 3 1
16
4 10
14 7 9 3
2 8 1
[16, 14, 10, 4, 7, 9, 3, 2, 8, 1]
----------------------------------------
exchange: 8 3
16
14 10
4 7 9 3
2 8 1
[16, 14, 10, 8, 7, 9, 3, 2, 4, 1]
初始化完毕
[1, 14, 10, 8, 7, 9, 3, 2, 4, 16] 9
----------------------------------------
exchange: 1 0
1
14 10
8 7 9 3
2 4 16
[14, 1, 10, 8, 7, 9, 3, 2, 4, 16]
----------------------------------------
exchange: 3 1
14
1 10
8 7 9 3
2 4 16
[14, 8, 10, 1, 7, 9, 3, 2, 4, 16]
----------------------------------------
exchange: 8 3
14
8 10
1 7 9 3
2 4 16
[14, 8, 10, 4, 7, 9, 3, 2, 1, 16]
[1, 8, 10, 4, 7, 9, 3, 2, 14, 16] 8
----------------------------------------
exchange: 2 0
1
8 10
4 7 9 3
2 14 16
[10, 8, 1, 4, 7, 9, 3, 2, 14, 16]
----------------------------------------
exchange: 5 2
10
8 1
4 7 9 3
2 14 16
[10, 8, 9, 4, 7, 1, 3, 2, 14, 16]
[2, 8, 9, 4, 7, 1, 3, 10, 14, 16] 7
----------------------------------------
exchange: 2 0
2
8 9
4 7 1 3
10 14 16
[9, 8, 2, 4, 7, 1, 3, 10, 14, 16]
----------------------------------------
exchange: 6 2
9
8 2
4 7 1 3
10 14 16
[9, 8, 3, 4, 7, 1, 2, 10, 14, 16]
[2, 8, 3, 4, 7, 1, 9, 10, 14, 16] 6
----------------------------------------
exchange: 1 0
2
8 3
4 7 1 9
10 14 16
[8, 2, 3, 4, 7, 1, 9, 10, 14, 16]
----------------------------------------
exchange: 4 1
8
2 3
4 7 1 9
10 14 16
[8, 7, 3, 4, 2, 1, 9, 10, 14, 16]
[1, 7, 3, 4, 2, 8, 9, 10, 14, 16] 5
----------------------------------------
exchange: 1 0
1
7 3
4 2 8 9
10 14 16
[7, 1, 3, 4, 2, 8, 9, 10, 14, 16]
----------------------------------------
exchange: 3 1
7
1 3
4 2 8 9
10 14 16
[7, 4, 3, 1, 2, 8, 9, 10, 14, 16]
[2, 4, 3, 1, 7, 8, 9, 10, 14, 16] 4
----------------------------------------
exchange: 1 0
2
4 3
1 7 8 9
10 14 16
[4, 2, 3, 1, 7, 8, 9, 10, 14, 16]
[1, 2, 3, 4, 7, 8, 9, 10, 14, 16] 3
----------------------------------------
exchange: 2 0
1
2 3
4 7 8 9
10 14 16
[3, 2, 1, 4, 7, 8, 9, 10, 14, 16]
[1, 2, 3, 4, 7, 8, 9, 10, 14, 16] 2
----------------------------------------
exchange: 1 0
1
2 3
4 7 8 9
10 14 16
[2, 1, 3, 4, 7, 8, 9, 10, 14, 16]
[1, 2, 3, 4, 7, 8, 9, 10, 14, 16] 1
进程已结束,退出代码为 0
堆排序的理解核心:假想一棵虚拟的满二叉树,没有使用新的内存空间,仅仅用数字在数组中的位置来推断它在虚拟的二叉树中的位置。