1.首先了解堆是什么
堆是一种数据结构,一种叫做完全二叉树的数据结构。
2.堆的性质
在这里我们用到两种堆,其实也算是一种。
大顶堆:每个节点的值都大于或者等于它的左右子节点的值。
小顶堆:每个节点的值都小于或者等于它的左右子节点的值。
如果我们把这两种对分别映射到数组中,就是一下两种:
从这里我们可以得出以下性质(重点)
对于大顶堆:下标为i的节点的父节点下标:(i-1)/2【整数除法】
下标为i的节点的左孩子下标:i*2+1
下标为i的节点的右孩子下标:i*2+2
3.堆排序的基本思想
堆排序的基本思想是:
1.将待排序的序列构造成一个大顶堆,根据大顶堆的性质,当前堆的根节点(堆顶)就是序列中最大的元素;
2.将堆顶元素和最后一个元素交换,然后将剩下的节点重新构造成一个大顶堆;
3. 重复步骤2,如此反复,从第一次构建大顶堆开始,每一次构建,我们都能获得一个序列的最大值,然后把它放到大顶堆的尾部。最后,就得到一个有序的序列了。
假设有一个无需序列arr是:
(1)将无序序列构建成一个大顶堆
首先我们将现在的无序序列看成一个堆结构,一个没有规则的二叉树,将序列里的值按照从上往下,从左到右依次填充到二叉树中。
(2)从尾部向上观察,要维护大顶堆的性质
交换后的序列为:
(3)从尾部向上观察,继续维护大顶堆的性质
因为元素8没有子节点,所以继续比较下一个非叶子节点,元素值为5的节点,它的两个子节点值都比本身小,不需要调整;然后是元素值为4的节点,也就是根节点,因为9>4,所以需要调整位置
交换后的序列为:
(4) 从尾部向上观察,继续维护大顶堆的性质
此时,原来元素值为9的节点值变成4了,而且它本身有两个子节点,所以,这时需要再次调整该节点
交换后的序列为:
到此,大顶堆就构建完毕了。满足大顶堆的性质。
(5)利用对排序的思想,将堆顶的元素之与尾部的元素交换-多次重复
然后将剩余的元素重新构建大顶堆,其实就是调整根节点以及其调整后影响的子节点,因为其他节点之前已经满足大顶堆性质。
然后,继续交换,堆顶节点元素值为8与当前尾部节点元素值为1的进行交换
重新构建大顶堆
继续交换
重新构建大顶堆
继续交换
重新构建大顶堆
继续交换
构建大顶堆
继续交换
重新构建大顶堆
继续交换
交换后的序列为:
3.C语言实现堆排序
#include "../utils.h"
/**
* 维护堆的性质
* @param arr 存储堆的数组
* @param len 数组长度
* @param i 待维护节点的下标
*/
void heapify(int arr[], int len, int i)
{
int largest = i;
int lson = i * 2 + 1;
int rson = i * 2 + 2;
if (lson < len && arr[largest] < arr[lson])
largest = lson;
if (rson < len && arr[largest] < arr[rson])
largest = rson;
if (largest != i)
{
swap(&arr[largest], &arr[i]);
heapify(arr, len, largest);
}
}
// 堆排序入口
void heap_sort(int arr[], int len)
{
int i;
// 建堆
for (i = len / 2 - 1; i >= 0; i--)
heapify(arr, len, i);
// 排序
for (i = len - 1; i > 0; i--)
{
swap(&arr[i], &arr[0]);
heapify(arr, i, 0);
}
}
int main(int argc, char const *argv[])
{
test(&heap_sort);
return 0;
}