堆排序
完全二叉树
新节点的生成按照从左到右,从上到下的规则生成的二叉树叫做完全二叉树。如下图所示
这样的树有很好的特征可以让我们利用,我们从上到下,从左到右依次给节点编号后,有如下规律:
- i 号节点的左孩子 = 2 * i + 1 ,i 号节点的右孩子 = 2 * i + 2
- i 号节点的父亲 = (i-1)/2
知道了这些特点后,我们说一下堆
堆
堆又可以分为大根堆和小根堆,这里我们以大跟堆为例
如上图所示,每一个父节点都比自己的两个孩子节点要大,这样形成的完全二叉树被称为一个大跟堆。
如何生成一个堆
假设我们的所有节点上的数都是随机生成的。我们要维护它成为一个大跟堆。如下图所示。
- 维护第一个非叶子节点,使其成为一个大跟堆,如图既交换1和4。
- 再依次维护比第一个非叶子节点更小的节点。既依次维护9 3 2
- 9这个节点不需要维护,所以保持原状即可。
- 3这个节点需要将3和10进行互换。互换过后,有可能会破坏现在3这个位置上大跟堆的性质,所以需要对3这个位置再做一次维护,这个例子中3这个位置上没有子节点,所以不需要维护。
- 接着维护2这个节点,需要做的操作是将2和10互换位置,这样保证了当前10这个位置大跟堆的性质,但是却破坏掉了当前2这个位置大根堆的性质,所以需要对当前2这个节点重新做一次维护。
- 需要做的操作是将2和4互换位置,这样重新维护了当前4这个位置的性质,同时,也可能会破坏当前2这个位置的大根堆的性质,但庆幸的是这时没有破坏掉,所以这时就将所有的节点都维护好了,这样我们就得到了一个大根堆。
代码实现的话也很容易,我们使用一个数组来当大根堆,这样就很容易的表示出每个节点的父亲节点和儿子节点。
接着从第一个非叶子节点(最后一个节点的父节点)开始做维护即可。
如果遇到了交换的情况,就再维护与根节点交换的节点即可。
堆排序:
如果是大根堆的话,我们只需要每次将数组中第一个元素与最后一个元素做一个交换后,砍掉最后一个元素,再对整棵树的根节点做一次维护即可。每次将砍掉的最后一个元素取出,就得到了依次递减的排序,同理,如果是小根堆得到的就是依次递增的排序
C代码:
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
//交换arr数组中下标为i和下标为j的值
void swap(int arr[], int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
//对tree中的i号节点做heapify,tree中共有n个元素
void heapify(int tree[], int n,int i) {
if (i > n) {
return;
}
int c1 = 2 * i + 1;
int c2 = 2 * i + 2;
int max = i;
if (c1<n && tree[c1]>tree[max]) {
max = c1;
}
if (c2<n && tree[c2]>tree[max]) {
max = c2;
}
if (i != max) {
swap(tree, max, i);
heapify(tree, n, max);
}
}
int main() {
srand((unsigned)time(NULL));
int a = rand();
int n = 1000;
int tree[1000];
for (int i = 0; i < n; i++) {
tree[i] = rand()%1000+1;
}
for (int i = (n - 1) / 2; i >= 0; i--) {
heapify(tree, n, i);
}
int result[1000];
int d = n;
for (int i = 0; i < n; i++) {
result[i] = tree[0];
swap(tree, 0, d-1);
d--;
heapify(tree, d, 0);
}
for (int i = 0; i < n; i++) {
printf("%d\n", result[i]);
}
}