算法学习记录-排序——堆排序

堆排序

 

问题提出:

  前面写过 简单选择排序 讲过,要找出n个数据中最小的值,那么需要比较n-1次。仔细想想,比较第一趟的时候,第一个哨兵下标元素

与每一个元素比较,如果哨兵元素不是最小的,那么会发生交换,记交换后的元素下标为swap。继续比较第二趟 到 第n趟时候,每一趟都

会有元素与swap下标元素比较,但是这个比较之前就已经比较了,只是没有保存下来。能不能通过方法能够记录这些比较,使其重复比较的

次数减少?

  堆排序就是对简单选择排序的一种改进,通过一种数据结构来保存比较之后的元素关系,在大量数据比较时候,效率非常高。

 


 

堆具有的性质:

n个元素序列{k1,k2,k3,...,kn}满足如下关系:

  • ki ≤ k2i 且 ki ≤ k2i+1     此为:小顶堆

或者 

  • ki ≥ k2i 且 ki ≥ k2i+1     此为:大顶堆

    (i=1,2,。。。,【n/2】取不大于该值的整数)

          

  若将和此序列对应的一维数组(以一维数组存储)看成是一个完全二叉树,则堆的含义表明,

完全二叉树中所有非终端结点的值均不大于(或不小于)其左、右孩子的结点值。

可以推出:堆顶元素一定是序列中最大(最小)的值。

 


 

堆如何排序?

以升序排列和大顶堆为例,

(1)根据堆的性质,堆中最大的元素在堆顶。那么将无序堆中的堆顶元素与堆的最后一个元素交换,原先无序堆中的堆顶交换到无序集合最后一个位置上,该元素归为有序集合。

  (无序堆少一个元素,有序集合多一个元素)

(2)交换元素后,无序新堆(比之前少一个元素)重新按堆的性质构建新堆。调整完成后,其最大的元素就排在了新堆的堆顶了。

(3)重复(1)(2)。直到未排序的堆中没有元素。最后产生有序集合。 

 

 其基本思想为(大顶堆):

    1)将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆,此堆为初始的无须区;

    2)将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),且满足R[1,2...n-1]<=R[n]; 

    3)由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最 后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排 序过程完成。

 


 

如何构建堆:

  为了能够减少选择排序中重复比较次数,就需要构建一个这样的堆,堆的结构就能够决定堆中元素大小关系,

省去了反复比较的次数。

  • 所以构建堆是一个重要的过程。(a)
  • 移除堆顶后,重新构造新堆也是一个重要的操作。(b)

所以 ab 是堆排序的基本操作。

  多思考一下,可以看到a是构建堆的过程,其实质是将无序的堆调整为满足堆性质的过程,与b过程相同。所以ab可以合并为同一个过程。

 

构建堆(调整堆):

 

原始图像

 

 

一维数组是真实的存储结构

完全二叉树结构则是我们构想的结构。

 

  • 调整堆过程

 

 

最后我们得到调整好的堆叫初始化堆。之后我们基于这个基础来调整

 

  • 堆排序过程:

 

 

 三条图像分别显示了最大三个数出堆过程。后面过程以此类推。

基本步骤就是:1.交换——2.入有序队列——3.调整新堆

 

代码:

 

  1 #include "stdafx.h"
  2 
  3 
  4 typedef int myDataType;
  5 myDataType src_ary[10] = {9,1,5,8,3,7,6,0,2,4};
  6 
  7 void prt_ary(myDataType *ary,int len)
  8 {
  9     int i=0;
 10     while(i < len)
 11     {
 12         printf(" %d ",ary[i++]);
 13     }
 14     printf("\n");
 15 }
 16 
 17 void prev_process_ary(myDataType *ary,int len)
 18 {
 19     
 20     int i;
 21     len = len+1;
 22     for (i=len-1;i>=0;i--)
 23     {
 24         ary[i] = ary[i-1];
 25     }
 26     ary[0]=0;
 27 }
 28 void retn_process_ary(myDataType *ary,int len)
 29 {
 30     int i;
 31     for (i=0;i<len;i++)
 32     {
 33         ary[i] = ary[i+1];
 34     }
 35 }
 36 
 37 //堆调整——对于堆的调整,我们需要知道参数 : 
 38 //1.调整子树的根结点
 39 //2.树的最后一个叶子节点
 40 void HeapAdjust(myDataType *ary,int Idx,int eIdx)
 41 {
 42     int lchdIdx = 2*Idx;        //左孩子结点的序号
 43     int rchdIdx = lchdIdx+1;    //右孩子结点的序号
 44     
 45 
 46     int i;//i是指向左或者右孩子的序号
 47 
 48     int crntVal = ary[Idx];
 49 
 50     //从将要调整点的孩子孩子节点开始,每次沿着被调整的孩子子树调整
 51     for (i = lchdIdx;i <= eIdx ; i = i*2 )
 52     {
 53         //判断 当前指向点 的左孩子和右孩子大小,取值大的
 54         if (i < eIdx && ary[lchdIdx] < ary[rchdIdx])
 55         {
 56             i=rchdIdx;//如果是右孩子大,则孩子下标为右孩子
 57         }
 58         //如果要调整的子树的根结点大于左右孩子,则不用调整
 59         if (crntVal > ary[i])
 60         {
 61             break;
 62         }
 63 
 64         ary[Idx] = ary[i];
 65 
 66         Idx = i;
 67         lchdIdx = 2*Idx;
 68         rchdIdx = lchdIdx+1;
 69     }
 70     //Idx 为 最后调整到的结点序号,把调整的值 调整到这个序号结点
 71     ary[Idx] = crntVal;
 72 }
 73 
 74 void HeapSort(myDataType *ary,int len)
 75 {
 76     int i;
 77     int endIdx = len+1;
 78     for (i=1 ; i < endIdx ; i++)
 79     {
 80         int temp = ary[1];
 81         ary[1] = ary[endIdx-i];
 82         ary[endIdx-i] = temp;
 83 
 84         HeapAdjust(ary,1,endIdx-i-1);
 85     }
 86 }
 87 
 88 void heapSort(myDataType *ary,int len)
 89 {
 90     int i;
 91 //    printf("prev_process_array:\n");
 92     prev_process_ary(ary,len);//调整数组,全部右移。这里做实验用,如果是对于很多数组,这样的操作很浪费时间,
 93                                 //就要对HeapSort的序号改进下。避免这样的移位。
 94 //    prt_ary(ary,len+1);
 95 
 96     //建立一个大顶堆
 97     for (i= len/2;i>0;i--)
 98     {
 99         HeapAdjust(ary,i,len);
100     }
101     //真正的堆排序
102     HeapSort(ary,len);
103 
104 //    printf("retn_process_array\n");
105     retn_process_ary(ary,len);
106 //    prt_ary(ary,len);
107 
108 }
109 
110 int _tmain(int argc, _TCHAR* argv[])
111 {
112     printf("before sort:\n");
113     prt_ary(src_ary,10);
114 
115     heapSort(src_ary,10);
116     
117     printf("after sort:\n");
118     prt_ary(src_ary,10);
119 
120 
121 
122     getchar();
123     return 0;
124 }


测试结果:

 

补充:对于调整还可以使用 递归的方法,可能更容易理解。

每次对一个点进行调整时候,如果它比孩子小,则和孩子交换,这个时候我们会出现新的子树(以交换的孩子为根),

继续对这样的子树进行新的调整。这就构成了一个递归调用。

把之前的HeapAdjust函数修改一下就可以了

 1 void HeapAdjust_R(myDataType *ary,int Idx,int eIdx)
 2 {
 3     int lchdIdx = 2*Idx;        //左孩子结点的序号
 4     int rchdIdx = lchdIdx+1;    //右孩子结点的序号
 5 
 6     if (lchdIdx > eIdx)
 7     {
 8         return ;
 9     }
10     int crntVal = ary[Idx];
11 
12     int i = lchdIdx;//i是指向左或者右孩子的序号
13 
14     if (rchdIdx <= eIdx && ary[rchdIdx] > ary[lchdIdx])
15     {
16         i = rchdIdx;
17     }
18     if (crntVal > ary[i])
19     {
20         return ;
21     }
22     ary[Idx] = ary[i];
23     ary[i] = crntVal;
24     HeapAdjust_R(ary,i,eIdx); 
25 }

 

 

 

 

 

转载于:https://www.cnblogs.com/jsgnadsj/p/3458050.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值