当前位置:   article > 正文

重温数据结构:堆,堆排序,优先队列,TopK问题_python 大顶堆优先队列

python 大顶堆优先队列

1.堆的基本概念

堆实际上是一颗完全二叉树,其中,任何一个非叶子节点均满足如下性质:

key[i]<=key[2*i+1]&&key[i]<=key[2*i+2]   (小顶堆) 或

key[i]>=key[2*i+1]&&key[i]>=key[2*i+2]    (大顶堆

其中i是从0开始的。

2.堆排序的思想

   利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录)变得简单。

    其基本思想为(大顶堆):

    1)将初始待排序关键字序列(R0,R1....Rn-1)构建成大顶堆,此堆为初始的无序区;

    2)将堆顶元素R[0]与最后一个元素R[n-1]交换,此时得到新的无序区(R0,R1,......Rn-2)和新的有序区(Rn-1),且满足R[0,1...n-2]<=R[n-1]; 

    3)由于交换后新的堆顶R[0]可能违反堆的性质,因此需要对当前无序区(R0,R1,......Rn-2)调整为新堆,然后再次将R[0]与无序区最后一个元素交换,得到新的无序区(R0,R1....Rn-3)和新的有序区(Rn-2,Rn-1)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

注意:上述算法描述与《算法导论》上有一点点的差别,算法导论上将元素的第一个值标记为R1,因此每个元素的下标相差1.为了有利于程序实现,以上均是按照数组从0开始的习惯进行描述的

Java代码实现:

  1. /*
  2. * $filename: HeapSort.java,v $
  3. * $Date: 2014-3-17 $
  4. * Copyright (C) ZhengHaibo, Inc. All rights reserved.
  5. * This software is Made by Zhenghaibo.
  6. */
  7. package edu.njupt.zhb;
  8. /*
  9. *@author: ZhengHaibo
  10. *web: http://blog.csdn.net/nuptboyzhb
  11. *mail: zhb931706659@126.com
  12. *2014-3-9 Nanjing,njupt,China
  13. */
  14. public class HeapSort {
  15. /**
  16. * @param args
  17. */
  18. public static void main(String[] args) {
  19. // TODO Auto-generated method stub
  20. int []a = {12,5,6,98,45,2,4,9};
  21. myHeapSort(a);
  22. for(int e:a){
  23. System.out.println(e);
  24. }
  25. }
  26. /**
  27. * 最大堆调整
  28. * 将data当中范围为[i~high]的元素调整为
  29. * 以i为根的子树调整为最大堆
  30. * @param data
  31. * @param i
  32. * @param high
  33. */
  34. public static void maxHeapify(int []data,int i,int high){
  35. int left=2*i+1;//左子树
  36. int right=2*i+2;//右子树
  37. int largest=i;//定义一个最大值
  38. if(left<=high&&data[left]>data[i]){
  39. largest=left;
  40. }
  41. if(right<=high&&data[right]>data[largest]){
  42. largest=right;
  43. }
  44. if(largest!=i){//如果最大值不是根节点,将根节点和最大值交换
  45. int temp=data[i];
  46. data[i]=data[largest];
  47. data[largest]=temp;
  48. //由于交换,可能其子树不满足最大堆,继续向后调整,使其子树成为最大堆
  49. maxHeapify(data,largest,high);
  50. }
  51. }
  52. /**
  53. * 堆排序
  54. * 1.建立堆
  55. * 2.逐步输出堆中的最大值
  56. * @param data
  57. */
  58. public static void myHeapSort(int []data){
  59. int n=data.length;
  60. //建立堆,由于n/2及以后的元素肯定是叶子节点,可看为大小为1的最大堆
  61. //因此从最后一个叶子节点逐步向前调整
  62. //调整结果:data[0]为根节点的最大堆
  63. for(int i=n/2-1;i>=0;i--){
  64. maxHeapify(data,i,n-1);
  65. }
  66. //此时,data是一个data[0]为根节点的最大堆
  67. //逐步将data[0]与最后一个元素交换,然后再将0~n-2调整为最大堆
  68. //相当于逐步将堆的最大元素放到数组最后的过程
  69. for(int i=n-1;i>=1;i--){
  70. int temp=data[0];
  71. data[0]=data[i];
  72. data[i]=temp;
  73. maxHeapify(data,0,i-1);
  74. }
  75. }
  76. }


3.优先队列

优先队列和队列很像,只是优先队列的出列是按照优先级大小出列的。优先队列可以用对来实现。

  1. package edu.njupt.zhb;
  2. /*
  3. *@author: ZhengHaibo
  4. *web: http://blog.csdn.net/nuptboyzhb
  5. *mail: zhb931706659@126.com
  6. *2014-3-15 Nanjing,njupt,China
  7. */
  8. /**
  9. *使用二叉堆实现优先队列
  10. *说明:
  11. *为了演示优先队列的功能,该优先队列只支持整型的数据
  12. */
  13. public class MyPriorityQueue {
  14. final int INIT_LEN=10;
  15. int size;
  16. int datas[];
  17. public MyPriorityQueue(){
  18. datas=new int[INIT_LEN];
  19. size=0;
  20. }
  21. public boolean isEmpty(){
  22. return size<=0;
  23. }
  24. /**
  25. * 获取堆顶的元素
  26. * 最大值
  27. * @return
  28. */
  29. public int peek(){
  30. if(datas.length<1){
  31. System.out.println("ERROR");
  32. return -1;
  33. }
  34. return datas[0];
  35. }
  36. /**
  37. * 入列
  38. * 向堆中添加一个元素,添加后的仍为一个堆
  39. * @param data
  40. */
  41. public void add(int data){
  42. if(size>=datas.length){//扩充数组
  43. int []temp=new int[datas.length+INIT_LEN];
  44. for(int i=0;i<datas.length-1;i++){
  45. temp[i]=datas[i];
  46. }
  47. datas=temp;
  48. }
  49. datas[size++]=data;//将数据添加到末尾
  50. for(int i=size/2;i>=0;i--){//建立堆
  51. maxHeapify(datas, i, size-1);
  52. }
  53. }
  54. /**
  55. * 出列
  56. * 取出堆中最大的元素,并删除之
  57. * @return
  58. */
  59. public int remove(){
  60. int data=datas[0];//将堆顶数据取出
  61. for(int i=1;i<size;i++){//平移
  62. datas[i-1]=datas[i];
  63. }
  64. size--;
  65. for(int i=size/2-1;i>=0;i--){//将新的数组建立堆
  66. maxHeapify(datas, i, size);
  67. }
  68. return data;
  69. }
  70. /**
  71. * 最大堆调整
  72. * 将data当中范围为[i~high]的元素调整为
  73. * 以i为根的子树调整为最大堆
  74. * @param data
  75. * @param i
  76. * @param high
  77. */
  78. public void maxHeapify(int []data,int i,int high){
  79. int left=2*i+1;//左子树
  80. int right=2*i+2;//右子树
  81. int largest=i;//定义一个最大值
  82. if(left<=high&&data[left]>data[i]){
  83. largest=left;
  84. }
  85. if(right<=high&&data[right]>data[largest]){
  86. largest=right;
  87. }
  88. if(largest!=i){//如果最大值不是根节点,将根节点和最大值交换
  89. int temp=data[i];
  90. data[i]=data[largest];
  91. data[largest]=temp;
  92. maxHeapify(data,largest,high);//继续调节最大值的子树,使其成为最大堆
  93. }
  94. }
  95. public static void main(String[] args) {
  96. MyPriorityQueue queue=new MyPriorityQueue();
  97. int []datas={2,54,23,56,8,65,3};
  98. for(int data:datas){
  99. queue.add(data);
  100. }
  101. while(!queue.isEmpty()){
  102. System.out.println(queue.remove());
  103. }
  104. }
  105. }

3.堆排序的应用:TopK

1.首先,我们看一下这个问题:输出一个数组中第二大的值

我们分析的思路是:初始化一个最大值和第二大的值,然后逐步扫描。

1)如果当前值a[i]>maxV,则将secV=maxV;maxV=a[i];

2)  如果当前值a[i]>secV或者secV==maxV&&a[i]<maxV,则secV=a[i]

代码如下:

  1. /**
  2. * 查找第二大的值
  3. * 时间复杂度O(n)
  4. * 空间复杂度O(1)
  5. * @param data
  6. */
  7. public static void findSecondMax(int []data){
  8. int maxValue=data[0];
  9. int secValue=data[0];
  10. for(int i=1;i<data.length;i++){
  11. if(data[i]>maxValue){
  12. secValue=maxValue;
  13. maxValue=data[i];
  14. }else if(data[i]>secValue||(secValue==maxValue&&data[i]<secValue)){
  15. secValue=data[i];
  16. }
  17. }
  18. if(maxValue>secValue){
  19. System.out.println(secValue);
  20. return;
  21. }
  22. System.out.println("不存在第二大的值");
  23. }


2.如果要查找很大的一组数(n很大)中的前K个最大值(k<<n),我们应该怎么办呢?显然,上面的思路,我们代码写起来就会比较麻烦,而且效率还不高。我们可以根据堆排序的特点,借助堆排序。

1)首先建立堆,时间复杂度为O(n)

2)只需要输出前K个最大值即可,也就是调整k次即可

空间复杂度O(1)

代码如下:

  1. /**
  2. * 查找data中的最大的前K个值
  3. * @param data
  4. * @param k
  5. */
  6. public static void findTopK(int []data,int k){
  7. if(k>=data.length){
  8. return;
  9. }
  10. int n=data.length;
  11. for(int i=n/2-1;i>=0;i--){//建堆的时间复杂度为O(n)
  12. maxHeapify(data,i,n-1);
  13. }
  14. for(int i=n-1;i>=n-k;i--){//只需要调整k次即可
  15. System.out.println(data[0]);
  16. int temp=data[0];
  17. data[0]=data[i];
  18. data[i]=temp;
  19. maxHeapify(data,0,i-1);
  20. }
  21. }
  22. /**
  23. * 堆调整
  24. * @param data
  25. * @param low
  26. * @param high
  27. */
  28. private static void maxHeapify(int[] data, int low, int high) {
  29. // TODO Auto-generated method stub
  30. int left=2*low+1;
  31. int right=2*low+2;
  32. int largest=low;
  33. if(left<=high&&data[left]>data[largest]){
  34. largest=left;
  35. }
  36. if(right<=high&&data[right]>data[largest]){
  37. largest=right;
  38. }
  39. if(largest!=low){
  40. int temp=data[low];
  41. data[low]=data[largest];
  42. data[largest]=temp;
  43. maxHeapify(data, largest, high);
  44. }
  45. }

3.当data数据数据量有1T时。且存储在硬盘上,显然无法一次性读入到内存中进行排序,此时,求解这些数据的TopK。

我们的思路是:初始化一个大小为K的数组result,然后建立最小堆,使得result[0]最小,然后逐个遍历data中的数据,如果比result[0]大,就将其替换result[0],然后调整最小堆,依次遍历结束。

  1. /**
  2. * 查找topK
  3. * 思路:result为存放topK的数组,首先将data中的前K个元素复制到result中
  4. * 对result建立最小堆,使result[0]为最小值。
  5. * 其次,遍历data中剩余的元素,如果data[i]大于result[0],那么就用data[i]代替result[0]
  6. * 然后调整堆,使result[0]成为最小值。依次遍历结束
  7. * 优点:当data很大时,无法将data直接读入到内存时,用该方式很合适!
  8. * 时间复杂度:O(K)+O(K)+O(n*logK)
  9. * @param data
  10. * @param result
  11. */
  12. public static void findTopK(int []data,int []result){
  13. if(result.length>=data.length){
  14. return;
  15. }
  16. for(int i=0;i<result.length;i++){//初始化result O(K)
  17. result[i]=data[i];
  18. }
  19. for(int i=result.length/2-1;i>=0;i--){//对result数组建堆 O(K)
  20. minHeapify(result, i, result.length-1);
  21. }
  22. for(int i=result.length;i<data.length;i++){//O(n*logK)
  23. if(data[i]>result[0]){
  24. result[0]=data[i];
  25. minHeapify(result,0,result.length-1);
  26. }
  27. }
  28. }
  29. /**
  30. * 最小堆调整
  31. * @param data
  32. * @param low
  33. * @param high
  34. */
  35. private static void minHeapify(int []data,int low,int high){
  36. int left=2*low+1;
  37. int right=2*low+2;
  38. int small=low;
  39. if(left<=high&&data[left]<data[small]){
  40. small=left;
  41. }
  42. if(right<=high&&data[right]<data[small]){
  43. small=right;
  44. }
  45. if(small!=low){
  46. int temp=data[low];
  47. data[low]=data[small];
  48. data[small]=temp;
  49. minHeapify(data, small, high);
  50. }
  51. }

查找nums中第K大的数

  1. public int findKthLargest(int[] nums, int k) {
  2. PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
  3. for (int item : nums) {
  4. if (priorityQueue.size() < k) {
  5. priorityQueue.offer(item);
  6. } else {
  7. Integer min = priorityQueue.peek();
  8. if (item > min) {
  9. priorityQueue.offer(item);
  10. }
  11. }
  12. }
  13. return priorityQueue.peek();
  14. }

参考博文:

[1]堆排序 - Matrix海子 - 博客园

[2]数据结构之优先队列--二叉堆(Java实现)_java先进先出数据结构_LCore的博客-CSDN博客

[3]堆排序实现以及相关笔试题_WA说的博客-CSDN博客

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

闽ICP备14008679号