堆的定义

堆排序是一种树形结构选择排序方法,其特点是:在排序过程中,将序列视为一颗完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的关系,在当前无序区间中选择关键字最大(或最小)的元素。堆的定义如下:

n 个关键字序列 L[1,2,3...n] 称为堆,当且仅当该序列满足:

① L(i) <= L(2i) 且 L(i) <= L(2i+1)         ② L(i) >= L(2i) 且 L(i) >= L(2i+1)                   (1 <= i <= ⎿n/2⏌)

满足第一种情况的堆称为小根堆(或小顶堆),满足第二种情况的堆称为大根堆(大顶堆)。在大根堆中,最大的元素存放在根结点中。对其任一非根结点,它的值小于等于其父亲结点的值。小根堆的定义刚好相反,根结点是最小元素。建立一个堆有两种方法,很多书籍把这两种方法称为向上调整和向下调整:

向下调整是让调整的结点与其孩子结点进行比较。
向上调整是让调整的结点与其父亲结点进行比较。

向上调整

向上调整需要从根结点开始建堆(以小根堆为例),基本思路:每添加一个元素,将该元素与其父结点进行比较,若新增结点小于父结点,则交换两个结点的值,然后继续与上一级的父结点进行比较。停止向上调整的条件是该结点大于父结点的值。现有序列 list [ ] = {87, 45, 78, 32, 17, 65, 53, 9} 分别用向上调整和向下调整进行建堆,看看这两种方法有什么区别。

向上调整建堆的过程:

添加元素 9 的向上调整 gif:

向上调整代码:

void AdjustUp(int a[],int size)
{                                //size是数组目前的大小int temp,flag=0;if(size==1)return;                  //此时是堆顶,不需要上调while(size!=1 && flag==0)    {if(a[size] < a[size/2])  //与父结点进行对比{temp = a[size];a[size] = a[size/2];a[size/2] = temp;}else{flag = 1;}size = size/2;           //更新编号i为它父结点的编号。}
}void AdjustUp1(int a[], int k)
{//参数k为向上调整的结点,也是堆的元素个数if(k == 1)return;a[0] = a[k];int i = k/2;while(i > 0 && a[i] > a[0])  //若结点值小于父结点,则将父结点向下调{a[k] = a[i];              //父节点向下调k = i;i = k/2;                  //继续向上比较}a[k] = a[0];
}

向下调整

向下调整建堆是一个反复筛查的过程。n 个结点的完全二叉树,最后一个结点是第⎿n/2⏌个结点的孩子(完全二叉树的性质)。对第⎿n/2⏌个结点为根的子树筛查(对于小根堆,若结点的关键字大于左右孩子中关键字较小者,则交换),使该子树成为堆。之后向前依次对各结点(⎿n/2⏌-1 ~ 1)为根的子树进行筛选,看该结点值是否小于其左右子结点的值,若不小于,则将左右子结点中的较小值与之交换,交换后可能会破坏下一级的堆,于是继续采取上述方法构造下一级的堆,直到以该结点为根的子树构成堆为止。反复利用上述调整方法建堆,直到根结点。根据原始数据建立的树形结构如下:

根据向下调整的思路,从第 4 个结点开始进行筛查建立小根堆:

向下调调整思路:将该结点与其孩子结点进行比较,选取其中最小进行交换,若该节点本身就是最小的结点,则不需要进行交换操作。如上图,需要将 9 和 32 进行交换。然后对第 3 个结点进行调整,该过程较为简单,所以省略。接下来展示第 2 个结点的调整情况。

对第 2 个结点进行调整时,需要交换 9 和 45。但是交换后发现 32 比 45 要小,所以还需要进行一次交换操作。该过程告诉我们,在交换父子结点后,子结点的堆属性可能被破坏,所以要对子结点进行调整(直至子结点为叶子结点为止)。以下是第 2 个结点最终的调整情况:

向下调整的最终形态:

向下调整 gif:

向下调整代码:

void heap_ajust_min(int *b, int i, int size)  //a为数组,size为堆的大小
{int lchild = 2*i;        //i的左孩子节点序号 int rchild = 2*i +1;     //i的右孩子节点序号 int min = i;             //记录根和左右子树中最小的数的下标int temp;if(i <= size/2)          //调整不需要从叶结点开始{if(lchild<=size && b[lchild]<b[min]){min = lchild;}    if(rchild<=size && b[rchild]<b[min]){min = rchild;}                    //两个if语句寻找三个结点中最小的数if(min != i)         //如果min不等于i,说明最小的值在左右子树中{temp = b[i];     //交换a[i]和a[min]的值b[i] = b[min];b[min] = temp;heap_ajust_min(b, min, size); //被交换的子树可能不满足堆的定义,需要对被交换的子树重新调整}}
}void build_heap_min(int *b, int size)      //建立小根堆
{int i;for(i = size/2; i >= 1; i--)           //非叶子节点最大序号值为size/2,从这个结点开始调整{                                      //注意for中的循环条件(i = size/2; i >= 1; i--)heap_ajust_min(b, i, size);         }
}

向下调整的时间与树的高度有关,为 O(h)。建堆过程中每次向下调整时,大部分结点的高度都比较小。我们很容易会发现向上调整和向下调整的最终结果有所不同,但都满足了小根堆的性质。向上调整和向下调整都能够完成建立堆的工作。区别在于,使用向上调整建堆需要从第一个元素开始,过程相当于每次插入一个元素,然后再进行向上调整的工作。向下调整充分的利用了完全二叉树的性质,从⎿n/2⏌结点到根结点逐一筛查。它们的具体操作如下:

for(k = 1; k < size ; ++k)     //向上调整建堆
{AdjustUp(b, k);
}for(i = size/2; i >= 1; i--)   //向下调整建堆
{heap_ajust_min(b, i, size);
}    

堆排序

应用堆进行排序的思路很简单:首先将存放在 L[1,2,3...n] 中的 n 个元素建成初始堆,由于堆本身的特点,堆顶元素就是最值。输出堆顶元素后,通常将堆底元素送入堆顶,此时根结点不满足堆的性质,经过向下调整后使其恢复堆的性质,再输出堆顶元素。重复操作,直至最后一个元素为止。

堆排序代码:

void heap_sort_min(int *a, int size)
{int i;int temp;for(i = size; i >= 1; i--){temp = a[1];a[1] = a[i];a[i] = temp;               //交换堆顶和最后一个元素heap_ajust_min(a, 1, i-1); //再一次调整堆顶节点成为小顶堆}
} 

利用上述小根堆代码完成排序,会发现序列是倒序的,这是因为排序过程是从后往前不断调整小根堆的结果。如果想要获得升序序列,需要使用大根堆进行排序。

堆支持删除和插入操作,删除元素时,需要将最后一个元素放到堆顶,然后使用向下调整,使堆保持堆的特性。对堆进行插入操作时,先将新结点放在堆的末端,再对这个新结点进行向上调整操作。注意:插入元素时(小根堆为例),只需要调用一次向上调整函数便能恢复堆的属性。因为在插入之前,堆本身是完整的。此时若插入元素小于父结点的值,那么只需要交换两个结点而不需要考虑别的情况。这是因为之前满足堆性质,而插入了一个更小的值,交换后堆属性不变。具体可以看添加 9 的时候的 gif。可以根据需求,将向上调整和向下调整搭配着使用。例如:小根堆删除元素后使用向下调整恢复堆的性质。

完整代码:

#include<stdio.h>
#include<math.h>void heap_ajust_min(int *b, int i, int size)  /*a为堆存储数组,size为堆的大小*/
{int lchild = 2*i;       //i的左孩子节点序号 int rchild = 2*i +1;     //i的右孩子节点序号 int min = i; /*存放三个顶点中最大的数的下标*/int temp;if(i <= size/2)          //假设i是叶节点就不用进行调整 {if(lchild<=size && b[lchild]<b[min]){min = lchild;}    if(rchild<=size && b[rchild]<b[min]){min = rchild;}if(min != i){temp = b[i]; /*交换a[i]和a[max]的值*/b[i] = b[min];b[min] = temp;heap_ajust_min(b, min, size); /*被交换的位置曾经是大根堆,如今可能不是大根堆所以须要又一次调整使其成为大根堆结构*/ }}
}void build_bheap_min(int *b, int size) //建立小堆
{int i;for(i=size/2; i >= 1; i--)      //非叶节点最大序号值为size/2{heap_ajust_min(b, i, size); }
}void heap_sort_min(int *a, int size)
{int i;int temp;for(i = size; i >= 1; i--){temp = a[1];a[1] = a[i];a[i] = temp;               //交换堆顶和最后一个元素heap_ajust_min(a, 1, i-1); //再一次调整堆顶节点成为小顶堆}
} void AdjustUp(int a[],int size)
{int temp,flag=0;int k;if(size==1)return;//此时是堆顶,不需要上调while(size!=1 && flag==0){if(a[size] < a[size/2]){temp = a[size];a[size] = a[size/2];a[size/2] = temp;}else{flag = 1;}size = size/2;//更新编号i为它父结点的编号。}
}void AdjustUp1(int a[], int k)
{//参数k为向上调整的结点,也是堆的元素个数if(k == 1)return;a[0] = a[k];int i = k/2;while(i > 0 && a[i] > a[0])  //若结点值小于父结点,则将父结点向下调{a[k] = a[i];              //父节点向下调k = i;i = k/2;                  //继续向上比较}a[k] = a[0];
}int main(int argc, char *argv[])
{   int i,j,k;int count=1;int a[]={0, 87, 45, 78, 32, 17, 65, 53, 9};int b[]={0, 87, 45, 78, 32, 17, 65, 53, 9};int size = sizeof(a)/sizeof(int) -1;build_bheap_min(a, size);printf("向下调整建立小顶堆:\n"); count=1;for(i=0;i<=4;i++){for(j=0;j<pow(2,i);j++){if(count<=size){printf("%d ",a[count++]);}else{break;}}printf("\n");}for(k = 1; k < 9 ; ++k){AdjustUp(b, k);}printf("向上调整小顶堆:\n"); count=1;for(i=0;i<=4;i++){for(j=0;j<pow(2,i);j++){if(count<=size){printf("%d ",b[count++]);}else{break;}}printf("\n");}return 0;
}

通俗易懂的讲解堆排序(含Gif图)相关推荐

  1. 通俗易懂的讲解梯度,散度,旋度

    通俗易懂的讲解梯度,散度,旋度(有图很好理解)!!! 并且会不断连载原创或转载有价值的参数化软件教程返回搜狐,查看更多 声明:该文观点仅代表作者本人,搜狐号系信息发布平台,搜狐仅提供信息存储空间服务.

  2. 编译原理 First集 Follow集 select集 通俗易懂的讲解 + 实例

    #First集Follow集通俗易懂的讲解加实例 ##First 如A->aB | CD 这里面包含了组成First(A)的两种情况: 以终结符开头,当然要把这个终结符(a)放到A的First里 ...

  3. 模拟退火算法详细讲解(含实例python代码)

    模拟退火算法详细讲解(含实例python代码) (一)模拟退火算法简介 (二)模拟退火算法原理 (三)退火过程中参数控制 (四)算法步骤 (五)实例分析 最近老师要求做模拟退火算法实验,看了很多博客之 ...

  4. 坯子库曲面推拉教程_psd素材丨嘤,今天是仙仙的水墨风建筑表达教程(文末附讲解视频+效果图+贴图素材合集)...

    今天是仙仙的水墨风建筑表达教程,并且附带一些建筑表皮的贴图+懒人景观贴图 讲解视频+效果图+贴图素材合集 「获取方式」 关注公众号:毅禾空间 回复[019] 自助获取 关注公众号:毅禾空间 回复以下关 ...

  5. 五子棋游戏Java代码简单实现(含活动图和类图设计)

    五子棋游戏Java代码简单实现(含活动图和类图设计) 文章目录 五子棋游戏Java代码简单实现(含活动图和类图设计) 活动图设计 类图设计 代码实现 总结 OOA和OOD设计 代码设计 可改进部分 活 ...

  6. Oracle Unifier 工程项目管理业务架构(含脑图)持续更新中...

    继上文介绍了Unifier学习地图后,可以再依据Unifier强大的零代码配置功能 配置类似SAP ERP强大的业务模板功能 Primavera Unifier学习地图(含脑图Mind)_蚕豆哥的博客 ...

  7. 雕刻机6轴usb控制卡源码RTCP算法双源码含pcb图

    雕刻机6轴usb控制卡源码RTCP算法双源码含pcb图,,可真正使用 ID:1828639828742992你妹说的对

  8. 史上最全电子产品接口知识大全(含实物图40种,收藏)

    史上最全电子产品接口知识大全(含实物图40种,收藏) 2017-07-31 EDN电子技术设计 1.IDE接口(一种硬盘接口) IDE的英文全称为"Integrated Drive Elec ...

  9. 多种形貌氧化锌ZnO纳米片、纳米棒、纳米线、纳米管的合成简单介绍(含电镜图)

    多种形貌氧化锌ZnO纳米片.纳米棒.纳米线.纳米管的合成简单介绍(含电镜图) 西安齐岳生物专业供应各种各种形态.维数的ZnO纳米材料,多种形貌包括纳米氧化锌(ZnO)纳米线.(ZnO)纳米棒.ZnO氧 ...

最新文章

  1. 长篇自动驾驶技术综述论文(上)
  2. 别再写满屏的try-catch了,真丑,全局异常处理不会吗?
  3. 机器学习误差分析(Error Analysis)实战
  4. Android WiFi热点完全研究(自定义创建、跳转系统界面设置、读取配置、切换,Android6.0适配)...
  5. python语言入门u-Python语言十分钟快速入门
  6. Hyperledger Fabric 1.2 --- Chaincode Operator 解读和测试(一)
  7. 栈的应用_中缀表达式转后缀表达式
  8. JAVA可不可以编写应用程序_编写一个java应用程序
  9. 【OpenCV 例程200篇】90. 频率域陷波滤波器
  10. 小米 信号测试软件,iQOO和小米9信号之争:多方位网络测试,最终由谁胜出?
  11. 关于Visual Studio .NET 2010最近的发布情况
  12. 个人三观的东西(1)
  13. hihoCoder #1349 Nature Numbers
  14. VS2022编译项目出现““csc.exe”已退出,代码为 -1073741819”的错误解决办法
  15. 【python】matplotlib绘图显示不了中文,且没有SimHei、FangSong等字体
  16. 离线式echarts模拟百度迁徙的实现
  17. 2020 ICPC 济南 A Matrix Equation (高斯消元)
  18. 开发群发微信图文消息,正文中的图片却不显示问题
  19. U-BOOT小全(一)
  20. PaaS简介及国内PaaS平台

热门文章

  1. 《openssl编程》之BIO
  2. cocos2d-x游戏实例(18)-纵版射击游戏(5)
  3. 零字节WSASend,WSARecv
  4. C++中空指针调用类成员函数的原理
  5. HashMap+双向链表实现LRU
  6. 完成一个休闲网络游戏需要学习的知识
  7. 我就改了一行代码,为什么就全超时了?
  8. 无人值守的自动 dump(一)
  9. 窥见C++11智能指针
  10. 科普篇 | 推荐系统之矩阵分解模型