一.堆(heap)
优先队列(Priority Queue):特殊的“队列”,取出元素的顺序是依照元素的优先权(关键字)大小,而不是元素进入队列的先后顺序。
数组 : 插入 — 元素总是插入尾部 ~ O ( 1 ) 删除 — 查找最大(或最小)关键字 ~ O ( n ) 从数组中删去需要移动元素 ~ O( n ) 链表: 插入 — 元素总是插入链表的头部 ~ O ( 1 ) 删除 — 查找最大(或最小)关键字 ~ O ( n ) 删去结点 ~ O( 1 ) 有序数组: 插入 — 找到合适的位置 ~ O( n ) 或 O(log2 n ) 移动元素并插入 ~ O( n ) 删除 — 删去最后一个元素 ~ O( 1 ) 有序链表: 插入 — 找到合适的位置 ~ O( n ) 插入元素 ~ O( 1 ) 删除 — 删除首元素或最后元素 ~ O( 1 ) 二叉树 删除会导致不平衡
若采用数组或链表实现优先队列
优先队列的完全二叉树表示
堆的两个特性
结构性:用数组表示的完全二叉树;
有序性:任一结点的关键字是其子树所有结点的最大值(或最小值)
“最大堆(MaxHeap)”,也称“大顶堆”:最大值
“最小堆(MinHeap)”,也称“小顶堆” :最小值
1.最大堆(代码:sj4_3)
1 //最大堆 2 #include <stdio.h> 3 #include <stdlib.h> 4 5 #define MAXDATA 1000 /* 该值应根据具体情况定义为大于堆中所有可能元素的值 */ 6 #define ERROR -1 /* 错误标识应根据具体情况定义为堆中不可能出现的元素值 */ 7 8 typedef int ElementType; 9 10 typedef struct HNode *Heap; /* 堆的类型定义 */ 11 struct HNode { 12 ElementType *Data; /* 存储元素的数组 */ 13 int Size; /* 堆中当前元素个数 */ 14 int Capacity; /* 堆的最大容量 */ 15 }; 16 typedef Heap MaxHeap; /* 最大堆 */ 17 18 MaxHeap CreateHeap( int MaxSize ); 19 bool IsFull( MaxHeap H ); 20 bool Insert( MaxHeap H, ElementType X ); 21 bool IsEmpty( MaxHeap H ); 22 ElementType DeleteMax( MaxHeap H ); 23 void PercDown( MaxHeap H, int p ); 24 void BuildHeap( MaxHeap H ); 25 26 int main() 27 { 28 MaxHeap Heap; 29 Heap = CreateHeap( 10 ); 30 BuildHeap(Heap); 31 Insert( Heap, 1); 32 Insert( Heap, 2); 33 Insert( Heap, 3); 34 ElementType Max = DeleteMax( Heap ); 35 printf("%d\n",Max); 36 return 0; 37 } 38 39 /* 创建容量为MaxSize的空的最大堆 */ 40 MaxHeap CreateHeap( int MaxSize ) 41 { 42 43 MaxHeap H = (MaxHeap)malloc(sizeof(struct HNode)); 44 H->Data = (ElementType *)malloc((MaxSize+1)*sizeof(ElementType)); 45 H->Size = 0; 46 H->Capacity = MaxSize; 47 H->Data[0] = MAXDATA; /* 定义"哨兵"为大于堆中所有可能元素的值*/ 48 49 return H; 50 } 51 52 bool IsFull( MaxHeap H ) 53 { 54 return (H->Size == H->Capacity); 55 } 56 57 /* 将元素X插入最大堆H,其中H->Data[0]已经定义为哨兵 */ 58 bool Insert( MaxHeap H, ElementType X ) 59 { 60 if ( IsFull(H) ) { 61 printf("最大堆已满"); 62 return false; 63 } 64 int i = ++H->Size; /* i指向插入后堆中的最后一个元素的位置 */ 65 for ( ; H->Data[i/2] < X; i /= 2 ) 66 H->Data[i] = H->Data[i/2]; /* 上滤X */ 67 H->Data[i] = X; /* 将X插入 */ 68 return true; 69 } 70 71 bool IsEmpty( MaxHeap H ) 72 { 73 return (H->Size == 0); 74 } 75 76 /* 从最大堆H中取出键值为最大的元素,并删除一个结点 */ 77 ElementType DeleteMax( MaxHeap H ) 78 { 79 int Parent, Child; 80 ElementType MaxItem, X; 81 82 if ( IsEmpty(H) ) { 83 printf("最大堆已为空"); 84 return ERROR; 85 } 86 87 MaxItem = H->Data[1]; /* 取出根结点存放的最大值 */ 88 /* 用最大堆中最后一个元素从根结点开始向上过滤下层结点 */ 89 X = H->Data[H->Size--]; /* 注意当前堆的规模要减小 */ 90 for( Parent = 1; Parent * 2 <= H->Size; Parent = Child ) { 91 Child = Parent * 2; 92 if( (Child != H->Size) && (H->Data[Child] < H->Data[Child+1]) ) 93 Child++; /* Child指向左右子结点的较大者 */ 94 if( X >= H->Data[Child] ) break; /* 找到了合适位置 */ 95 else /* 下滤X */ 96 H->Data[Parent] = H->Data[Child]; 97 } 98 H->Data[Parent] = X; 99 100 return MaxItem; 101 } 102 103 /*----------- 建造最大堆 -----------*/ 104 /* 下滤:将H中以H->Data[p]为根的子堆调整为最大堆 */ 105 void PercDown( MaxHeap H, int p ) 106 { 107 int Parent, Child; 108 ElementType X; 109 110 X = H->Data[p]; /* 取出根结点存放的值 */ 111 for( Parent = p; Parent*2 <= H->Size; Parent = Child ) { 112 Child = Parent * 2; 113 if( (Child != H->Size) && (H->Data[Child] < H->Data[Child+1]) ) 114 Child++; /* Child指向左右子结点的较大者 */ 115 if( X >= H->Data[Child] ) break; /* 找到了合适位置 */ 116 else /* 下滤X */ 117 H->Data[Parent] = H->Data[Child]; 118 } 119 H->Data[Parent] = X; 120 } 121 122 /* 调整H->Data[]中的元素,使满足最大堆的有序性 */ 123 /* 这里假设所有H->Size个元素已经存在H->Data[]中 */ 124 void BuildHeap( MaxHeap H ) 125 { 126 /* 从最后一个结点的父节点开始,到根结点1 */ 127 for(int i = H->Size/2; i > 0; i-- ) 128 PercDown( H, i ); 129 }
sj4_3
typedef struct HNode *Heap; /* 堆的类型定义 */
struct HNode {
ElementType *Data; /* 存储元素的数组 */
int Size; /* 堆中当前元素个数 */
int Capacity; /* 堆的最大容量 */
};
typedef Heap MaxHeap; /* 最大堆 */
1.创建最大堆
/* 创建容量为MaxSize的空的最大堆 */ MaxHeap CreateHeap( int MaxSize ) { MaxHeap H = (MaxHeap)malloc(sizeof(struct HNode)); H->Data = (ElementType *)malloc((MaxSize+1)*sizeof(ElementType)); H->Size = 0; H->Capacity = MaxSize; H->Data[0] = MAXDATA; /* 定义"哨兵"为大于堆中所有可能元素的值*/ return H; }
2.最大堆的插入
将新插入的元素放在数组的最后位置。
case1:新插入元素小于其parent。例如插入20,未破坏其有序性,插入成功。
case2:新插入元素大于其parent。例如插入35,破坏其结点是其子树所有结点的最大值,交换35与其parent31位置。
case3:新插入元素大于其parent的parent……。例如插入58,若其大于parent,交换,一直往上换。
1 /* 将元素X插入最大堆H,其中H->Data[0]已经定义为哨兵 */ 2 bool Insert( MaxHeap H, ElementType X ) 3 { 4 if ( IsFull(H) ) { 5 printf("最大堆已满"); 6 return false; 7 } 8 int i = ++H->Size; /* i指向插入后堆中的最后一个元素的位置 */ 9 for ( ; H->Data[i/2] < X; i /= 2 ) 10 H->Data[i] = H->Data[i/2]; /* 上滤X */ 11 H->Data[i] = X; /* 将X插入 */ 12 return true; 13 }
3.最大堆的删除
获取根(即最大值),删除最后的结点,放入根位置。取根最大的孩子与根交换,最大孩子的最大孩子与其交换,一直向下换。
1 /* 从最大堆H中取出键值为最大的元素,并删除一个结点 */ 2 ElementType DeleteMax( MaxHeap H ) 3 { 4 int Parent, Child; 5 ElementType MaxItem, X; 6 7 if ( IsEmpty(H) ) { 8 printf("最大堆已为空"); 9 return ERROR; 10 } 11 12 MaxItem = H->Data[1]; /* 取出根结点存放的最大值 */ 13 /* 用最大堆中最后一个元素从根结点开始向上过滤下层结点 */ 14 X = H->Data[H->Size--]; /* 注意当前堆的规模要减小 */ 15 for( Parent = 1; Parent * 2 <= H->Size; Parent = Child ) { 16 Child = Parent * 2; 17 if( (Child != H->Size) && (H->Data[Child] < H->Data[Child+1]) ) 18 Child++; /* Child指向左右子结点的较大者 */ 19 if( X >= H->Data[Child] ) break; /* 找到了合适位置 */ 20 else /* 下滤X */ 21 H->Data[Parent] = H->Data[Child]; 22 } 23 H->Data[Parent] = X; 24 25 return MaxItem; 26 }
4.最大堆的建立:将已经存在的N个元素按最大堆的要求存放在一个一维数组中
方法1:通过插入操作,将N个元素一个个相继插入到一个初始为空的堆中去,其时间代价最大为O(N logN)。
方法2:在线性时间复杂度下建立最大堆。
①将N个元素按输入顺序存入,先满足完全二叉树的结构特性
②调整各结点位置,以满足最大堆的有序特性。
从最小单位堆开始建堆,从后往前,第一个有孩子的最小单位堆开始。
1 /*----------- 建造最大堆 -----------*/ 2 /* 下滤:将H中以H->Data[p]为根的子堆调整为最大堆 */ 3 void PercDown( MaxHeap H, int p ) 4 { 5 int Parent, Child; 6 ElementType X; 7 8 X = H->Data[p]; /* 取出根结点存放的值 */ 9 for( Parent = p; Parent*2 <= H->Size; Parent = Child ) { 10 Child = Parent * 2; 11 if( (Child != H->Size) && (H->Data[Child] < H->Data[Child+1]) ) 12 Child++; /* Child指向左右子结点的较大者 */ 13 if( X >= H->Data[Child] ) break; /* 找到了合适位置 */ 14 else /* 下滤X */ 15 H->Data[Parent] = H->Data[Child]; 16 } 17 H->Data[Parent] = X; 18 } 19 20 /* 调整H->Data[]中的元素,使满足最大堆的有序性 */ 21 /* 这里假设所有H->Size个元素已经存在H->Data[]中 */ 22 void BuildHeap( MaxHeap H ) 23 { 24 /* 从最后一个结点的父节点开始,到根结点1 */ 25 for(int i = H->Size/2; i > 0; i-- ) 26 PercDown( H, i ); 27 }
2.最小堆(代码:sj4_4)
同理
1 //最小堆 2 #include <stdio.h> 3 #include <stdlib.h> 4 5 #define MINDATA -1000 /* 该值应根据具体情况定义为小于堆中所有可能元素的值 */ 6 #define ERROR -1 /* 错误标识应根据具体情况定义为堆中不可能出现的元素值 */ 7 8 typedef int ElementType; 9 10 typedef struct HNode *Heap; /* 堆的类型定义 */ 11 struct HNode { 12 ElementType *Data; /* 存储元素的数组 */ 13 int Size; /* 堆中当前元素个数 */ 14 int Capacity; /* 堆的最大容量 */ 15 }; 16 typedef Heap MinHeap; /* 最小堆 */ 17 18 MinHeap CreateHeap( int MaxSize ); 19 bool IsFull( MinHeap H ); 20 bool Insert( MinHeap H, ElementType X ); 21 bool IsEmpty( MinHeap H ); 22 ElementType DeleteMin( MinHeap H ); 23 void PercDown( MinHeap H, int p ); 24 void BuildHeap( MinHeap H ); 25 26 int main() 27 { 28 MinHeap Heap; 29 Heap = CreateHeap( 10 ); 30 BuildHeap(Heap); 31 Insert( Heap, 1); 32 Insert( Heap, 2); 33 Insert( Heap, 3); 34 ElementType Min = DeleteMin( Heap ); 35 printf("%d\n",Min); 36 return 0; 37 } 38 39 /* 创建容量为MaxSize的空的最小堆 */ 40 MinHeap CreateHeap( int MaxSize ) 41 { 42 43 MinHeap H = (MinHeap)malloc(sizeof(struct HNode)); 44 H->Data = (ElementType *)malloc((MaxSize+1)*sizeof(ElementType)); 45 H->Size = 0; 46 H->Capacity = MaxSize; 47 H->Data[0] = MINDATA; /* 定义"哨兵"为小于堆中所有可能元素的值*/ 48 49 return H; 50 } 51 52 bool IsFull( MinHeap H ) 53 { 54 return (H->Size == H->Capacity); 55 } 56 57 /* 将元素X插入最小堆H,其中H->Data[0]已经定义为哨兵 */ 58 bool Insert( MinHeap H, ElementType X ) 59 { 60 if ( IsFull(H) ) { 61 printf("最小堆已满"); 62 return false; 63 } 64 int i = ++H->Size; /* i指向插入后堆中的最后一个元素的位置 */ 65 for ( ; H->Data[i/2] > X; i /= 2 ) 66 H->Data[i] = H->Data[i/2]; /* 上滤X */ 67 H->Data[i] = X; /* 将X插入 */ 68 return true; 69 } 70 71 bool IsEmpty( MinHeap H ) 72 { 73 return (H->Size == 0); 74 } 75 76 /* 从最小堆H中取出键值为最小的元素,并删除一个结点 */ 77 ElementType DeleteMin( MinHeap H ) 78 { 79 int Parent, Child; 80 ElementType MinItem, X; 81 82 if ( IsEmpty(H) ) { 83 printf("最小堆已为空"); 84 return ERROR; 85 } 86 87 MinItem = H->Data[1]; /* 取出根结点存放的最小值 */ 88 /* 用最小堆中最后一个元素从根结点开始向上过滤下层结点 */ 89 X = H->Data[H->Size--]; /* 注意当前堆的规模要减小 */ 90 for( Parent = 1; Parent * 2 <= H->Size; Parent = Child ) { 91 Child = Parent * 2; 92 if( (Child != H->Size) && (H->Data[Child] > H->Data[Child+1]) ) 93 Child++; /* Child指向左右子结点的较小者 */ 94 if( X >= H->Data[Child] ) break; /* 找到了合适位置 */ 95 else /* 下滤X */ 96 H->Data[Parent] = H->Data[Child]; 97 } 98 H->Data[Parent] = X; 99 100 return MinItem; 101 } 102 103 /*----------- 建造最小堆 -----------*/ 104 /* 下滤:将H中以H->Data[p]为根的子堆调整为最小堆 */ 105 void PercDown( MinHeap H, int p ) 106 { 107 int Parent, Child; 108 ElementType X; 109 110 X = H->Data[p]; /* 取出根结点存放的值 */ 111 for( Parent = p; Parent*2 <= H->Size; Parent = Child ) { 112 Child = Parent * 2; 113 if( (Child != H->Size) && (H->Data[Child] > H->Data[Child+1]) ) 114 Child++; /* Child指向左右子结点的较小者 */ 115 if( X >= H->Data[Child] ) break; /* 找到了合适位置 */ 116 else /* 下滤X */ 117 H->Data[Parent] = H->Data[Child]; 118 } 119 H->Data[Parent] = X; 120 } 121 122 /* 调整H->Data[]中的元素,使满足最大堆的有序性 */ 123 /* 这里假设所有H->Size个元素已经存在H->Data[]中 */ 124 void BuildHeap( MinHeap H ) 125 { 126 /* 从最后一个结点的父节点开始,到根结点1 */ 127 for(int i = H->Size/2; i > 0; i-- ) 128 PercDown( H, i ); 129 }
sj4_4
二.哈夫曼树与哈弗曼编码
1.哈夫曼树
带权路径长度(WPL):设二叉树有n个叶子结点,每个叶子结点带有权值 Wk,从根结点到每个叶子结点的长度为 Lk,则每个叶子结点的带权路径长度之和就是:
WPL =
最优二叉树或哈夫曼树: WPL最小的二叉树
哈夫曼树的特点:
①没有度为1的结点
②n个叶子结点的哈夫曼树共有2n-1个结点
③哈夫曼树的任意非叶节点的左右子树交换后仍是哈夫曼树
④对同一组权值{w1 ,w2 , …… , wn},存在不同构的两棵哈夫曼树
1.哈夫曼树的构造
每次把权值最小的两颗二叉树合并.(利用堆)
->
-->
--->
---->
1 //Huffman Tree 2 typedef struct TreeNode *HuffmanTree; 3 struct TreeNode{ 4 int weight; 5 HuffmanTree left,right; 6 }; 7 8 HuffmanTree Huffman(MinHeap H) 9 {/*假设H->Size个权值已经存在在H->data[]->weight里*/ 10 HuffmanTree T; 11 BuildMinHeap(H);//将H->data[]按权值调整为最小堆 12 for(int i = 1; i < H->Size; i++) {//做H->Size-1次合并 13 T = (HuffmanTree)malloc(sizeof(struct TreeNode));//建立新结点 14 T->left = DeleteMin(H); //从最小堆中删除一个结点,作为新T的左子结点 15 T->right = DeleteMin(H);//从最小堆中删除一个结点,作为新T的右子结点 16 T->weight = T->left->weight + T->right->weight;//计算新权值 17 Insert(H,T);//将新T插入最小堆 18 } 19 T = DeleteMin(H); 20 return T; 21 }
2.哈弗曼编码
前缀码(prefix code):任何字符的编码都不是另一字符编码的前缀
可以无二义地解码
构造一颗编码代价最小的二叉树:HaffumanTree
三.并查集
集合的表示
集合运算:交、并、补、差,判定一个元素是否属于某一集合
并查集:集合并、查某元素属于什么集合
并查集问题中集合存储如何实现:
①可以用树结构表示集合,树的每个结点代表一个集合元素
②采用数组存储形式
合并后根为负数,负数代表根,其绝对值代表个数。
1.查找某个元素所在的集合(用根结点表示)
1 SetName Find( ElementType S[], ElementType X ) 2 { /* 默认集合元素全部初始化为-1 */ 3 if ( S[X] < 0 ) /* 找到集合的根 */ 4 return X; 5 else 6 return S[X] = Find( S, S[X] ); /* 路径压缩 */ 7 }
2.集合的并运算
①分别找到X1和X2两个元素所在集合树的根结点
②如果它们不同根,则将其中一个根结点的父结点指针设置成另一个根结点的数组下标。
为了改善合并以后的查找性能,采用小的集合合并到相对大的集合中。
1 /* 这里默认Root1和Root2是不同集合的根结点 */ 2 void Union( ElementType S[], SetName Root1, SetName Root2 ) 3 { 4 /* 保证小集合并入大集合 */ 5 if ( S[Root2] < S[Root1] ) { /* 如果集合2比较大 */ 6 S[Root2] += S[Root1]; /* 集合1并入集合2 */ 7 S[Root1] = Root2; 8 } 9 else { /* 如果集合1比较大 */ 10 S[Root1] += S[Root2]; /* 集合2并入集合1 */ 11 S[Root2] = Root1; 12 } 13 }