参考
- 2017版数据结构高分笔记-天勤
- 大话数据结构
介绍
堆,可看成一颗完全二叉树,其满足:任何一个非叶结点的值都不大于(或不小于)其左右节点的值。
大顶堆:父亲大孩子小
小顶堆:父亲小孩子大
堆排序思想:堆(完全二叉树)的根结点的值是最大(或最小)。因此循环地将序列调整为堆,找到序列的最大值(或最小值),将其值交换到序列的最后(或最前),使得有序序列元素增1,无序序列元素减1。
最关键操作:将序列调整
执行流程
得到原始序列(数组)
- 建堆
- 插入节点
保持堆性质(完全二叉树,父大子小)。插入节点放置最底层最右边。将其依次向上调整。 - 删除节点
填充空位:利用最底层最右边的叶子的值填充,并下调到合适位置。 - 排序
执行流程:(大顶堆)
- 从无序队列所确定的完全二叉树的第一个非叶子结点开始,从右至左,从下至上,对每个结点进行调整,得到大顶堆。
- 将当前无序序列的第一个元素(树中的根结点)a ,与无序序列的最后一个元素 b 交换,a 进入有序序列,无序序列元素减少1,有序序列元素增加1。仅 b 不满足堆定义,对其调整。
- 重复2过程,直到无序序列元素仅剩1个时排序结束。
算法代码:
#include <stdio.h>
#define MAXSIZE 10000 /* 用于要排序数组个数最大值,可根据需要修改 */
typedef struct
{
int r[MAXSIZE+1]; /* 用于存储要排序数组,r[0]用作哨兵或临时变量 */
int length; /* 用于记录顺序表的长度 */
}SqList;
/* 交换L中数组r的下标为i和j的值 */
void swap(SqList *L, int i, int j)
{
int temp = L->r[i];
L->r[i] = L->r[j];
L->r[j] = temp;
}
/** 堆排序
*
* 时间复杂度:O(nlogn)
* 空间复杂度:O(1)
* 作用:堆是完全二叉树,具备以下性质:
* 每个结点的值都大于或等于其左右孩子结点的值(大顶堆);
* 每个结点的值都小于或等于其左右孩子结点的值(小顶堆)
* 应用:不断交换堆顶元素到末尾,并重新调整堆,达到堆排序的目的。
* 问题划分:无序队列构建堆;调整剩余元素成为新堆。
*/
/* 已知L->r[s..m]中记录的关键字除L->r[s]之外均满足堆的定义, */
/* 本函数调整L->r[s]的关键字,使L->r[s..m]成为一个大顶堆 */
void HeapAdjust(SqList *L, int s, int m)
{
int temp, j;
temp = L->r[s];
for(j = 2*s; j <= m; j*=2) /* 沿关键字较大的孩子结点向下筛选 */
{
if(j < m && L->r[j] < L->r[j+1]) /* r[j]是r[s]的左孩子结点 */
++j; /* j为关键字中较大的记录的下标 */
if(temp >= L->r[j])
break; /* rc应插入在位置s上 */
L->r[s] = L->r[j]; /* r[j] 调整到双亲结点上 */
s = j; /* 修改s 的值,以便继续向下调整 */
}
L->r[s] = temp; /* 被调整的节点值导入最终位置 */
}
/* 对顺序表L进行堆排序 */
void HeapSort(SqList *L)
{
int i;
for(i = L->length/2; i > 0; i--) /* 把L中的r构建成一个大根堆 */
HeapAdjust(L, i, L->length);
for(i = L->length; i > 1; i--)
{
swap(L, 1, i); /* 将堆顶记录和当前未经排序子序列的最后一个记录交换 */
HeapAdjust(L, 1, i-1); /* 将L->r[1..i-1]重新调整为大根堆 */
}
}
/* **************************************** */
#define N 9
int main()
{
int i;
/* int d[N] = {9,1,5,8,3,7,4,6,2}; */
int d[N] = {50,10,90,30,70,40,80,60,20};
/* int d[N] = {9,8,7,6,5,4,3,2,1}; */
SqList l0, l6;
for(i = 0; i < N; i++)
l0.r[i+1] = d[i];
l0.length = N;
l6 = l0;
printf("排序前:\n");
print(l0);
printf("堆排序:\n");
HeapSort(&l6);
print(l6);
}