数组是完全二叉树的很好的存储方式。比如说一个完全二叉树如下图所示。
他的物理结构便是这样。
满足这样一个关系:双亲结点的位置 X 2 +1 = 左孩子结点的位置。(孩子节点的位置 - 1)/ 2 = 双亲结点的位置。
然后说关于堆排序的一个重要的算法:
向下调整算法: 我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整
有了向下调整算法,我们就可以把一棵完全二叉树调整成一个大堆或者是一个小堆。(大堆就是双亲节点大于或等于孩子结点的树,小堆就是双亲节点小于或等于孩子节点的树)
下边先实现算法
void AdjustDown(int *a, int n, int root) { // a 是给定的数组,n 是数组大小, root 是根(也就是开始向下调整的位置)
int parents = root;
int child = 2 * parents + 1;
while (child < n) { // 选出数值小的那个孩子
if (child + 1< n && a[child + 1] < a[child]) {
++child;
}
if (a[parents] > a[child]) { // 如果双亲结点的数值大于孩子结点的数值就交换
int temp;
temp = a[parents];
a[parents] = a[child];
a[child] = temp;
parents = child;
child = 2 * parents + 1;
}
else
{
break;
}
}
}
有了向下调整算法,离堆排序还差建堆。向下调整算法有一个前提:左右子树必须是堆。那要建一个堆,就要从孩子为叶节点的双亲结点用向下调整法,一直到双亲为根节点结束。
具体的代码实现如下
for (int root = (n - 1 - 1)/2; root >= 0; --root) { // n 为数组的大小,n-1 为最后一个数的位置,再减一除以 2 便是根节点的位置。
AdjustDown(a, n, root);
}
最后便是堆排序了。先按照降序来说,降序首先要建立一个小堆,小堆会把最小的数选择出来作为根节点,然后将根节点的数与最后一个子叶的数进行交换,于是在数组里边,相当于把最小的那个数放到了数组的最后边,接着将数组长度减一不管最后那个数(也就是最小的数),对剩下的数再进行向下调整,求出次小的数放到最小的那个数前边,以此类推……
具体堆排序的代码如下:
void AdjustDown(int *a, int n, int root) {
int parents = root;
int child = 2 * parents + 1;
while (child < n) {
if (child + 1< n && a[child + 1] < a[child]) {
++child;
}
if (a[parents] > a[child]) {
int temp;
temp = a[parents];
a[parents] = a[child];
a[child] = temp;
parents = child;
child = 2 * parents + 1;
}
else
{
break;
}
}
}
void Heap(int *a,int n){
// 建堆
for (int root = (n - 1 - 1)/2; root >= 0; --root) {
AdjustDown(a, n, root);
}
int end = n - 1;
while (end>0) { // 最小的与最后一个数字进行交换
int tmp;
tmp = a[end];
a[end] = a[0];
a[0] = tmp;
AdjustDown(a, end, 0);
--end;
}
for (int i = 0; i < n; ++i) {
printf("%d ",a[i]);
}
printf("\n");
}
int main() {
int a[10] = { 15,18,19,28,27,37,25,49,34,65 };
int n = 10; // 数组长度
Heap(a,n);
return 0;
}
如上只说了降序,如果升序则将向下调整里边的取孩子数值较小的改为取孩子数值较大的,将双亲大于孩子的判断条件改为双亲小于孩子即可,如下:
最后一个问题: 关于降序用小堆,升序用大堆
比如说降序用小堆,建完堆只需要将根节点与最后一个叶子结点的数值进行交换,然后继续向下调整即可。但是如果用大堆,每次出来的根节点都是最大的值,如果说直接放在数组头部,那就要用根节点的左孩子作为下一个根节点进行建堆,这样做也可以,但是建堆的时间复杂度比向下调整的时间复杂度要大的多,可以但没必要。升序用大堆也是同样的道理。