算法学习记录-排序——堆排序
堆排序
问题提出:
前面写过 简单选择排序 讲过,要找出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 }