堆排序是排序中的经典算法,重要程度不言而喻。
它是一种树形选择排序算法,其特点是:在排序过程中,把L[1-n]视为一颗完全二叉树的顺序存储结构,利用完全二叉树中双亲节点和孩子节点之间的内在关系,在当前无序数中,选择一个最大或者最小的数。
当双亲节点大于它的两个子节点,称为大根堆;反之为小根堆。
算法描述,n个节点的完全二叉树,最后一个节点是第n/2-1个节点的孩子,对第n/2-1个节点为根的子树筛选,如果要建立大根堆,若父节点小于两个子节点中较大的一个,则交换。之后以次对第n/2-1个以前的节点进行筛选,如果父子节点发生交换 ,交换后可能会破环下一级,于是继续采用相同的方法,进行调整。
1 #include<stdlib.h> 2 #include<stdio.h> 3 #define N 10 4 #define SWAP(a, b) { int temp; temp = a; a = b; b = temp; } 5 #include<time.h> 6 void adjust_heap(int a[], int start, int len){ 7 int dad = start; //父节点 8 int son = 2 * dad + 1; //子节点 9 while (son<len) 10 { 11 if (son+1<len&&a[son+1]>a[son]) 12 { 13 son++; 14 } 15 if (a[son]>a[dad]) 16 { 17 SWAP(a[son], a[dad]); 18 dad = son; 19 son = 2 * dad + 1; 20 } 21 else 22 { 23 break; 24 } 25 } 26 } 27 void max_heap(int a[]){ 28 int i; 29 for ( i = N/2-1; i >0; i--) //对第N/2-1个节点调整 30 { 31 adjust_heap(a,i,N); 32 } 33 SWAP(a[0], a[N - 1]); //选择出的最大节点和尾部元素进行交换, 34 for ( i = N-1; i >0; i--) 35 { 36 adjust_heap(a, 0, i); //调整第0个元素,然后选择最大的元素 37 SWAP(a[0], a[i-1]); //和尾部元素交换 38 } 39 } 40 int main(){ 41 int a[N]; 42 srand(time(NULL)); 43 for (int i = 0; i < N; i++) 44 { 45 a[i] = rand() % 100; 46 } 47 max_heap(a); 48 system("pause"); 49 }
关于堆排的应用
1、n个元素中找出第K大。
K个元素建一个小根堆,堆顶元素即为第K大。
如果是N个元素(N>K),把前K个元素建一个小根堆,然后取出堆顶元素依次和第K+1、第K+2.....的元素进行比较,如果堆顶元素小于后面的元素则交换,重新调整小根堆。遍历到最后一个时,此时的堆顶元素是第K大。
1 #include<stdlib.h> 2 #include<stdio.h> 3 #define N 15 4 #define K 5 5 #define swap(a,b) {int temp;temp=a;a=b;b=temp;} 6 //k<n/2 7 void adjust(int *a, int start, int len){ 8 int dad = start; 9 int son = 2 * start + 1; 10 while (son<len) 11 { 12 if (son+1<len&&a[son]>a[son+1]) 13 { 14 son++; 15 } 16 if (a[son]<a[dad]) 17 { 18 swap(a[son], a[dad]); 19 dad = son; 20 son = 2 * dad + 1; 21 } 22 else 23 { 24 break; 25 } 26 } 27 28 } 29 void min_heap(int *a){ 30 for ( int i = K/2-1;i >=0;i--) 31 { 32 adjust(a, i, K); 33 } 34 for (int i = K; i < N; i++) 35 { 36 if (a[i]>a[0]) 37 { 38 a[0] = a[i]; 39 adjust(a, 0, K); 40 } 41 } 42 } 43 int main(){ 44 int arr[N] = { 20, 45, 13, 65, 47, 2, 7, 34, 78, 44, 34, 67, 83, 23, 10 }; 45 min_heap(arr); 46 printf("第5大的数为:%d\n", arr[0]); 47 system("pause"); 48 }
2、进阶,如果N个元素内存放不下,K个元素也放不下时。运用反向思维,那么N-K个元素是否放的下,如果放的下,则建立一个N-K个元素的大根堆,依次和剩下的元素进行比较。
3、再次进阶。如果N 、K、N-K个元素都放不下内存。此时无法建堆。
这时需要两个机器,每台机器都找第K大的数,建小根堆,把这k个数进行排序。于是A机器上有K个有序的数,B机器也有K个有序的数。这时退化成求两个升序序列中求中位数的问题。
算法描述:设a和b分别为两个序列的中位数。
1)如果a=b,则a或者b为两个序列的中位数;
2)如果a<b,序列元素个数为奇数,则舍弃A序列中中间点以前的部分且保留中间点,舍弃B序列中间点以后的部分且保留中间点;元素个数为偶数,舍弃A序列中中间点及中间点以前的部分,舍弃B序列中间点以后的部分且保留中间点;
3)如果a>b,序列元素个数为奇数,则舍弃B序列中中间点以前的部分并保留中间点,舍弃A序列中中间节点以后部分并保留中间点;元素个数为偶数,则舍弃B序列中中间点及中间节点以前的部分,舍弃A序列中中间点以后的部分并保留中间点。
1 #include<stdlib.h> 2 #include<stdio.h> 3 int M_Search(int A[], int B[], int n) 4 { 5 int s1 = 0; //序列首元素下表 6 int d1 = n - 1;//末元素下标 7 int s2 = 0; 8 int d2 = n - 1; 9 int m1, m2; //中位数下标 10 while (s1 != d1|| s2 != d2) 11 { 12 m1 = (s1 + d1) / 2; 13 m2 = (s2 + d2) / 2; 14 if (A[m1] == B[m2]) //第一种情况 15 { 16 return A[m1]; 17 } 18 if (A[m1] < B[m2]) //第二种情况 19 { 20 if ((s1 + d1) % 2 == 0) //奇数序列 21 { 22 s1 = m1; 23 d2 = m2; 24 } 25 else //偶数序列 26 { 27 s1 = m1 + 1; 28 d2 = m2; 29 } 30 } 31 else //第三种情况 32 { 33 if ((s1 + d1) % 2 == 0) 34 { 35 s2 = m1; 36 d1 = m1; 37 } 38 else 39 { 40 s2 = m2 + 1; 41 d1 = m1; 42 } 43 } 44 } 45 return A[s1] > B[s2] ? B[s2] : A[s1]; 46 } 47 int main() 48 { 49 int A[5] = { 2, 4, 5, 7, 9 }; //准备两个有序序列 50 int B[5] = { 1, 3, 5, 6, 8 }; 51 int ret; 52 ret=M_Search(A, B, 5); 53 system("pause"); 54 55 }
 
                    
                     
                    
                 
                    
                 
                
            
         
         
 浙公网安备 33010602011771号
浙公网安备 33010602011771号