0x01.关于堆排序
堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
0x02.堆的结构
堆是具有以下性质的一棵树:
- 是一棵完全二叉树
- 每个结点的值都大于等于其左右孩子结点的值,称为大顶堆
- 每个结点的值都小于等于其左右孩子结点的值,称为小顶堆
0x03.堆排序算法
调整堆结构算法:
//s是要需要调整堆结构的顺序表的下标元素
//调整完后使之成为大顶堆,m是堆的大小
void HeapAdjust(SqList* L, int s, int m)
{
int temp, j;
temp = L->r[s];//记录需要调整的这个元素的值
//j的初始化的含义是等于s的左子树的下标值
//j*=2的含义是每次循环过后,j都等于左子树的下标值
for (j = 2 * s; j <= m; j *= 2)
{
//j+1的下标值其实就是下标s值右子树
if (j < m && L->r[j] < L->r[j + 1])//如果左子树的值小于右子树的值,那么j的下标含义变为右子树的下标值
{
j++;
}
//此时的temp其实就是传进来需要调整的下标值,此时的j是传进来的结点的左右子树中值较大的值
if (temp >= L->r[j])//如果传进来的这个值已经比左右子树都要大了,那么直接退出循环
{
break;
}
L->r[s] = L->r[j];//如果之前那个结点的值不比左右子树都大,那么就把左右子树中的较大值给这个结点
s = j;//此时需要改变的结点是左右子树中较大的结点,再次循环
}
L->r[s] = temp;//找到需要插入的位置,插入元素
}
堆排序算法:
void HeapSort(SqList* L)
{
int i;
for (i = L->length / 2; i > 0; i--)//建造一个大顶堆,因为一半就已经包含所有有孩子的结点,所以一半已经能创建大顶堆序列了
{
HeapAdjust(L, i, L->length);
}
for (i = L->length; i > 1;i--)
{
swap(L, 1, i);//每次将堆顶元素往后面放
HeapAdjust(L, 1, i - 1);//放完一个堆顶元素后,必须重新调整以下堆的结构
}
}
0x04.原理深究
堆排序其实没有真正的堆:
从上面的代码可以看出,整个代码中都没有涉及树的结构,而是一直在用那个需要排序的是顺序表。其实,堆排序并没有真正的用到堆的结构,而是将待排序的顺序表中的元素顺序变为堆的层序遍历的顺序,因为最终用到堆的地方,就是每次从堆顶拿出哟个最大的元素放到表尾。
初始的顺序表可以看成一个完成二叉树:
初始的顺序表其实可以看出一个按层序遍历的二叉树序列,所以,我们要想将这个顺序表变为一个大顶堆,只需要对这个顺序表的一半的元素进行堆调整进行了,因为一半已经是完全二叉树所有的中间结点了。
堆调整原理:
堆调整就是对传入的结点不断的与左右结点比较,取得最大值,直到这个结点找到合适的位置插入,在插入这个结点前,已经对这个结点本来位置到插入的位置,都进行了堆调整。具体如何实现,可以试着模仿计算机进行运行该程序,找到这个调整的思想。
堆排序原理:
堆排序的前半部分,把顺序表构成了大顶堆结构,后半部分只需要每次把一个堆顶值放到末尾的地方,就完成了排序的过程,相当于是倒着降序。
时间复杂度:
堆排序的最忧,最坏,平均时间复杂度都是。