堆排序是排序中的经典算法,重要程度不言而喻。

  它是一种树形选择排序算法,其特点是:在排序过程中,把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 }