【算法导论】学习笔记——第6章 堆排序
2014-08-19 22:42
375 查看
堆这个数据结构应用非常广泛,数字图像处理的算法里也见过。似乎记得以前老师上课说需要用树结构实现堆排序,看了一下算法导论才明白其精髓。堆虽然是一棵树,但显然没必要非得用树结构实现堆排序。堆排序的性质很好,算法时间复杂度为O(nlgn)。
1. 堆排序的简要说明。
二叉堆可以分为两种形式:最大堆和最小堆。
在最大堆中,最大堆性质是指除了根以外的所有结点i都要满足:
A[PARENT(i)] >= A[i];
在最小堆中,最小堆性质是指除了根以外的所有结点i都要满足:
A[PARENT(i)] <= A[i]。
关于堆排序的算法实现,有如下基本过程需要了解:
(1)MAX_HEAPIFY过程:其时间复杂度为O(lgn),它是维护最大堆性质的关键;
(2)BUILD_MAX_HEAP过程:具有线性时间复杂度,功能是从无序的输入数据数组中构造一个最大堆;
(3)HEAPSORT过程,其时间复杂度为O(nlgn),功能是对一个数组进行原址排序;
(4)MAX_HEAP_INSERT、MAX_EXTRACT_MAX、HEAP_INCREASE_KEY和HEAP_MAXIMUM过程:时间复杂度为 O(lgn),功能是利用堆实现一个优先队列。
堆结构定义如下:
2. 维护堆的性质。
MAX_HEAPIFY是维护最大堆性质的重要过程。代码实现如下:
也可采用非递归形式实现,代码如下:
3. 建堆
BUILD_MAX_HEAPIFY描述了建堆的主要过程,建堆时仅需针对非叶子结点调用MAX_HEAPIFY过程。建堆的时间复杂度为O(n)。
有关6.3-3的证明,对于任何包含n个元素的堆中,至多有ceil(n/2^(h+1))个高度为h的点。
证明:
(1)先证叶子结点(高度h=0)满足该结论,即至多有ceil(n/2^1)个叶子结点。令叶子结点的下标为i,i属于[1,n],
则2*i>n,i<=n,即n/2 < i <= n。因为i为整数,因此floor(n/2) < i <= n,所以N(h=0) <= floor(n) - floor(floor(n/2)) (《具体数学》3.12),因此N(h=0) <= n - floor(n/2),又因为floor(n/2)+ceil(n/2) = n,所以N(h=0) <= ceil(n/2),余下点的数目n - N(h=0) >= floor(n/2);
(2)假设高度为k-1的子树均满足上述结论,采用数学归纳法证明高度为k的子树满足上述结论。
因此,易知k<=H(H为树的高度,H=floor(lgn))。假设,高度为k的点下标为i。因为N(h=0)时,N(h=0)=ceil(n/2);将原叶子结点全部去掉,子数的高度势必均减1,因此N(h=k) = N(hh = k-1) <= ceil(nn/2^h),nn = n - ceil(n/2) = floor(n/2),因此N(h=k) = <= ceil(floor(n/2)/2^h),因为floor(n/2) <= n/2,因此N(h=k) <= ceil(n/2/2^h),所以N(h = k) <= ceil(n/2^(h+1))。
4. 堆排序算法。
初始时,堆排序算法利用BUILD_MAX_HEAPIFY过程建立初始的最大堆,此时,最大元素一定处在根结点,交换根结点与A
,并重新建立堆(此时堆大小需要减1),我们即将最大元素置于数组最末处进行孤立。不断重复此过程,直至仅有两个元素的堆为止,即可得到有序的数组。代码实现如下:
5. 优先队列。
优先队列是堆这种数据结构的典型应用。优先队列是一种用来维护由一组元素构成的集合S的数据结构,每个元素都有一个相关的值,称为关键字,一个优先队列支持如下操作:
(1)INSERT(S, x):把元素x插入S中;
(2)MAXIMUM(S):返回S中具有最大关键字的元素;
(3)EXTRACT_MAX(S):去掉并返回S中的具有最大关键字的元素;
(4)INCREASE(S, x, k):将元素x的关键字值增加到k,假设k的值不小于x的原始关键字值。
代码实现:
6.5-6问题解答,即简单优化Heap_Increase_Key函数,代码实现如下:
1. 堆排序的简要说明。
二叉堆可以分为两种形式:最大堆和最小堆。
在最大堆中,最大堆性质是指除了根以外的所有结点i都要满足:
A[PARENT(i)] >= A[i];
在最小堆中,最小堆性质是指除了根以外的所有结点i都要满足:
A[PARENT(i)] <= A[i]。
关于堆排序的算法实现,有如下基本过程需要了解:
(1)MAX_HEAPIFY过程:其时间复杂度为O(lgn),它是维护最大堆性质的关键;
(2)BUILD_MAX_HEAP过程:具有线性时间复杂度,功能是从无序的输入数据数组中构造一个最大堆;
(3)HEAPSORT过程,其时间复杂度为O(nlgn),功能是对一个数组进行原址排序;
(4)MAX_HEAP_INSERT、MAX_EXTRACT_MAX、HEAP_INCREASE_KEY和HEAP_MAXIMUM过程:时间复杂度为 O(lgn),功能是利用堆实现一个优先队列。
堆结构定义如下:
#define PARENT(i) (i>>1) #define LEFT(i) (i<<1) #define RIGHT(i) (i<<1|1) #define MAXN 105 typedef struct { int buf[MAXN]; int length; int size; } Heap_t;
2. 维护堆的性质。
MAX_HEAPIFY是维护最大堆性质的重要过程。代码实现如下:
void MAX_Heapify(Heap_t *A, int i) { int l = LEFT(i); int r = RIGHT(i); int largest; int tmp; if (l<=A->size && A->buf[l]>A->buf[i]) largest = l; else largest = i; if (r<=A->size && A->buf[r]>A->buf[largest]) largest = r; if (largest != i) { tmp = A->buf[i]; A->buf[i] = A->buf[largest]; A->buf[largest] = tmp; MAX_Heapify(A, largest); } }
也可采用非递归形式实现,代码如下:
void MAX_Heapify(Heap_t *A, int i) { int l, r; int largest; int tmp; while (i+i <= A->size) { l = LEFT(i); r = RIGHT(i); if (l<=A->size && A->buf[l]>A->buf[i]) largest = l; else largest = i; if (r<=A->size && A->buf[r]>A->buf[largest]) largest = r; if (largest != i) { tmp = A->buf[i]; A->buf[i] = A->buf[largest]; A->buf[largest] = tmp; i = largest; } else { break; } } }
3. 建堆
BUILD_MAX_HEAPIFY描述了建堆的主要过程,建堆时仅需针对非叶子结点调用MAX_HEAPIFY过程。建堆的时间复杂度为O(n)。
void Build_MAX_Heap(Heap_t *A) { int i; A->size = A->length; for (i=A->length>>1; i>=1; --i) { MAX_Heapify(A, i); } }
有关6.3-3的证明,对于任何包含n个元素的堆中,至多有ceil(n/2^(h+1))个高度为h的点。
证明:
(1)先证叶子结点(高度h=0)满足该结论,即至多有ceil(n/2^1)个叶子结点。令叶子结点的下标为i,i属于[1,n],
则2*i>n,i<=n,即n/2 < i <= n。因为i为整数,因此floor(n/2) < i <= n,所以N(h=0) <= floor(n) - floor(floor(n/2)) (《具体数学》3.12),因此N(h=0) <= n - floor(n/2),又因为floor(n/2)+ceil(n/2) = n,所以N(h=0) <= ceil(n/2),余下点的数目n - N(h=0) >= floor(n/2);
(2)假设高度为k-1的子树均满足上述结论,采用数学归纳法证明高度为k的子树满足上述结论。
因此,易知k<=H(H为树的高度,H=floor(lgn))。假设,高度为k的点下标为i。因为N(h=0)时,N(h=0)=ceil(n/2);将原叶子结点全部去掉,子数的高度势必均减1,因此N(h=k) = N(hh = k-1) <= ceil(nn/2^h),nn = n - ceil(n/2) = floor(n/2),因此N(h=k) = <= ceil(floor(n/2)/2^h),因为floor(n/2) <= n/2,因此N(h=k) <= ceil(n/2/2^h),所以N(h = k) <= ceil(n/2^(h+1))。
4. 堆排序算法。
初始时,堆排序算法利用BUILD_MAX_HEAPIFY过程建立初始的最大堆,此时,最大元素一定处在根结点,交换根结点与A
,并重新建立堆(此时堆大小需要减1),我们即将最大元素置于数组最末处进行孤立。不断重复此过程,直至仅有两个元素的堆为止,即可得到有序的数组。代码实现如下:
void HeapSort(Heap_t *A) { int i, tmp; Build_MAX_Heap(A); for (i=A->length; i>=2; --i) { tmp = A->buf[1]; A->buf[1] = A->buf[i]; A->buf[i] = tmp; --A->size; MAX_Heapify(A, 1); } }
5. 优先队列。
优先队列是堆这种数据结构的典型应用。优先队列是一种用来维护由一组元素构成的集合S的数据结构,每个元素都有一个相关的值,称为关键字,一个优先队列支持如下操作:
(1)INSERT(S, x):把元素x插入S中;
(2)MAXIMUM(S):返回S中具有最大关键字的元素;
(3)EXTRACT_MAX(S):去掉并返回S中的具有最大关键字的元素;
(4)INCREASE(S, x, k):将元素x的关键字值增加到k,假设k的值不小于x的原始关键字值。
代码实现:
int Heap_Maximum(Heap_t *A) { return A->buf[1]; } int Heap_Extract_MAX(Heap_t *A) { int max; if (A->size < 1) { printf("heap underflow.\n"); return -1; } max = A[1]; A[1] = A[A->size]; --A->size; MAX_Heapify(A, 1); return max; } void Heap_Increase_Key(Heap_t *A, int i, int key) { int tmp; if (key < A->buf[i]) { printf("new key is smaller than current key.\n"); return ; } A[i] = key; while (i>1 && A->buf[PARENT(i)]<A->buf[i]) { tmp = A->buf[i]; A->buf[i] = A->buf[PARENT(i)]; A->buf[PARENT(i)] = tmp; i = PARENT(i); } } void MAX_Heap_Insert(Heap_t *A, key) { ++A->size; A[A->size] = NINF; // NINF: negative infinite Heap_Increase_Key(A, A->size, key); }
6.5-6问题解答,即简单优化Heap_Increase_Key函数,代码实现如下:
void Heap_Increase_Key(Heap_t *A, int i, int key) { if (key < A->buf[i]) { printf("new key is smaller than current key.\n"); return ; } while (i>1 && A->buf[PARENT(i)]<key) { A->buf[i] = A->buf[PARENT(i)]; i = PARENT(i); } A[i] = key; }
相关文章推荐
- 算法导论学习笔记 第6章 堆排序
- 算法学习导论学习笔记-第6章 堆排序
- 算法学习笔记----第二部分:排序和顺序统计量----第6章、堆排序
- 算法学习笔记----第二部分:排序和顺序统计量----第6章、堆排序
- 算法导论学习笔记---第三章
- 算法导论学习笔记-第十六章-贪心算法
- 算法导论学习笔记——桶排序
- 算法导论学习笔记(六):计数排序与基数排序
- 算法导论学习笔记(11)——贪心算法之哈夫曼树
- 算法导论学习笔记-第十一章-散列表
- Introduction to Algorithms 算法导论 第3章 函数的增长 学习笔记及习题解答
- 算法导论学习笔记——基数排序
- Introduction to Algorithms 算法导论 第4章 递归式 学习笔记及习题解答
- 算法导论学习笔记——插入排序
- Introduction to Algorithms 算法导论 第2章 算法入门 学习笔记及习题解答
- 算法导论学习笔记-第2章 算法入门
- 算法导论学习笔记-第九章-中位数和顺序统计学
- 算法导论学习笔记(16)——图的基本算法
- 算法导论学习笔记(8)——动态规划之矩阵链乘法
- Introduction to Algorithms 算法导论 第1章 基础知识 学习笔记及习题解答