排序:堆排序(C)

1. 堆排序思想

1.1 思想概括

基于完全二叉树进行操作排序的
第一步:创建大根堆(或者小根堆)
第二步:取堆顶关键字与无序区最后一个元素互换.然后剩下的无序区元素进行第一步.
最后直到无序区元素为1个,结束循环.
解释:
大根堆:根结点大于左右子孩子
小根堆:根结点小于左右子孩子
注意:大根堆或者小根堆是完全二叉树.

1.2 准备完全二叉树

因为堆排序是基于完全二叉树,则提前准备好一个完全二叉树.
待排序数组R=[10,15,56,6,33,5,26,8]
完全二叉树:
在这里插入图片描述

1.3 创建大根堆

这里以建立大根堆为例:

1.若结点位置为: i ,其左孩子位置: 2*i, 其右孩子位置: 2*i+1.
2.先判断左孩子和右孩子的最大值, 如果 i 结点的值小于其左孩子和右孩子的最大值,则 i 结点与最大值位置的互换.否则不用调整.
3.就是 i 结点的值大于其左孩子(2*i)的值和右孩子(2*i+1)的值.
4.执行以上的操作,N/2的位置结点起到树根结点开始进行大堆的创建.
注意:堆排序中大根堆(小根堆)是完全二叉树为基础.

一共有8个结点,

在这里插入图片描述
第一步: 从第4个结点(6)判断是否需要调整:
第4个结点小于其左孩子,需要调整,6和8互换.结果为下图.

在这里插入图片描述
第二步:第3个结点(56)判断是否需要调整:
第3个结点大于其左孩子和右孩子,不需要调整.结果为下图:

在这里插入图片描述
建大根堆第三步: 第2个结点(15)判断是否需要调整:
第2个结点小于其右孩子,则15月33互换位置.结果为下图:

在这里插入图片描述
第四步: 第1个结点(10)判断是否需要调整:
第1个结点小于其左孩子和右孩子,右孩子大于左孩子,所以10与56互换位置.
互换后:10又小于其右孩子26,所以10月26位置还要互换一次.结果为下图:
在这里插入图片描述
到此大根堆已经建立完成:由结果图可以验证所有的根结点都大于其左右子孩子.

1.4 堆排序过程

大根堆创建完成后,其实还是无序的.
下面一步就是取关键字的步骤.
1.每次都取堆顶,然后与无序区的最后一个记录进行交换,
2.然后再将剩下的区域再走一次创建大根堆.
然后重复1和2步骤,直到无序区的结点个数为1

下面进行取关键字的步骤:

无序区用:
在这里插入图片描述

有序区用:
在这里插入图片描述

在这里插入图片描述
取关键字第一步:取堆顶元素56,与无序区最后一个元素6互换位置(56加入了有序区),然后再把剩下的无序区走一遍建立大根堆,结果为下面的图.

在这里插入图片描述
取关键字第二步:取堆顶元素33,与无序区最后一个元素10互换位置(33加入了有序区),然后再把剩下的无序区走一遍建立大根堆,结果为下面的图.
在这里插入图片描述
取关键字第三步:取堆顶元素26,与无序区最后一个元素5互换位置(26加入了有序区),然后再把剩下的无序区走一遍建立大根堆,结果为下面的图.
在这里插入图片描述
取关键字第四步:取堆顶元素15,与无序区最后一个元素6互换位置(15加入了有序区),然后再把剩下的无序区走一遍建立大根堆,结果为下面的图.
在这里插入图片描述
取关键字第五步:取堆顶元素10,与无序区最后一个元素5互换位置(10加入了有序区),然后再把剩下的无序区走一遍建立大根堆,结果为下面的图.
在这里插入图片描述
取关键字第六步:取堆顶元素8,与无序区最后一个元素6互换位置(8加入了有序区),然后再把剩下的无序区走一遍建立大根堆,结果为下面的图.

在这里插入图片描述
取关键字第七步:取堆顶元素6,与无序区最后一个元素5互换位置(6加入了有序区),然后再把剩下的无序区走一遍建立大根堆,结果为下面的图.

在这里插入图片描述
此时无序区就剩下了一个结点,取关键字结束.
最后的有序的完全二叉树为:
在这里插入图片描述
有序的数组为前序遍历二叉树[5,6,8,10,15,26,33,56].

2.堆排序实现

2.1 定义数据结构

typedef int KeyType;
typedef struct {
    KeyType  key;
}RecType;
typedef RecType SeqSortList[MAXSIZE+1];//表中的0元素空着或用作哨兵单元

一般把数组中的第0个位置预留出来,就是为了作为哨兵,进行两个数据进行交换时,可以把哨兵作为临时变量进行交换.

2.2 堆排序方法

//一次堆排序.将指定范围的结点置为大根堆.
void Sift(SeqSortList R,int i,int h){
    //将R[i...h]调整为大堆跟,假定R[i]的左右孩子均满足堆的性质
    int j;
    RecType x=R[i];
    j=2*i;
    while (j<=h) {//当R[i]的左孩子不为空时执行循环
        if(j<h && R[j].key<R[j+1].key)
            j++;//若右孩子的关键字较大,j++.置为右孩子角标
        if(x.key>R[j].key)//表示当前x大于其左右孩子,找到了x的位置
            break;
        R[i]=R[j];//将大于i位置的置为双亲的位置
        i=j;j=2*i;//然后修改i和j,重新调整指向,重新查找位置.
    }
    R[i]=x;
}
//堆排序方法
void HeapSort(SeqSortList R,int n){
    //对R[1..h]进行堆排序,设置R[0]为暂存单元
    int i;
    //这里的循环是建立大堆
    for(i=n/2;i>=1;i--)
        Sift(R, i, n);
    printf("大根堆:");
    logSort(R, 1, n);
    printf("\n");
    for(i=n;i>1;i--){//对R[1..h]进行n-1此堆排序,取出了n-1个数据,剩下的一个数据,自然就是最小的.
        R[0]=R[1];R[1]=R[i];R[i]=R[0];//每次都R[1]为最大的,将R[1]与R[i]进行互换,
        Sift(R, 1, i-1);//然后再让下面是对R[1...i-1]建立堆排序
        printf("取关键字%d后的完全二叉树:",R[0]);
        logSort(R, 1, n);
        printf("\n");
    }
}

2.3 打印集合方法

打印数组集合: R:数组对象. start:开始打印的角标. end:结束打印的角标.

void  logSort(SeqSortList R,int start,int end){
    printf("[");
    for(int i=start;i<=end;i++){
        if(i==start){
            printf("%d",R[i].key);
        }else{
            printf(",%d",R[i].key);
        }
    }
    printf("]");
}

2.4 示例调用

    int  n=8;
    SeqSortList R;
    R[0].key=0;//哨兵
    R[1].key=10;
    R[2].key=15;
    R[3].key=56;
    R[4].key=6;
    R[5].key=33;
    R[6].key=5;
    R[7].key=26;
    R[8].key=8;
    printf("无序R=");
    logSort(R, 1, n);
    printf("\n");
    HeapSort(R, n);
    printf("有序R=");
    logSort(R, 1, n);
    printf("\n");

3.堆排序结果

在这里插入图片描述

4.复杂度

时间复杂度:O(n log ⁡ 2 n \log_2{n} log2n)
控件复杂度:O(1)
源码下载

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值