一.堆的性质
1.是一颗完全的二叉数
2.每个节点的值都大于或等于其子节点的值,为最大堆;反之则为最小堆。
下标为i的节点的父节点下标:(i-1)/2
下标为i的节点的左孩子下标:i*2+1
下标为i的节点的右孩子下标:i*2+2
上面的大顶堆用树的层序遍历吧数据放在数组中为:
9 | 6 | 5 | 4 | 5 | 4 | 4 | 1 |
小顶堆用树的层序遍历吧数据放在数组中为:
1 | 3 | 5 | 4 | 5 | 6 | 8 | 9 |
二.实现思想
利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录)变得简单。
1.将一个数组的元素存入二叉树中;
2.将待排序的序列构造成一个最大堆,此时序列的最大值为根节点;
3.依次将根节点与待排序序列的最后一个元素交换;
4.再维护从根节点到该元素的前一个节点为最大堆,如此往复,最终得到一个递增序列;
三.实现步骤
1.假设有一数组,buf[5] = {3,5,2,1,4};从上至下按顺序排列存入二叉树中,形成无序堆(如下图)
转换成无序堆之后,我们要让这个无序堆变成最大堆(或是最小堆),即每个堆里都实现父节点的值都大于任何一个子节点的值。
从最后一个堆开始,首由于左孩子1 < 右孩子4,所以交换左右孩子的值得到右图,又因为此时的左孩子小于父节点,所以不交换两个的值。(如果这个堆没有右孩子,只能用左孩子的值与父节点的值作比较,如果父节点大,不用交换,反之交换。)
在进行上面一层的堆的操作
此时由于上一层交换位置过后使下面一层不满足了父节点的值都大于任何一个子节点的值,所以要检测子节点是否为其他堆的父节点,如果是,递归进行同样的操作。
依次这样的操作可以得到一个最大堆:
2.堆排序(最大堆调整):
现在得到了一个最大堆,将对顶元素5交换到最底部1的位置,1升至对顶,5所在底部位置即为有序区,有序区不参与之后的任何对比。(跳过有序区)
在进行上面的操作,将1和4交换位置过后,在递归比较下一层是否满足父节点的值都大于任何一个子节点的值。,将会得到如下最大堆:
在将堆的顶点与最底部的1交换位置,放到有序区。递归操作,不断建立最大堆,并且扩大有序区,最终全部有序。再从上往下遍历得到一个由小到大的一个数组。
buf遍历结果:
1 | 2 | 3 | 4 | 5 |
四.代码实现
#include <stdio.h>
#include <stdlib.h>
void swap(int *a,int *b) //交换两个数的值
{
*a ^= *b;
*b ^= *a;
*a ^= *b;
}
void max_heapify(int arr[], int start, int end)
{
//建立父节点指标和子节点指标
int dad = start;
int lchild = dad * 2 + 1; //左孩子
int rchild = dad * 2 + 2; //右孩子
while (lchild <= end) //若左孩子存在才做比较
{
if (rchild <= end && arr[lchild] < arr[rchild]) //若存在右孩子,比较两个孩子大小,若左孩子小于右孩子,交换位置
swap(arr+lchild,arr+rchild);
if (arr[dad] > arr[lchild]) //如果父节点大于左孩子的值,退出!!!
return;
//否则交换父亲与左孩子的值,再继续子节点和孙节点比较
else
{
//交换父亲与左孩子的值,保证父亲的值比孩子大
swap(arr+dad,arr+lchild);
//交换过后在判断当前左孩子是否满足在它左右孩子中是否最大
dad = lchild;
lchild = dad * 2 + 1;
}
}
}
void heap_sort(int arr[], int len)
{
int i;
//初始化,i从最后一个父节点开始调整
for (i = len / 2 - 1; i >= 0; i--)
max_heapify(arr, i, len - 1);
//先将第一个元素和已排好元素前一位做交换,再从新调整,直到排序完毕
for (i = len - 1; i > 0; i--)
{
swap(arr,arr+i); //交换两个之间的值
max_heapify(arr, 0, i - 1);
}
}
int main() {
int arr[] = {24,36,62,21,24,15,53,23,97,23,78,45,56,87};
int len = (int) sizeof(arr) / sizeof(*arr); //数组长度
heap_sort(arr, len);
int i;
for (i = 0; i < len; i++) //遍历数组
printf("%d ", arr[i]);
printf("\n");
return 0;
}