模型
优先队列是允许至少下列两种操作的数据结构:插入,和删除最小者。
插入等价入队,删除等价出队。
二叉堆
二个性质:
- 结构性
- 堆序性
结构性质
堆是一颗被完全填满的二叉树,有可能的例外是在底层,底层上的元素从左到右填入。这样的树称为完全二叉树。
利用数组表示二叉堆
一个堆数据结构由一个数组,一个代表最大值的整数以及当前的堆大小组成。
1.struct HeapStruct; 2.typedef struct HeapStruct *PriorityQueue; 3. 4.struct HeapStruct 5.{ 6. int Capacity;//最大容量 7. int Size;//当前大小 8. int *Elements;//数组 9.};
堆序性质
使操作被快速执行的性质是堆序性。由于我们想要快速的找出最小元,因此,最小元应该在根上。
1.PriorityQueue Initialize(int MaxElements) 2.{ 3. PriorityQueue H; 4. if (MaxElements < 100) 5. { 6. Error(""); 7. } 8. H = malloc(sizeof(struct HeapStruct)); 9. 10. if (H == NULL) 11. { 12. Error(""); 13. } 14. H->Elements = malloc((MaxElements + 1) * sizeof(int)); 15. 16. if (H->Elements == NULL) 17. { 18. Error(""); 19. } 20. 21. H->Capacity = MaxElements; 22. H->Size = 0; 23. H->Elements[0] = 0; 24.}
基本堆操作
Insert(插入)
为将一个元素X插入到堆中,我们在下一个空闲位置创建一个空穴,否则该堆将不是完全树。如果X可以放在该空穴中而并不破坏推的序,那么插入完成。否则,我们把空穴的父节点上的元素移入该空穴中,这样空穴就朝着根的方向上行一步。继续该过程直到X能被放入空穴中为止。这种策略叫做上滤
1.void Insert(int x, PriorityQueue H) 2.{ 3. int i; 4. if (IsFull(H)) 5. { 6. Error(""); 7. } 8. for (i = ++H->Size; H->Elements[i / 2] > x; i /= 2) 9. { 10. H->Elements[i] = H->Elements[i / 2];//移动空穴 11. H->Elements[i] = x; 12. } 13.}
如果插入的值是新的最小元素,那么它将一直被推向顶端。当i为1时,我们就需要使程序跳出循环。我们将一个很小的值放在0处,这个值必须小于或等于堆中的任何值,我们将这个值称为标记
DeleteMin(删除最小元)
当删除一个最小元时,在根节点出产生一个空穴。由于现在堆少了一个元素,因此推中最后一个元素X必须移动到改堆的某个地方,如果X被放到空穴中,那么DeleteMin完成。我们把空穴的两个儿子中较小的移入空穴中,这样就把空穴向下推了一层。重复改步骤只到X被放入空穴中。这种策略叫做下滤
1.int DeleteMin(PriorityQueue H) 2.{ 3. int i, Child; 4. int MinElement, LastElement; 5. 6. if (IsEmpty(H)) 7. { 8. Error(""); 9. return H->Elements[0]; 10. } 11. 12. MinElement = H->Elements[1]; 13. LastElement = H->Elements[H->Size - 1]; 14. 15. for (i = 1; i * 2 < H->Size; i = Child) 16. { 17. Child = i * 2; 18. if (i!=H->Size&&H->Elements[Child+1]<H->Elements[Child]) 19. { 20. Child++; 21. } 22. if (LastElement > H->Elements[Child]) 23. { 24. 25. 26. H->Elements[i] = H->Elements[Child]; 27. } 28. else 29. break; 30. } 31. H->Elements[i] = LastElement; 32. return MinElement; 33.}
左式堆
二个性质:
- 结构性
- 堆序性
左式堆和二叉树间唯一的区别是:左式堆不是理想平衡的,而实际上是趋向非常不平衡的。
左式堆的性质
我们把任意节点的零路径长Npl定义为从X到一个没有两个儿子的节点的最短路径的长。
性质:对于每一个节点X,左儿子的零路径至少与右儿子的零路径长一样大。

左式堆的操作
对左式堆的基本操作是合并
两个左式堆:

合并:
1.#include<stdio.h> 2. 3.struct TreeNode; 4.typedef struct TreeNode *PriorityQueue; 5. 6. 7.struct TreeNode 8.{ 9. int Element; 10. PriorityQueue Left; 11. PriorityQueue Right; 12. int Npl; 13.}; 14. 15. 16.PriorityQueue Merge(PriorityQueue H1, PriorityQueue H2) { 17. if (H1==NULL) 18. { 19. return H2; 20. } 21. if (H2==NULL) 22. { 23. return H1; 24. } 25. if (H1->Element<H2->Element) 26. { 27. return Merge1(H1, H2); 28. } 29. else 30. { 31. return Merge1(H2, H1); 32. } 33.} 34. 35.static PriorityQueue Merge1(PriorityQueue H1, PriorityQueue H2) { 36. if (H1->Left==NULL) 37. { 38. H1->Left = H2; 39. } 40. else 41. { 42. H1->Right = Merge(H1->Right, H2); 43. if (H1->Left->Npl<H1->Right->Npl)//判断Npl 44. { 45. SwapChildren(H1);//交换子树 46. } 47. H1->Npl = H1->Right->Npl + 1; 48. } 49. return H1; 50.} 51. 52. 53.PriorityQueue Insert1(int x, PriorityQueue H) { 54. PriorityQueue SingleNode; 55. SingleNode = malloc(sizeof(struct TreeNode)); 56. if (SingleNode==NULL) 57. { 58. FatalError("Error"); 59. } 60. else 61. { 62. SingleNode->Element = x; 63. SingleNode->Npl = 0; 64. H = Merge(SingleNode, H); 65. } 66. return H; 67.} 68. 69. 70.PriorityQueue DeleteMin1(PriorityQueue H) { 71. PriorityQueue LeftHeap, RightHeap; 72. if (IsEmpty(H)) 73. { 74. Error("Error"); 75. return H; 76. } 77. LeftHeap = H->Left; 78. RightHeap = H->Right; 79. free(H); 80. return Merge(LeftHeap, RightHeap); 81.}
斜堆
斜堆是左式堆的自调节形式。
斜堆是具有堆序的二叉树,但是不存在对树的结构限制。
不过,不同于左式堆,关于任意节点的零路径长的任何信息都不保留。
与左式堆相同,斜推的基本操作也是合并操作。
对于左式堆,我们查看是否左儿子和右儿子满足左式堆堆序性质并交换那些不满足该性质者;对于斜堆,除了这些右路径上所有节点的最大者不交换它们的左右儿子外,交换都是无条件的。
斜堆也也可以向左式堆那样非递归的进行所有操作:合并右路径,除最后的节点外交换右路径上每个节点的左儿子和右儿子。
二项队列
二项队列不是一颗堆序的树,而是堆序的树的集合,称为森林。堆序树中的每一棵都是有约束的形式,叫做二项树。
每一个高度上至多存在一棵二项树。
二项队列操作
最小元可以通过搜索所有的树的根来找出。如果我们记住当最小元在其他操作期间变化时更新它,那么我们也可以保留最小元的信息并以O(1)时间执行该操作。
将高度相同的子树合并。
二项队列的实现
DeleteMin操作需要快速找出根的所有子树的能力,因此,需要一般树的标准表示方法:每个节点的儿子都存在一个链表中,而且每个节点都有一个指向它的第一个儿子的指针。诸儿子按照他们子树的大小排序。当两棵树被合并时,其中一棵树作为儿子被加到另一棵树上。
二项树的每一个节点将包含数据,第一个儿子以及右兄弟。二项树中的诸儿子以递减次序排列。
1.typedef struct BinNode *Position; 2.typedef struct Collection *BinQueue; 3. 4.struct BinNode 5.{ 6. int Element; 7. Position LeftChild; 8. Position NextSibling; 9.}; 10. 11.struct Collection 12.{ 13. int CurrentSize; 14. Position TheTree[MaxTrees]; 15.}; 16. 17. 18.Position CombineTrees(Position T1, Position T2) { 19. if (T1->Element>T2->Element) 20. { 21. return CombineTrees(T2, T1); 22. } 23. T2->NextSibling = T1->LeftChild; 24. T1->LeftChild = T2; 25. return T1; 26.}