堆
排
序
\mathbf{堆排序}
堆排序
堆排序:只需要一个记录大小的辅助空间,每个待排序的记录仅占有一个存储空间。
堆的定义:n个元素的序列
{
k
1
,
k
2
,
.
.
.
,
k
n
}
\{k_1,k_2,...,k_n\}
{k1,k2,...,kn},当且仅当满足以下关系是,称为堆。
{
k
i
⩽
k
2
i
k
i
⩽
k
2
i
+
1
\begin{cases}k_i \leqslant k_{2i}\\ k_i \leqslant k_{2i+1}\end{cases}
{ki⩽k2iki⩽k2i+1或
{
k
i
⩾
k
2
i
k
i
⩾
k
2
i
+
1
\begin{cases}k_i \geqslant k_{2i}\\ k_i \geqslant k_{2i+1}\end{cases}
{ki⩾k2iki⩾k2i+1,
i
=
1
,
2
,
.
.
.
,
⌊
n
2
⌋
i=1,2,...,\left\lfloor\dfrac{n}{2}\right\rfloor
i=1,2,...,⌊2n⌋
若将和此序列对应的二维数组(即以一维数组作此序列的存储结构)看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有非终端节点的值均不大于(或不小于)其左右孩子节点的值。由此,若序列
{
k
1
,
k
2
,
.
.
.
,
k
n
}
\{k_1,k_2,...,k_n\}
{k1,k2,...,kn}是堆,则堆顶元素(或完全二叉树的根)必为序列中n个元素的最小值(或最大值)。
堆排序的主要思想:在输出堆顶的最小值(或最大值)之后,使得剩余的n-1个元素的序列又重建成一个堆,则得到n个元素中的次小值(或次大值)。如此反复执行,便能得到一个有序序列,此过程即为堆排序。
实现堆排序需要解决两个问题:
1)如何由一个无序序列建成一个堆?
2)如何在输出堆顶元素之后,调整剩余元素成为一个新的堆?
例题:假设给定的数组为[2,1,7,9,5,8],对其进行堆排序。(小顶堆)
基本流程:
1)首先对其进行堆调整。(无序序列建堆)
2)堆筛选
代码实现:
def HeapSort(H):
# 从非叶节点开始调整
i = (len(H)-1)//2
while i > 0:
# 堆筛选函数
HeapAdjust(H,i,len(H)-1)
i -= 1
# 用于存储堆排序的结果
result = []
# 输出H[1],并和尾结点交换,并删除。
j = len(H)-1
while j > 1:
# 添加H[1]到result中。
result.append(H[1])
# 交换位置
H[1],H[j] = H[j],H[1]
# 删除尾结点。
H.pop()
# 堆筛选
HeapAdjust(H,1,j-1)
j -= 1
# 将H[1]添加到result中
result.append(H[1])
return result
def HeapAdjust(H,s,m):
# 记录当前节点的值。
rc = H[s]
# 其左孩子节点
j = 2 * s
while j <= m:
# 如果j<m,则判断其左右孩子节点的值的大小,如果左孩子大于右孩子,则最小值为
# 右孩子
if j < m and H[j] > H[j+1]:
j += 1
# 判断当前节点和左右孩子的最小值的大小
# 如果根节点小于其左右孩子,则break。
if rc < H[j]:
break
# 否则,将其左右孩子的最小值赋值给当前节点,继续调整被破坏的堆结构。
H[s] = H[j]
s = j
j *= 2
# 最后将当前节点的值,赋值给调整后的位置
H[s] = rc
算法分析:
时间复杂度:由于HeapSort函数的时间复杂度为O(n),HeapAdjust函数的时间复杂度为O(logn),所以整体的时间复杂度为O(nlogn)。
空间复杂度:由于我们采用了一个result来存储最终的结果,所以其空间复杂度为O(n),但是我们也可以直接输出,不保存结果的话,其空间复杂度为O(1)。(大家有改进的方法的话,可以留言讨论。共勉!)