当前位置:   article > 正文

数据结构2:C++基于二叉堆实现优先队列

数据结构2:C++基于二叉堆实现优先队列

基本概念

        既然是通过二叉堆来实现一个优先队列,那么我们需要先知道什么是二叉堆,实际上,二叉堆的本质就是一个数组,只是我们从逻辑上将这个数组看作一颗完全二叉树,而这颗完全二叉树就是二叉堆。

        二叉堆又分为小根堆和大根堆,要判断一个二叉堆是哪种类型,首先要找到第一个非叶子节点的数组索引,假如数组的末尾元素索引是n,那么第一个非叶子节点的数组索引就是(n-1)/2。

        比如如图所示二叉堆的第一个非叶子节点的元素就是5。

        由此引出大根堆的性质,当i>=0并且i<=(n-1)/2时,如果arr[i]>arr[2*i+1]并且arr[i]>arr[2*i+2],也就是当前数组的节点i的元素的值,不仅大于其右孩子的值也大于其左孩子的值,对于整个数组的i都是如此,那么这个二叉堆就是一个大根堆。

        相应的如果arr[i]<arr[2*i+1]并且arr[i]<arr[2*i+2],这个二叉堆就是一个小根堆。

堆的操作

        如果要向一个二叉堆中增加元素,也就是入堆操作,那么添加的元素只能在数组的末尾,然后判断元素是否需要进行上浮操作,调整元素的位置,使其满足这个二叉堆的性质。

        如果要删除一个二叉堆中的元素,也就是出堆操作,那么删除的元素只能是堆顶元素也就是数组的0号位元素,并且用数组的末尾元素将数组的0号位元素进行覆盖,并判断覆盖后的0号位元素是否需要进行下沉操作。

        对于二叉堆来说,由于它本身的二叉树结构,因此入堆和出堆的时间复杂度都是log(n)。

代码实现

        要通过二叉堆实现一个优先队列,实际上就是将二叉堆的下沉和上浮操作进行封装,达到一个入队列和出队列的操作。

  1. class PriorityQueue
  2. {
  3. using Cmp = std::function<bool(int, int)>;
  4. private:
  5. int size;//队列中的元素个数
  6. int cap;//队列占用的总空间大小(以元素大小为单位)
  7. int* que;//指向动态数组的指针
  8. Cmp cmp;
  9. public:
  10. PriorityQueue(int cap = 20, Cmp cmp = std::greater<int>()) :size(0), cmp(cmp), cap(cap)
  11. {
  12. que = new int[cap];
  13. }
  14. PriorityQueue(Cmp cmp) :cmp(cmp),size(0), cap(20)
  15. {
  16. que = new int[cap];
  17. }
  18. ~PriorityQueue()
  19. {
  20. delete[]que;
  21. que = nullptr;
  22. }
  23. //入堆函数
  24. void push(int val)
  25. {
  26. if (size == cap)
  27. {
  28. int* p = new int[cap * 2];
  29. memcpy(p, que, cap*sizeof(int));
  30. delete[]que;
  31. que = p;
  32. cap *= 2;
  33. }
  34. if (size == 0)
  35. {
  36. que[size] = val;
  37. }
  38. else
  39. {
  40. siftup(size, val);
  41. }
  42. size++;
  43. }
  44. //出堆函数
  45. void pop()
  46. {
  47. if (size == 0)throw "the queue is empty";
  48. size--;
  49. if (size > 0)
  50. {
  51. siftdown(0, que[size]);
  52. }
  53. }
  54. //上浮函数
  55. void siftup(int i,int val)
  56. {
  57. //最多上浮到首元素
  58. while (i > 0)
  59. {
  60. int father = (i - 1) / 2;//从孩子的索引推出父亲的索引
  61. if (cmp(val,que[father]))
  62. {
  63. que[i]=que[father];
  64. i = father;
  65. }
  66. else
  67. {
  68. break;
  69. }
  70. }
  71. que[i] = val;
  72. }
  73. //下沉函数
  74. void siftdown(int i, int val)
  75. {
  76. //不能下沉超过第一个非叶子节点
  77. while (i <= (size - 1 - 1) / 2)//(n-1)/2
  78. {
  79. int child = 2 * i + 1;
  80. //找到两个孩子中,满足条件的孩子
  81. if (child + 1 < size && cmp(que[child + 1], que[child]))
  82. {
  83. child = child + 1;
  84. }
  85. if (cmp(que[child], val))
  86. {
  87. que[i] = que[child];
  88. i = child;
  89. }
  90. else
  91. {
  92. break;
  93. }
  94. }
  95. que[i] = val;
  96. }
  97. bool empty() const
  98. {
  99. return size == 0;
  100. }
  101. int getsize()const
  102. {
  103. return size;
  104. }
  105. int top()const
  106. {
  107. if (size == 0)
  108. throw "que is empty";
  109. return que[0];
  110. }
  111. };

这里我使用了一个函数对象作为构造函数的传入参数,这样用户就可以自己定义优先队列的构造机制,可以是小根堆也可以是大根堆,用户只需要自己定义传入的函数对象就可以了,默认的话就是大根堆。

测试代码

  1. int main()
  2. {
  3. PriorityQueue que;
  4. srand(time(NULL));
  5. for (int i = 0; i < 10; i++)
  6. {
  7. que.push(i+1);
  8. }
  9. while(!que.empty())
  10. {
  11. std::cout << que.top()<< " ";
  12. que.pop();
  13. }
  14. return 0;
  15. }

从输出结果也可以发现,大根堆每次出堆的都是整个数组中的最大元素。 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/人工智能uu/article/detail/927952
推荐阅读
相关标签
  

闽ICP备14008679号