学习《算法导论》第六章 堆排序 总结
来源:互联网 发布:暴雪降至 知乎 编辑:程序博客网 时间:2024/05/16 17:40
学习《算法导论》第六章 堆排序 总结
完全二叉树的概述
一颗完全二叉树:树的每一层都是填满的,最后一层可能除外(最后一层是从一个结点的左子树开始填的).
树的结点表示
对于完全二叉树,给定一个结点的下标i,其父结点PARENT(i),左儿子LEFT(i),右二子RIGHT(i)的下标可通过下面简单计算出来:
PARENT(i) return i / 2;LEFT(i) return 2 * i;RIGHT(i) return 2 * i + 1;
这三个过程通常是用宏或内联来实现的.
树的高度
结点的高度:该结点到叶子结点的最长简单下降路径上的边的数目.
树的高度:树的根结点到叶子结点的最长简单下降路径上的边的数目.
因此, 对于有n 个结点的完全二叉树的高度为: O(lg n)
堆
堆可以看作是一颗二叉树. 这样, 表示堆的数组A有两个属性:length[A]是数组A中的元素. heapsize[A]是存放在A中的堆的元素个数. 也就是说A[heapsize[A]]之后的元素都不属于这个堆. 堆有两种:最大堆和最小堆.
最大堆
最大堆特性是指除了根以外的每个结点i, 有A[PARENT(i)] >= A[i], 即每个结点的值至多和其父结点的值一样大. 这样, 堆中最大的元素就是根结点; 且在以某一个结点为根的子树中, 各结点的值都不大于该子树根结点的值.
最小堆
最小堆的元素组织方式正好相反; 它的特性是指除了根以外的每个结点i, 有A[PARENT(i)] =< A[i]. 最小堆的最小元素就是根结点.
堆的高度
堆的高度即树的高度, 其高度也是O(lgn)
堆排序
堆排序有两个优点:1. 堆排序的运行时间为O(nlg n), 比插入排序要快; 2. 堆排序是一种原地排序算法. 所谓原地排序是指在任何时候,数组中只有常数个元素存储在数组之外. 下面将从三个方面介绍堆排序(这里用最大堆来实现).
MAX-HEAPIFY过程(保持堆的性质)
这里就是保持最大堆的性质, 即每个结点的值都不能小于它的子女. 下面直接给出它的算法描述, 先只考虑单个结点i和它的左右孩子比较, 判断它们之间是否满足最大堆的性质. 该算法的输入有两个参数: 数组A和结点下标i.
MAX-HEAPIFY(A, i)1 l <--- LEFT(i)2 r <--- RIGHT(i)3 if l <= heapsize[A] and A[l] > A[i]4 then largest <--- l5 else largest <--- i 6 if r <= heapsize[A] and A[r] > A[i]7 then largest <--- r8 if largest != i 9 then exchange A[i] <-> A[largest]10 MAX-HEAPIFY(A, largest)
MAX-HEAPIFY作用在单个结点上时, 其运行时间为调整A[i], A[LEFT[i]], A[RIGHT[i]]的关系所用的时间O(1), 再加上对以i的某个结点为根的子树递归调用MAX-HEAPIFY所用的时间. MAX-HEAPIFY的运行时间为O(lg n).
BUILD-HEAP过程(建堆)
对于一个数组A[1…n], 我们知道子数组A[(n/2+1)…n]中的元素都是树中的叶子. 所以建堆过程只要对树中的其他结点都调用一次MAX-HEAPIFY过程. 具体算法如下:
BUILD-MAX-HEAP(A)1 heapsize[A] <--- length[A]2 for i <--- heapsize[A] / 2 downto 13 do MAX-HEAPIFY(A, i)
注意:算法中的步骤2, i是一直递减到1的. 也就是建堆是从下面往上面建的, 就和建房子类似. 它的运行时间为O(n)(具体见算法导论书).
直到这一步, 给定一个数组A, 已经可以建立数组A对应的堆. 那么如何对这个最大堆进行排序呢?我们直到最大堆的特点就是最大的元素是堆的根结点A[1]. 那么我们只要将根结点和最后一个结点互换, 来达到”正确的位置”. 现在, 如果将A[n]从堆中”去掉”, 可以发现A[1…n-1]破坏了堆的性质. 这时要调用3.1MAX-HEAPIFY过程将A[1…n-1]保持堆的性质, 然后最大元素又是根结点,将根结点和最后一个结点互换, 不断重复这个过程. 堆的大小也从n-1一直降到2. 这就是下面的堆排序算法.
HEAPSORT过程(堆排序)
下面直接给出它的算法和运行时间:
HEAPSORT(A)1 BUILD-MAX-HEAP(A)2 for i <--- length[A] downto 23 do exchange A[1] <-> A[i]4 heapsize--5 MAX-HEAPIFY(A, 1)
注意:每次都是根结点和最后一个结点互换. 所以算法第5步每次都是在根结点上开始保持堆的性质
时间代价为:O(lgn).
算法实现和程序代码
直接根据上面的算法就可以写程序验证了.
#include <stdio.h>#include <stdlib.h>// 待测试的数组的长度#define ARRAY_LENGTH 100// 用宏和位运算来提高运算速度#define LEFT(i) (i<<1)#define RIGHT(i) (2*(i)+1) // 这个还不知道怎么用位运算实现????????????????????????????????inline void swap(int* a, int* b){ int temp = *a; *a = *b; *b = temp; return;}// MAX-HEAPIFY过程(保持堆的性质)// 入参有:数组HeapArray、数组长度、以及结点下标ParentNodeIdvoid MaxHeapify (int* Heap, int HeapLength, int ParentNodeId){ int LeftNodeId = LEFT(ParentNodeId); int RightNodeId = RIGHT(ParentNodeId); // 用于标记父结点和子结点中值最大的结点下标 int LargestNodeId = ParentNodeId; if ((LeftNodeId <= HeapLength) && (Heap[LeftNodeId -1] > Heap[ParentNodeId - 1])) { LargestNodeId = LeftNodeId; } // 注意:RightNodeId的值是和LargestNodeId的值进行比较. 而不是ParentNodeId!!!! if ((RightNodeId <= HeapLength) && (Heap[RightNodeId - 1] > Heap[LargestNodeId - 1])) { LargestNodeId = RightNodeId; } // 当子结点的值比父结点的值大时, 互换他们的位置, // 互换后, 以largestnodeid为结点的根的子树又可能违反最大堆的性质 // 所以递归调用MAX-HEAPIFY过程 if (LargestNodeId != ParentNodeId) { swap(Heap + ParentNodeId - 1, Heap + LargestNodeId - 1); MaxHeapify(Heap, HeapLength, LargestNodeId); }}// BUILD-HEAP过程(建堆)// 入参为:数组HeapArray和数组长度ArrayLengthvoid BuildMaxHeap (int* HeapArray, int HeapLength){ int HeapSize = HeapLength; int LoopNodeId = 0; for (LoopNodeId = HeapSize / 2; LoopNodeId > 0; LoopNodeId--) { MaxHeapify (HeapArray, HeapSize, LoopNodeId); }}// HEAPSORT过程(堆排序)int HeapSort (int* Array, int ArrayLength){ // 合法性检查,这里要求数组至少为3, 否则排序无意义 if ((NULL == Array) || (ArrayLength < 3)) { return -1; } int LoopId = 0; int HeapSize = ArrayLength; BuildMaxHeap(Array, ArrayLength); for (LoopId = ArrayLength; LoopId > 1; LoopId--) { swap(Array, Array + LoopId - 1); HeapSize--; MaxHeapify (Array, HeapSize, 1); } return 0;}int main(){ int number[ARRAY_LENGTH] = {0}; for (int i = 0; i < ARRAY_LENGTH; i++) { number[i] = i + 1; } HeapSort (number, ARRAY_LENGTH); for (i = 0; i < ARRAY_LENGTH; i++) { printf ("The %d th value is %d.\n", i + 1, number[i]); } return 0;}
- 学习《算法导论》第六章 堆排序 总结
- 学习《算法导论》第六章 堆排序 总结二
- 算法导论第六章 堆排序总结
- 算法导论第六章总结:堆排序
- 算法导论: 第六章 堆排序算法
- 算法导论学习笔记-第六章-堆排序
- 《算法导论》学习笔记--第六章 堆排序
- 算法导论 学习笔记 第六章 堆排序
- 算法导论读书笔记 第六章 堆排序
- 算法导论第六章:堆排序
- 算法导论第六章 堆排序
- 算法导论 第六章 堆排序
- 【算法导论】第六章之堆排序
- 算法导论第六章 堆排序
- 堆排序(算法导论第六章)
- [算法导论]第六章《堆排序》
- 算法导论第六章堆排序思考题
- 算法导论第六章 堆排序
- autohotkey快捷键显示隐藏文件和文件扩展名
- UIday04_zy:容器视图控制器练习
- Dynamic Rankings(动态区间求第K小模板题:树套树(第二份模板效率更高,见下面截图))
- 题目:二叉树的锯齿形层次遍历
- 转:JSON字符串还原成JS原生值
- 学习《算法导论》第六章 堆排序 总结
- 使用SOCK_RAW构建全网广播包
- 题目:交叉字符串
- Poj.1113 Wall【凸包】 2015/08/29
- 面向对象的优缺点
- 题目:交错正负数
- 关于python文件操作
- ptmalloc,tcmalloc和jemalloc内存分配策略研究
- 请不要再用MongoDB了