之前所讲的算法,很多都是
1. 基本原理
1.1 完全二叉树
若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树,如下图就是一个二叉树
他并不一定保证所有的父节点都同时拥有左孩子和右孩子,如上图的这种情况也是满足定义的,但是如下图的这种情况却是不满足定义的
1.2 堆与二叉树
堆是具有以下性质的完全二叉树,每个结点的值都大于或等于其左右孩子节点的值,称之为大定堆;相反地可以得到小顶堆,如下图所示为大定堆和小顶堆
根结点一定是堆中所有结点最大或者最小者,如果按照层序遍历的方式给结点从1开始编号,则结点之间满足如下关系:
下标i与2i和2i+1是双亲和子女关系。那么把大顶堆和小顶堆用层序遍历存入数组,则一定满足上面的表达式。
1.3 堆排序算法
堆排序(Heap Sort)就是利用堆进行排序的算法,它的基本思想是:
(1) 将待排序的序列构造成一个大顶堆(或小顶堆)。
(2) 此时,整个序列的最大值就是堆顶的根结点。将它移走(就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值)。
(3) 然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的此大值。
(4) 如此反复执行,便能得到一个有序序列了。
2. 代码实现
#include <stdio.h>
int count = 0;
void swap(int k[], int i, int j) //将数组 k[] 中下标为 i 和 j 的元素进行互换
{
int temp;
temp = k[i];
k[i] = k[j];
k[j] = temp;
}
void HeapAdjust(int k[], int s, int n) //调整父节点与孩子结点之间的位置
{
int i, temp;
temp = k[s];
for( i=2*s; i <= n; i*=2 )
{
count++;
if( i < n && k[i] < k[i+1] )
{
i++;
}
if( temp >= k[i] )
{
break;
}
k[s] = k[i];
s = i;
}
k[s] = temp;
}
void HeapSort(int k[], int n)
{
int i;
// 对 1 - 4 这四个结点及它们对应的孩子节点将的相对顺序进行排列
// 整体是在建立一个初始的大顶堆
for( i=n/2; i > 0; i-- ) // i=n/2 是向下取整的,相当于最后的父节点
{
HeapAdjust(k, i, n); //k 是数组,i 循环变量,n 是数组中元素的个数
}
// 利用堆进行排序
for( i=n; i > 1; i-- )
{
swap(k, 1, i);
HeapAdjust(k, 1, i-1);
}
}
int main()
{
int i, a[10] = {-1, 5, 2, 6, 0, 3, 9, 1, 7, 4};
HeapSort(a, 9);
printf("总共执行 %d 次比较!", count);
printf("排序后的结果是:");
for( i=1; i < 10; i++ )
{
printf("%d", a[i]);
}
printf("\n\n");
return 0;
}
在上述的代码中,只有子函数 HeapAdjust(int k[], int s, int n)
较为难理解,他的过程实际上就是调节一个双亲结点和孩子节点之间顺序的作用,如果某一个孩子节点和双亲结点发生了互换,还要讨论互换后对下面的结点的影响。
由原始数组中的数字直接构成的二叉树如下所示
比如说当 i= 1
的时候,执行的结果如下图所示,将最末端的数进行了重新排序,使其满足最大顶的要求
比如说当 i= 2
的时候,执行的结果如下图所示
比如说当 i= 3
的时候,执行的结果如下图所示
这个时候不仅仅将以 2 号结点为根节点的小树的顺序进行了互换,还将以 4 号结点为根节点的小树的顺序进行了互换,这个是十分重要的。