C语言中堆排序怎么用(c语言,开发技术)

时间:2024-04-29 18:17:16 作者 : 石家庄SEO 分类 : 开发技术
  • TAG :

    一、堆排序的概念

    ???? 堆排序(Heapsort):利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。通过堆来进行选择数据,需要注意的是排升序要建大堆,排降序建小堆。

    堆排序使用堆来选数,效率就高了很多。

    • 时间复杂度:C语言中堆排序怎么用

    • 空间复杂度:C语言中堆排序怎么用

    • 稳定性:不稳定

    二、堆排序的实现

    我们先创建一个堆排序的函数:

    voidHeapSort(intarr[],intn);

    假设我们要对下列数组来使用堆排序(升序):

    intarr[]={70,56,30,25,15,10,75};

    根据我们之前学到的知识,数组是可以直接看作为完全二叉树的,所以我们可以把它化为堆。此时我们就可以 "选数" (堆排序本质上是一种选择排序)。

    C语言中堆排序怎么用

    第一步:构建堆

    第一步就是要想办法把 arr 数组构建成堆(这里我们先构建成小堆)。我们介绍两种方法,分别为向上调整算法和向下调整算法:

    方法1:向上调整

    通过我们之前学过的 "向上调整" ,利用插入的思路来解决。首先设计下向上调整的算法:

    voidSwap(HPDataType*px,HPDataType*py){HPDataTypetmp=*px;*px=*py;*py=tmp;}/*小堆的向上调整*/voidAdjustUp(int*arr,intchild){assert(arr);//首先根据公式计算算出父亲的下标intparent=(child-1)/2;//最坏情况:调到根,child=parent当child为根节点时结束(根节点永远是0)while(child>0){if(arr[child]<arr[parent]){//如果孩子小于父亲(不符合小堆的性质)//交换他们的值Swap(&arr[child],&arr[parent]);//传地址//往上走child=parent;parent=(child-1)/2;}else{//如果孩子大于父亲(符合小堆的性质)//跳出循环break;}}}

    ① 首先,通过公式计算出父亲的下标,公式如下:

    C语言中堆排序怎么用

    ② 其次,设计循环部分,最坏情况为调到根,如果已经符合大堆的条件则终止循环。

    ③ 进入循环后进行判断,如果 child > parent,则交换它们的值,让父亲获得孩子的值,孩子得到父亲的值,从而让它们符合大堆的性质。

    ④ 交换完毕后,以便于继续判断,我们需要更新 child 和 parent 指向的元素,做到 "往上走"

    此时,我们把数组里的元素依次调整即可。

    ???? 方法1:

    /*升序*/voidHeapSort(intarr[],intn){for(inti=1;i<n;i++){AdjustUp(arr,i);//传入数组和child的下标}}

    我们不需要从 0 开始调,从 1 开始调。所以 i 的起始值设置为 1 。此时,小堆就构建好了。

    方法2:向下调整

    向下调整算法我们在堆那个章节也学过了,这里我们再来复习一下:

    voidSmallAjustDown(int*arr,intn,intparent){intchild=parent*2+1;//默认为左孩子while(child<n){//叶子内//选出左右孩子中小的那一个if(child+1<n&&arr[child+1]<arr[child]){child=child+1;}//如果孩子小于父亲(不符合小堆的性质)if(arr[child]<arr[parent]){//交换它们的值Swap(&arr[child],&arr[parent]);//往下走parent=child;child=parent*2+1;}else{//如果孩子大于父亲(符合小堆的性质)//跳出循环break;}}}

    ① 因为要考虑左孩子还是右孩子,我们可以定义两个变量,分别记录左孩子和有孩子。但是我们这里可以用更好的方法,只需要定义一个孩子即可。具体实现方法如下:首先创建 child,我们先默认它是左孩子,利用传进来的 parent 根据公式计算出 child 的大小:

    C语言中堆排序怎么用

    因为我们暂且默认为左孩子,我们随后进入循环后要进行检查,看看是左孩子大还是右孩子大,这里我们只需要根据数组的性质,将 child + 1 即可得到右孩子的下标,从而方便我们进行比较。比较完毕后将 child 重新赋值,拿个孩子小就将 child 给谁。

    ② 一共有两个结束条件(出口),第一个结束条件是父亲小于孩子就停止,第二个结束条件是chi调整到叶子。所以,循环体判断部分根据我们分析的结束条件,如果调整到叶子就结束,我们接受了 n,也就是数组的大小,只要 child 超过数组的大小(child > n) 就结束,这是第一个出口。

    ③ 进入循环后,对比左孩子和右孩子哪一个更小,child 就交付给谁。这里还要注意的是,要防止孩子不存在的情况,如果 child + 1 比 n 大,就说明孩子不存在。所以我们再写 if 判断条件的时候就要注意了,我们加一个 child + 1 < n 的条件限制一下:

    if(child+1<n&&arr[child+1]<arr[child]){...}

    ④ 确认好较小的孩子后,我们就可以开始比较孩子和父亲的大小了。如果孩子小于父亲,就不符合小堆的性质,我们就要交换它们的值。这里我们直接调用我们刚才写的 Swap 接口即可,这就是为什么在写向上调整的时候要我们单独把交换部分的代码写成函数的原因。

    ⑤ 交换完毕后,他们的值已经互相交换好了,这时我们要改变 parent 和 child 的指向,让它们往下走,parent = child ,child 再次根据公式算出新的 child 即可。

    ⑥ 设计下 if 的 else 部分,如果数组的 child 的值大于 parent 的值,说明符合小堆的性质了, break 跳出循环即可,这是第二个出口。

    向下调整算法的前提:左右子树必须都是小堆

    如果左子树和右子树不是小堆,怎么办?

    可以用递归解决,但是我们能用循环就用循环来解决:

    叶子所在的子树是不需要调的。所以,我们从倒着走的第一个非叶子结点的子树开始调。

    C语言中堆排序怎么用

    (所以我们先调整30)

    为了能够演示得更明显,我们再给数组再加点数据,假设我们需要对以下数组排序:

    C语言中堆排序怎么用

    这里,我们找到了15。

    &hellip;&hellip; 下标不断地 - - 后:

    C语言中堆排序怎么用

    由于,从倒着走的第一个非叶子结点的子树开始调,即,最后一个节点的父亲。

    我们已知最后一个节点的下标为:C语言中堆排序怎么用

    那么,我们可以通过公式计算出他父亲的下标:C语言中堆排序怎么用

    ???? 方法2:

    /*升序*/voidHeapSort(intarr[],intn){for(inti=(n-1-1)/2;i>=0;i--){AdjustDown(arr,n,i);}}

    ⚡ 这么写或许可以看得更明白:

    /*升序*/voidHeapSort(intarr[],intsz){ intfather=((sz-1)-1)/2;//计算出最后一个叶子节点的父亲 while(father>=0){ AdjustDown(arr,sz,father); father--; }}

    ???? 测试堆是否创建好了:

    我们这里选择使用方法2。此外,我们刚才实现的是小堆。

    #include<stdio.h>/*交换函数*/voidSwap(int*px,int*py){inttmp=*px;*px=*py;*py=tmp;}/*小堆下调*/voidAdjustDown(intarr[],intsz,intfather_idx){intchild_idx=father_idx*2+1;//计算出左孩子的值(默认认为左孩子大)while(child_idx<sz){//最坏情況:调到叶子(child>=数组范围时必然已经调到叶子)if((child_idx+1<sz)&&(arr[child_idx+1]<arr[child_idx])){//如果右孩子存在且右孩子比左孩子小child_idx=child_idx+1;//让其代表右孩子}if(arr[child_idx]<arr[father_idx]){//如果孩子的值小于父亲的值(大符合小堆的性質)Swap(&arr[child_idx],&arr[father_idx]);//交换它们的值/*往下走*/father_idx=child_idx;//更新下标child_idx=father_idx*2+1;//计算出该节点路线的新父亲}else{//如果孩子的值大于父亲的值(符合小堆的性质)break;//终止循环}}}/*升序*/voidHeapSort(intarr[],intsz){ /*创建大堆,选出最大的数O(N)*/ intfather=((sz-1)-1)/2;//计算出最后一个叶子节点的父亲 while(father>=0){ AdjustDown(arr,sz,father); father--; }}voidHeapPrint(intarr[],intsz){for(inti=0;i<sz;i++){printf("%d",arr[i]);}printf("\n");}intmain(void){intarr[]={70,56,30,25,15,10,75,33,50,69};intsz=sizeof(arr)/sizeof(arr[0]);HeapSort(arr,sz);HeapPrint(arr,sz);return0;}

    ???? 运行结果:10 15 30 25 56 70 75 33 50 69

    C语言中堆排序怎么用

    第二步:排序

    刚才介绍了两种方法来构建堆,现在堆已经构建完毕了,我们可以开始设计排序部分的算法了。

    ❓ 如果排升序,建小堆&hellip;&hellip;

    ① 选出最小的数,放到第一个位置,这很简单,直接取顶部就可以得到最小的数。

    ② 但问题来了,如何选出次小的数呢?

    C语言中堆排序怎么用

    从 15 开始,剩下的元素看作一个堆。但是这之前建立好的堆关系全部乱了!你还得重新建堆,才能选出次小的数。建堆的时间复杂度为C语言中堆排序怎么用,需要不断地建堆C语言中堆排序怎么用则用小堆的时间复杂度就是C语言中堆排序怎么用,这太离谱了!搞了一大圈结果居然是C语言中堆排序怎么用,还不如直接遍历选出来的方便呢。

    建小堆来排升序是完全可以的,但是效率太低!完全没有体现出你用堆的优势!

    ⚡ 解决方法:使用大堆来排升序!

    ???? 我们刚才已经实现好小堆了,根据上一节学到的知识,小堆要变成大堆,直接把刚才的代码的 "<" 改成 ">"即可:

    #include<stdio.h>/*交换函数*/voidSwap(int*px,int*py){inttmp=*px;*px=*py;*py=tmp;}/*大堆下调*/voidAdjustDown(intarr[],intsz,intfather_idx){intchild_idx=father_idx*2+1;//计算出左孩子的值(默认认为左孩子大)while(child_idx<sz){//最坏情況:调到叶子(child>=数组范围时必然已经调到叶子)if((child_idx+1<sz)&&(arr[child_idx+1]>arr[child_idx])){//如果右孩子存在且右孩子比左孩子大child_idx=child_idx+1;//让其代表右孩子}if(arr[child_idx]>arr[father_idx]){//如果孩子的值大于父亲的值(不符合大堆的性質)Swap(&arr[child_idx],&arr[father_idx]);//交换它们的值/*往下走*/father_idx=child_idx;//更新下标child_idx=father_idx*2+1;//计算出该节点路线的新父亲}else{//如果孩子的值小于父亲的值(符合大堆的性质)break;//终止循环}}}/*升序*/voidHeapSort(intarr[],intsz){ /*创建大堆,选出最大的数O(N)*/ intfather=((sz-1)-1)/2;//计算出最后一个叶子节点的父亲 while(father>=0){ AdjustDown(arr,sz,father); father--; }}voidPrintArray(intarr[],intsz){for(inti=0;i<sz;i++){printf("%d",arr[i]);}printf("\n");}intmain(void){intarr[]={70,56,30,25,15,10,75,33,50,69};intsz=sizeof(arr)/sizeof(arr[0]);HeapSort(arr,sz);PrintArray(arr,sz);return0;}

    ???? 运行结果:75 69 70 50 56 10 30 33 25 15

    C语言中堆排序怎么用

    ???? 现在改成了大堆,我们要排升序,我们可以让堆顶数和最后的数进行交换:

    C语言中堆排序怎么用

    这并不会带来堆结构的破坏!我们把75不看作堆的一部分即可。再进行向下调整,就可以找到次小的数了。此时时间复杂度为

    C语言中堆排序怎么用

    ???? 步骤总结:

    ① 建大堆,选出最大的数。

    ② 最大的数跟最后一个数交换。

    ③ 如何选出次大的数呢?把最后一个数不看作堆里面,进行向下调整。

    ???? 代码实现(用 while):

    /*堆排序-升序*/voidHeapSort(intarr[],intsz){ /*创建大堆,选出最大的数O(N)*/ intfather=((sz-1)-1)/2;//计算出最后一个叶子节点的父亲 while(father>=0){ AdjustDown(arr,sz,father); father--; } /*依次选数,调堆O(N*logN)*/ intend=sz-1; while(end>0){ Swap(&arr[0],&arr[end]);//最大的数跟最后一个数交换 AdjustDown(arr,end,0);//调堆,选出次大的数 end--; }}

    ???? 代码实现(用 for):

    voidHeapSort(intarr[],intsz){ /*建堆*/ for(intfather=(sz-1-1)/2;father>=0;father--){ AdjustDown(arr,sz,father); } /*排序*/ for(intend=sz-1;end>0;end--){ Swap(&arr[0],&arr[end]);//最大的数跟最后一个数交换 AdjustDown(arr,end,0);//调堆,选出次大的数 }}

    三、完整代码

    (排升序要建大堆,排降序建小堆)

    ???? 升序:使用大堆

    #define_CRT_SECURE_NO_WARNINGS1#include<stdio.h>voidSwap(int*pa,int*pb){ inttmp=*pa; *pa=*pb; *pb=tmp;}voidAdjustDown(intarr[],intsz,intfather){ intchild=father*2+1; while(child<sz){ if(child+1<sz&&arr[child+1]>arr[child]){ child+=1; } if(arr[child]>arr[father]){ Swap(&arr[child],&arr[father]); father=child; child=father*2+1; } else{ break; } }}/*堆排序-升序*/voidHeapSort(intarr[],intsz){ /*创建大堆,选出最大的数O(N)*/ intfather=((sz-1)-1)/2;//计算出最后一个叶子节点的父亲 while(father>=0){ AdjustDown(arr,sz,father); father--; } /*依次选数,调堆O(N*logN)*/ intend=sz-1; while(end>0){ Swap(&arr[0],&arr[end]);//最大的数跟最后一个数交换 AdjustDown(arr,end,0);//调堆,选出次大的数 end--; }}voidHeapPrint(intarr[],intsz){ inti=0; for(i=0;i<sz;i++){ printf("%d",arr[i]); } printf("\n");}intmain(void){ intarr[]={70,56,30,25,15,10,75,33,50,69}; intsz=sizeof(arr)/sizeof(arr[0]); printf("排序前:"); HeapPrint(arr,sz); HeapSort(arr,sz); printf("排序后:"); HeapPrint(arr,sz); return0;}

    ???? 运行结果:

    C语言中堆排序怎么用

    ❓ 如果要排降序呢?使用小堆即可!

    ???? 降序:使用小堆

    #include<stdio.h>/*交换函数*/voidSwap(int*px,int*py){inttmp=*px;*px=*py;*py=tmp;}/*小堆下调*/voidAdjustDown(intarr[],intsz,intfather_idx){intchild_idx=father_idx*2+1;//计算出左孩子的值(默认认为左孩子大)while(child_idx<sz){//最坏情況:调到叶子(child>=数组范围时必然已经调到叶子)if((child_idx+1<sz)&&(arr[child_idx+1]<arr[child_idx])){//如果右孩子存在且右孩子比左孩子小child_idx=child_idx+1;//让其代表右孩子}if(arr[child_idx]<arr[father_idx]){//如果孩子的值小于父亲的值(不符合小堆的性質)Swap(&arr[child_idx],&arr[father_idx]);//交换它们的值/*往下走*/father_idx=child_idx;//更新下标child_idx=father_idx*2+1;//计算出该节点路线的新父亲}else{//如果孩子的值大于父亲的值(符合小堆的性质)break;//终止循环}}}/*堆排序-降序*/voidHeapSort(intarr[],intsz){ /*创建大堆,选出最大的数O(N)*/ intfather=((sz-1)-1)/2;//计算出最后一个叶子节点的父亲 while(father>=0){ AdjustDown(arr,sz,father); father--; } /*依次选数,调堆O(N*logN)*/ intend=sz-1; while(end>0){ Swap(&arr[0],&arr[end]);//最大的数跟最后一个数交换 AdjustDown(arr,end,0);//调堆,选出次小的数 end--; }}voidPrintArray(intarr[],intsz){for(inti=0;i<sz;i++){printf("%d",arr[i]);}printf("\n");}intmain(void){intarr[]={70,56,30,25,15,10,75,33,50,69};intsz=sizeof(arr)/sizeof(arr[0]);printf("排序前:");PrintArray(arr,sz);HeapSort(arr,sz);printf("排序后:");PrintArray(arr,sz);return0;}

    ???? 运行结果:

    C语言中堆排序怎么用

    四、证明建堆的时间复杂度

    因为堆是完全二叉树,而满二叉树也是完全二叉树。此处为了简化,将采用满二叉树来证明。(时间复杂度本来看的就是近似值,所以多几个节点不会影响最终结果):

    假设树的高度为C语言中堆排序怎么用

    C语言中堆排序怎么用

    C语言中堆排序怎么用层:C语言中堆排序怎么用个节点,需要向下移动C语言中堆排序怎么用

    C语言中堆排序怎么用层:C语言中堆排序怎么用个节点,需要向下移动C语言中堆排序怎么用

    C语言中堆排序怎么用层:C语言中堆排序怎么用个节点,需要向下移动C语言中堆排序怎么用

    C语言中堆排序怎么用层:C语言中堆排序怎么用个节点,需要向下移动C语言中堆排序怎么用

    &hellip;&hellip;

    C语言中堆排序怎么用层:C语言中堆排序怎么用个节点,需要向下移动C语言中堆排序怎么用

    则需要移动节点总的移动步数为:

    C语言中堆排序怎么用

    C语言中堆排序怎么用

    ② - ① 错位相减:

    C语言中堆排序怎么用

    C语言中堆排序怎么用

    C语言中堆排序怎么用

    C语言中堆排序怎么用

    C语言中堆排序怎么用

    因此,建堆的时间复杂度为C语言中堆排序怎么用

     </div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
    本文:C语言中堆排序怎么用的详细内容,希望对您有所帮助,信息来源于网络。
    上一篇:C#如何创建Windows服务与服务的安装、卸载下一篇:

    9 人围观 / 0 条评论 ↓快速评论↓

    (必须)

    (必须,保密)

    阿狸1 阿狸2 阿狸3 阿狸4 阿狸5 阿狸6 阿狸7 阿狸8 阿狸9 阿狸10 阿狸11 阿狸12 阿狸13 阿狸14 阿狸15 阿狸16 阿狸17 阿狸18