作为一名初学者,算法比较烂,但是为了避免遗忘,因此,特意在此开通博客,记录自己的学习过程,经常回头看看,增加自己的理解。
文章知识引用:算法导论
1.1 堆
(二叉)堆数据结构是一种数组对象,它可以被视为一棵完全二叉树。树中的每个结点和数组中存放该结点值的那个元素对应。树中的每一层都是填满的,最后一层可能除外(最后一层从一个结点的左子树开始填)。
表示堆的数组A是一个具有两个属性的对象:length[A]是数组中的元素的个数,heap_size[A]是存放在A中的堆的元素的个数。也就是说,虽然A[1...length[A]]中都可以包含有效值,但A[heap_size[A]]之后的元素都不属于相应的堆,此处heap_size[A] <= length[A]。请注意此处所讲的,树的根为A[1],也可以说,数组A下标从1开始的。
因此,给定了某个结点的下标i,其父结点Parent(i)、左儿子Left(i)和右儿子Right(i)的下标可以简单地计算出来,如下所示。
Parent(i)
return 「i/2」;//此处表示取下限
Left(i)
return 2*i;
Right(i)
return 2*i + 1;
说明:在大多数计算机中,Left过程可以在一条指令内计算出2*i,方法是将i的二进制表示左移1位;类似的,Right过程可以通过将i的二进制表示左移1位并在低位中加1,快速计算出2*i + 1;Parent过程也可以通过将把i右移一位得到「i/2」。
因此,在一个好的堆排序算法实现中,这三个过程通常是用“宏”过程或者是“内联”过程实现的。
二叉树有两种:最大堆和最小堆。在这两种堆中,结点内的数值都要满足堆特性。
在最大堆中,最大堆的特性是指除了根以外的每个结点i,有A[Parent(i)]>= A[i],即某个结点的值至多和其父结点的值一样大。这样,堆中的最大元素就存放在根结点中。
在最小堆中,最小堆的组织方式正好相反,最小堆特性是指除了根以外的每个结点,有A[Parent(i)]<= A[i]。最小堆的最小元素是在根部。
在堆排序算法中,我们使用的是最大堆。最小堆通常在构造优先队列时使用。
堆可以被看成一棵树,结点在堆中的高度定义为从本结点到叶子的最长简单下降路径上边的数目;定义堆的高度为树根的高度。因为具有n个元素的堆是基于一棵完全的二叉树的,因而其高度为O(lgn)。我们将看到,堆结构上的一些基本操作的运行时间至多与树的高度成正比,为O(lgn)。
下面是堆操作的一些基本过程:
a. Max_Heapify过程,其运行时间为O(lgn),是保持最大堆性质的关键;
b. Build_Max_Heap过程,以线性时间运行,可以在无序的输入数组基础上构造出最大堆;
c. HeapSort过程,运行时间为O(nlgn),对一个数组原地的进行排序;
d. Max_Heap_Insert、Heap_Extract_Max、Heap_Increase_Key和Heap_Maximum过程的运行时间都是O(lgn),可以让堆作为优先队列使用。
2、保持堆的性质
Max_Heapify是对最大堆进程操作的重要子程序。其输入为一个数组A和下标i。当该操作被调用时,我们假定以Left(i)和Right(i)为根的两棵二叉树都是最大堆,但这时A[i]可能小于其子女,这样违反了最大堆的性质。Max_Heapify让A[i]在最大堆中“下降”,使i为根的子树成为最大堆。
1 Max_Heapify(A,i)
2 l <- Left(i)
3 r <- Right(i)
4 if(l <= heap_size[A] && A[l] >= A[i])
5 then largest <- l
6 else largest <- i
7 if(r <= heap_size[A] && A[r] >= A[largest])
8 then largest <- r
9 if(largest != i)
10 then exchange A[i] <-> A[largest]
11 Max_Heapify(A, largest)
在算法的每一步中,从元素A[i]、A[Left(i)]、A[Right(i)]中找出最大的,并将其下标存在largest中。如果A[i]是最大的,则以i为根的子树已经是最大堆,程序结束。否则,i的某个子结点中有最大元素,则交换A[i]和A[largest],从而使i及其子女满足最大堆的性质。下标为largest的结点在交换后的值为A[i],以该结点为根的子树又有可能违反最大堆的性质。因而要对该子树递归的调用Max_heapify操作。
当Max_Heapify作用在一棵以i为根的、大小为n的子树上时,其运行时间为调整元素A[i]、A[Left(i)]、A[Right(i)]d的关系时所用时间为O(1),再加上对以i的某个子结点为根的子树递归调用Max_Heapify所需的时间。i结点的子树大小至多为2n/3(最坏的情况发生在最底层恰好半满的时候,此处对于自己有待理解???)。那么Max_Heapify的运行时间可由下描述:
T(n) <= T(2n/3) + O(1)
因此,Max_Heapify作用于一个高度为h的节点所需要的运行时间为O(h)。
3、建堆
我们可以自底向上的用Max_Heapify来将一个数组A[1....n]变成一个最大堆。过程Build_Max_Heap对树中的每一个其他结点都调用一次Max_Heapify。
1 Build_Max_Heap
2 heap_size[A] <- length[A]
3 for i <- length[A]/2 downto 1
4 do Max_Heapify(A, i)
我们可以这样来计算Build_Max_Heap运行时间的一个简单上届:每次调用Max_Heapify的时间为O(lgn),共有O(n)次调用,故运行时间为O(nlgn)。这个界虽然是对的,但从渐近意义上讲不够准确。
这有个画图例子http://www.cnblogs.com/dolphin0520/archive/2011/10/06/2199741.html,感谢作者海子。
4、堆排序算法
开始时,堆排序算法先用Build_Max_Heap将输入数组A[1....n](此处n = length[A])构造成一个最大堆。因为数组中最大元素在根A[1],则可以通过把它与A[n]互换来达到最终正确的位置。现在,如果从堆中”去掉“结点n(通过减小heap_size[A]),可以很容易地将A[1....n]建成最大堆。原来根的子女仍是最大堆,而新的根元素可能违背了最大堆的性质,这时调用Max_Heapify(A, 1)就可以保持这一性质,在A[1...n-1]中构造出最大堆。堆排序算法不断的重复这个过程,堆的大小由n - 1一直降到2。
1 Heap_Sort(A)
2 Build_Max_Heap(A)
3 for i <- length[A] downto 2
4 do exchange A[1] <-> A[i]
5 heap_size[A] <- heap_size[A] -1
6 Max_Heapify(A, 1)
Heap_sort过程的时间代价是O(nlgn),其中Build_Max_Heap的时间为O(n)(之所以不是O(nlgn),是因为O(n)是更加接近的上界),n-1次Max_Heapify调用中每一次的时间代价为O(lgn)。所以,O(n) + (n-1)*O(lgn) = O(nlgn)。
5、最大堆排序算法的c语言实现
1 #include <stdio.h>
2 #include <stdlib.h>
3
4
5 /*函数名称:Swap
6 *函数功能:交换两个数
7 *函数参数:*a - 数a
8 *b - 数b
9 */
10 void Swap(int *a, int *b)
11 {
12 *a = *a ^ *b;
13 *b = *a ^ *b;
14 *a = *a ^ *b;
15 }
16
17 /*函数名称:Max_Heapify
18 *函数功能:保持最大堆性质的函数
19 *函数参数:array[]-数组
20 i - 某一元素下标
21 length - 数组的长度
22 */
23 void Max_Heapify(int array[], int i, int length)
24 {
25 int left = 2*i; //左孩子
26 int right = 2*i + 1; //右孩子
27 int largest = 0;
28
29 if(left <= length && array[left] >= array[i])
30 {
31 largest = left;
32 }
33 else
34 {
35 largest = i;
36 }
37 if(right <= length && array[right] >= array[largest])
38 {
39 largest = right;
40 }
41 if(largest != i)
42 {
43 Swap(&array[i], &array[largest]);
44 Max_Heapify(array, largest, length);
45 }
46 }
47
48 /*函数名称:Build_Max_Heap
49 *函数功能:建立最大堆
50 *函数参数:array - 数组
51 length - 数组长度
52 */
53 void Build_Max_Heap(int array[], int length)
54 {
55 int index = 0;
56 for(index = length/2; index >= 1; index--)
57 {
58 Max_Heapify(array, index, length);
59 }
60 }
61 /*函数名称:Max_Heap_Sort
62 *函数功能:进行最大堆排序
63 *函数参数:array - 数组
64 length - 数组长度
65 */
66 void Heap_Sort(int array[], int length)
67 {
68 int i = 0;
69 //先建立最大堆
70 Build_Max_Heap(array, length);
71
72 for(i = length; i >= 1; i--)
73 {
74 //先交换A[0]和A[i]
75 int temp = array[1];
76 array[1] = array[i];
77 array[i] = temp;
78 Max_Heapify(array, 1, i - 1);
79 }
80 }
81
82 int main(int argc, char **argv)
83 {
84 //下面仅供测试用
85 int a[100];
86 int size;
87 printf("输入数组的个数:");
88 scanf("%d",&size);
89
90 int i;
91 for(i = 1; i <= size; i++)
92 {
93 printf("请输入元素 %d :",i);
94 scanf("%d",&a[i]);
95 }
96 Heap_Sort(a,size);
97 printf("最大堆排序");
98 for(i = 1; i <= size; i++)
99 {
100 printf("%d ",a[i]);
101 }
102 printf("\n");
103 return 0;
104 }
测试结果如下图所示:
通过这个过程,我想如果弄明白了算法的思想和实现过程,那么,再加上自己的细心和耐心,写出代码来是不成问题的。
加油!!!!
坚持!!!!
梦醒潇湘
2012-09-11