Heap & Heap Sort

 

1.堆及其性质

  1.1 定义 

        其实是一棵有着特殊性质的完全二叉树,所以而可以成为二叉堆

      这里的特殊性质是指:

        1、父节点的键值总是不大于(或不小于)其左右孩子的值;

        2、每个节点的左右子树都是一个二叉堆。」」

  1.2 分类

      父节点的键值不小于其左右孩子的键值的堆为大顶堆

      父节点的键值不大于其左右孩子的键值的堆为小顶堆

  1.3 性质

      我们一般用数组构建堆,并且从数组的a[0]用来存放堆的大小,从a[1]开始存放节点,以下除特殊说明外默认都是这样
    1. 含有n个节点的堆的高度为 ⌊lgn⌋
    2. a[1]根节点
    3. 叶节点为a[(n/2)+1] ~ a[n]
    4. 最大的非叶结点为a[n/2]
    5. 对于除根节点外的所有节点a[i]的父节点为a[i/2]
    6. 对于除叶节点外的所有节点a[i]的左孩子节点为a[2i],右节点(如果有)为a[2i+1]

 

2.堆的基本操作

  2.1 插入元素

    将一个新的元素插入到堆中时,首先先将待插入元素放在数组最后

    由于从这个新数据的父结点到根结点必然为一个有序的序列,现在的任务是将这个新数据插入到这个有序序列中——这就类似于直接插入排序中将一个数据并入到有序区间中

  

  2.2 删除元素

    按定义,堆中每次都只能删除堆顶元素。为了便于重建堆,实际的操作是将最后一个数据的值赋给根结点,然后再从根结点开始进行一次从上向下的调整。

    调整时先在左右儿子结点中找最大的,如果父结点比这个最大的子结点还大说明不需要调整了,反之将父结点和它交换后再考虑后面的结点。相当于从根结点将一个数据的“下沉”过程

  

  2.3 建堆

    将无序数组建成一个大顶堆的过程就是一个反复调整的过程。

    若将此数组看成是一个完全二叉树,则最后一个非终端结点是第n/2个结点,由此调整过程只需从该结点开始,直到堆顶元素。

  2.4 代码实现

  1 // Author : Jincheng
  2 // Date : 170319
  3 // Description : Insert,Delete,Create
  4 // Complexity : Time 0(nlgn) Space O(1)
  5 
  6 #include <iostream>
  7 using  namespace std;
  8 
  9 
 10 template <typename T>
 11 void Print(T *a,int size);
 12 
 13 // 调整堆,使以a[bottom]为“叶节点”的“最大树”成为大顶堆
 14 template <typename T>
 15 void HeapAdjustup(T *a,int bottom)
 16 {
 17     T temp = a[bottom]; // 保存数据
 18     int parent = bottom/2; // 用来访问父节点
 19 
 20     while(parent >= 1)
 21     {
 22         if(temp <= a[parent]) // 如果待插入节点小于当前节点的父节点,那么构成最大堆,无需继续调整
 23             
 24             break;
 25 
 26         a[bottom] = a[parent]; // 将父节点下移,替换当前节点
 27         bottom = parent;
 28         parent = bottom/2;
 29     }
 30     a[bottom] = temp; 
 31 }
 32 
 33 
 34 template <typename T>
 35 void HeapInsert(T *a,T data,int size)
 36 {
 37     a[size+1] = data;
 38     HeapAdjustup(a,size+1);
 39 }
 40 
 41 // 已知a[top]的左右子树均为大顶堆
 42 // 调整堆,使以a[top]为根节点的树使其成为大顶堆
 43 template <typename T>
 44 void HeapAdjustdown(T *a,int top,int size)
 45 {
 46     T temp = a[top];
 47     int child = top*2;
 48     while(child <= size)
 49     {
 50         if(child+1 <= size && a[child+1]>a[child])
 51         {
 52             child++;
 53         }
 54         if(temp >= a[child])
 55         {
 56             break;
 57         }
 58         a[top] = a[child];
 59         top = child;
 60         child = top*2;
 61     }
 62     a[top] = temp;
 63 }
 64 
 65 template <typename T>
 66 void HeapDelete(T *a,int size)
 67 {
 68     a[1] = a[size];
 69     HeapAdjustdown(a,1,size-1);
 70 }
 71 
 72 template <typename T>
 73 void CreatHeap(T *a,int size)
 74 {
 75     // 将原数组构建成为最大推
 76     // 从第一个非叶节点开始调整
 77     for(int i = size/2;i >= 1;i--)
 78     {
 79         HeapAdjustdown(a,i,size);
 80     }
 81 }
 82 int main()
 83 {
 84     int a[7] = {5,2,5,1,6,3};
 85     Print(a,5);
 86     CreatHeap(a,5);
 87     Print(a,5);
 88     HeapInsert(a,10,5);
 89     Print(a,6);
 90     HeapDelete(a,6);
 91     Print(a,5);
 92     return 0;
 93 }
 94 
 95 template <typename T>
 96 void Print(T *a,int size)
 97 {
 98     for(int i=1;i <= size;i++)
 99         
100         cout << "a[" << i << "] = " << a[i] << endl;
101 }

 

 

3.堆排序

  3.1 算法思想 

堆排序本质是一种选择排序,是一种树形选择排序
  在直接选择排序中,为了从R[1...n]中选择最大记录,需比较n-1次,从R[1...n-2]中选择最大记录需比较n-2次
  事实上这n-2次比较中有很多已经在前面的n-1次比较中已经做过,而树形选择排序恰好利用树形的特点保存了部分前面的比较结果,因此可以减少比较次数

堆排序的基本思想为:

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)。
4)不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成

    操作过程如下: 

1)初始化堆:将R[1..n]构造为堆;
2)将当前无序区的堆顶元素R[1]同该区间的最后一个记录交换,然后将新的无序区调整为新的堆。

因此对于堆排序,最重要的两个操作就是构造初始堆和调整堆,其实构造初始堆事实上也是调整堆的过程,只不过构造初始堆是对所有的非叶节点都进行调整。

 

  3.2 算法实现

     3.2.1 非递归版本的实现

 1 // Author : Jincheng
 2 // Date : 170318 
 3 // Description : HeapSort  non—recrusive method
 4 // Complexity : Time 0(nlgn) Space O(1)
 5 
 6 #include <iostream>
 7 using  namespace std;
 8 
 9 template <typename T>
10 void Swap(T *a,T *b);
11 
12 template <typename T>
13 void Print(T *a,int size);
14 
15 // 调整以start的为根节点的子树使其成为最大堆
16 template <typename T>
17 void HeapAdjustdown(T *a,int start,int end)
18 {
19     T temp = a[start]; // 保存待调整节点
20     int j = 2*start; // 用来访问孩子节点
21     while(j <= end)
22     {
23         if(a[j] < a[j+1] && j+1 <= end) // 取左右孩子节点中较大的一个
24         {
25             j++;
26         }
27         if(temp >= a[j]) // 如果大于当前父节点的孩子节点,那么构成最大堆,无需继续调整
28             
29             break;
30 
31         a[start] = a[j]; // 将较大的孩子节点上移,替换父节点
32         start = j;
33         j = j*2;
34     }
35     a[start] = temp; 
36 }
37 
38 template <typename T>
39 void HeapSort(T *a,int size)
40 {
41     // 将原数组构建成为最大推
42     // 从第一个非叶节点开始调整
43     for(int i = size/2;i >= 1;i--)
44     {
45         HeapAdjustdown(a,i,size);
46     }
47     // 将根节点和最后一个元素交换,使最后一个节点成为最大值
48     // 交换后调整a[1,i-1],使其成为最大堆
49     for(int i = size;i > 1;i--)
50     {
51         Swap(&a[1],&a[i]);
52         HeapAdjustdown(a,1,i-1);
53     }
54         
55 }
56 int main()
57 {
58     int a[] = {5,2,5,1,6,3};
59     int size = sizeof(a)/sizeof(int) - 1;
60     Print(a,size);
61     HeapSort(a,size);
62     Print(a,size);
63     return 0;
64 }
65 
66 template <typename T>
67 void Print(T *a,int size)
68 {
69     for(int i=1;i <= size;i++)
70         
71         cout << "a[" << i << "] = " << a[i] << endl;
72 }
73 
74 template <typename T>
75 void Swap(T *a,T *b)
76 {
77     T temp = *a;
78     *a = *b;
79     *b = temp;
80 }

 

    3.2.2 递归版本的实现

 1 // Author : Jincheng
 2 // Date : 170318 
 3 // Description : HeapSort  non—recrusive method
 4 // Complexity : Time 0(nlgn) Space O(n)
 5 
 6 #include <iostream>
 7 using  namespace std;
 8 
 9 template <typename T>
10 void Swap(T *a,T *b);
11 
12 template <typename T>
13 void Print(T *a,int size);
14 
15 // 调整以start的为根节点的子树使其成为最大堆
16 template <typename T>
17 void HeapAdjust(T *a,int start,int end)
18 {
19     int j = 2*start; // 用来访问孩子节点
20 
21     if(j <= end)
22     {
23         if(a[j] < a[j+1] && j+1 <= end) // 取左右孩子节点中较大的一个
24         {
25             j++;
26         }
27         if(a[start] >= a[j]) // 如果大于当前父节点的孩子节点,那么构成最大堆,无需继续调整
28             
29             return;
30         
31         // 将待调整节点与较大的孩子节点交换
32         Swap(&a[start],&a[j]);
33 
34         // 递归的调整以j为根节点的子树
35         HeapAdjust(a,j,end);
36 
37     }
38 
39 }
40 
41 template <typename T>
42 void HeapSort(T *a,int size)
43 {
44     // 将原数组构建成为最大推
45     // 从第一个非叶节点开始调整
46     for(int i = size/2;i >= 1;i--)
47     {
48         HeapAdjust(a,i,size);
49     }
50     // 将根节点和最后一个元素交换,使最后一个节点成为最大值
51     // 交换后调整a[1,i-1],使其成为最大堆
52     for(int i = size;i > 1;i--)
53     {
54         Swap(&a[1],&a[i]);
55         HeapAdjust(a,1,i-1);
56     }
57         
58 }
59 int main()
60 {
61     int a[] = {5,2,5,1,6,3};
62     int size = sizeof(a)/sizeof(int) - 1;
63     Print(a,size);
64     HeapSort(a,size);
65     Print(a,size);
66     return 0;
67 }
68 
69 template <typename T>
70 void Print(T *a,int size)
71 {
72     for(int i=1;i <= size;i++)
73         
74         cout << "a[" << i << "] = " << a[i] << endl;
75 }
76 
77 template <typename T>
78 void Swap(T *a,T *b)
79 {
80     T temp = *a;
81     *a = *b;
82     *b = temp;
83 }

 

posted on 2017-03-18 23:03  咔叽娃  阅读(119)  评论(0)    收藏  举报

导航