概念补充:
二叉树:是n个结点的有限集合,该集合或为空(空二叉树),或者由一个根节点和两颗互不相交的、分别称为根节点的左子树和
右子树的二叉树组成
完全二叉树:
以上是完全二叉树,具有n个节点的二叉树按层序遍历,如果i的节点与同样深度的满二叉树编号为i的节点位置
完全相同,则这个二叉树为完全二叉树
性质5:如果一个有n个节点的完全二叉树,的节点按层序编号,,对任意节点(1<=i<=n)
如果i=1,节点i是二叉树的根,无双亲,如果i>1,则其双亲节点是i/2
如果2i>n,该节点无左子树,否则其左节点是2i
如果2i+1>n,该节点无右子树,否则其右节点是2i+1
堆是具有以下特殊性质的完全二叉树:
每个节点的值都大于等于左右孩子节点,大顶堆
每个节点的值都小于等于左右孩子节点,小顶堆
按层序遍历的方式,给节点从1开始编号,节点间满足下列关系
ki>=k2i 或者ki<=k2i
ki>=k2i+1ki<=k2i+1(1<=i<=n/2)
堆排序算法:将待排序序列构造成大顶堆,此时整个序列最大值为根节点,将其与堆数组中的末尾元素交换,
此时末尾元素就是最大值,然后将剩余的n-1个节点的序列构造成大顶堆,如此反复。
堆排序代码:
void HeapSort(arr *L)
{
int i;
//构建大顶堆
for ( i=L->length/2;i>0;i-- )
{
HeapAdjust(L,i,L->length);
}
for ( i=L->length;i>1;i-- )
{
//将大顶堆的根节点与最后一个节点交换
swap(L,1,i);
//交换后的序列重新调整为大顶堆
HeapAdjust(L,1,i-1);
}
}
//L序列L->data[s...n]之间除了r->data[s]之外均满足堆的定义
//本函数调整L->data[s]的关键字,使之满足大顶堆
void HeapAdjust(arr *L,int s, int n )
{
int i,j;
int temp;
temp=L->data[s];
for ( i=2*s;i<=n;i*=2 )
{
if( i<n && (L->data[i] < L->data[i+1]) )
{
i++;
}
if ( temp >= L->data[i] )
{
break;
}
L->data[s]=L->data[i];
s=i;
}
L->data[s]=temp;
}
以{50,10,90,30,70,40,80,60,20}为待排序序列讲解
L->data:50 10 90 30 70 40 80 60 20
s=length/2=9/2=4,n=9,既节点30的调整,
变量j以2*s开始,又是以j*=2递变,是根据二叉树的性质5,当前节点为s,其左孩子是2*是,右孩子是2*s+1,
他们的孩子也是以2的位数序数增加。
temp=data[4]=30
i=2*s=8,i<n
左右孩子对比,找出大的孩子, data[j]<data[j+1];
j=8<n, 60>20,j=8
30<60,s=j,data[s=4]=data[j=8],既将左孩子放到双亲节点处
j=16>n,s=8,data[s=8]=temp=30
以上为一个节点的调整
接下来同样的方法对i=3,90节点做调整,i=2,10节点作调整,
i=1,50节点做调整
s=1,m=9
temp=data[s=1]=50
j=2*s=2左右孩子对比10<90,j++
j=3, temp<data[j],data[s]=data[j]
s=3,
j=2*s=6左右孩子对比,40<80,j++
j=7,temp<data[j=7]
s=7,j=2*s=14
j>n=9,退出for循环
data[s=7]=temp=10
通过对不是叶子节点的节点做调整,将该数组做成一个大顶堆的完全二叉树数组。
将根节点90与最后一个接点交换,并重新调整
下一次length=8,调整将9排除数组树外,既最大值在数组的最右边
for(i=length/2;i>0;i--)
我们所谓的将待排序的序列构建成一个大顶堆,其实就是从下往上,从右到左,将每个非
终端节点当做根节点,将其子树调整成大顶堆。
最小的非终端节点,n/2
堆排序时间复杂度分析:
构建堆的过程,时间复杂度为O(n)
正式排序时,时间复杂度为O(nlogn)
总体来说,堆的时间复杂度为O(nlogn)
堆排序对原始记录的排序状态并不敏感,因此它的最好,与最坏平均时间复杂度均为O(nlogn)
因为初始构建堆所需的比较次数较多,因此堆排序不适合排序序列个数较少的情况