堆排序:
步骤总结:
堆排序即利用堆的思想来进行排序,总共分为两个步骤
1、建堆(如果升序就建大根堆,降序就建小根堆)
2、利用堆删除的思想来进行排序
前面我们说到了建堆和堆删除的操作都是需要用到向下调整的思想的,所以当我们掌握了向下调整的思想就可以完成堆排序。
ps:不了解这部分知识的可以看一下博主的这篇博客内容:
建立思想:(以升序为例)
首先我们以大根堆的形式建堆,建堆完毕后堆顶元素为当前数组中的最大值,(注意说的是当前数组,因为我们的size一直在减小,这也是为了把最大的值放到原始数组的后面服务)将堆顶元素与堆尾元素进行互换,并向下调整,使其依然为大根堆,这样最大的值就放到了末尾,调整后的堆顶为原数组中的第二大的元素,经过上述过程,放到原数组的倒数第二个位置,依次类推,直至堆中只留一个元素,即可得到数组的升序排列。
特别注意:在进行这些操作的时候,我们只是定义了临时的变量来保存数组的长度,切不可改变原数组的长度!
1. import java.util.Arrays; 2. 3. public class Test { 4. public static void main(String[] args) { 5. int []arr={27,15,19,18,28,34,65,49,25,37}; 6. heapSort(arr); 7. System.out.println(Arrays.toString(arr)); 8. 9. } 10. 11. public static void heapSort(int[]arr){ 12. creatHeap(arr); 13. int length= arr.length-1; 14. while (length>=0){ 15. int temp=arr[0]; 16. arr[0]=arr[length]; 17. arr[length]=temp; 18. shiftDown(arr,0,length); 19. length--; 20. } 21. 22. } 23. 24. public static void creatHeap(int[]arr){ 25. int size= arr.length; 26. for(int parent=(size-1-1)/2;parent>=0;parent--){ 27. 28. shiftDown(arr,parent,size); 29. 30. } 31. } 32. 33. 34. public static void shiftDown(int[]arr,int parent,int len){ 35. 36. int child=2*parent+1; 37. 38. while(child<len){ 39. if(child+1<len&&arr[child]<arr[child+1]){ 40. child++; 41. } 42. 43. if(arr[child]>arr[parent]){ 44. int temp=arr[child]; 45. arr[child]=arr[parent]; 46. arr[parent]=temp; 47. parent=child; 48. child=2*parent+1; 49. 50. }else { 51. break; 52. } 53. } 54. 55. } 56. }
我们在使用堆排序的时候,其默认排序方式就是升序排序。
Top-K问题
Top-K问题是堆应用的一个非常经典的问题
问题描述:
TOP-K问题:即求数据集合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大(注意:切不可用简单的排序后取前k个值这种做法,这种方式在数据量不大的时候简单可行,但固然不是最优的方法。)
比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下
基本思路: 利用分布式思想处理海量数据
1. 用数据集合中前K个元素来建堆
前k个最大的元素,则建小堆
前k个最小的元素,则建大堆
2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素
将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素
1. class Solution { 2. public int[] smallestK(int[] arr, int k) { 3. if(k==0||arr==null){ 4. return new int[0]; 5. } 6. //1、建大根堆 7. PriorityQueue<Integer> minHeap = new PriorityQueue<>(k, new Comparator<Integer>() { 8. @Override 9. public int compare(Integer o1, Integer o2) { 10. return o2.compareTo(o1); 11. } 12. }); 13. //2、将前k个元素放到大根堆当中 14. for(int i=0;i<k;i++){ 15. minHeap.offer(arr[i]); 16. } 17. //3、对从k+1个元素开始的数字与堆顶元素比较,如果比堆顶元素小就把堆顶弹出,让该小的元素作为新 18. //的堆顶,并进行向下调整使之依然为大根堆 19. for(int j=k;j<arr.length;j++){ 20. 21. if(arr[j]<minHeap.peek()){ 22. minHeap.poll(); 23. minHeap.offer(arr[j]); 24. } 25. } 26. //4、把大根堆中的k个元素放到数组中(此时已经为前k个最小的数) 27. int[]temp=new int[k]; 28. for(int i=0;i<k;i++){ 29. temp[i]=minHeap.poll(); 30. } 31. 32. return temp; 33. 34. 35. } 36. }
Top-K问题与堆排序有类似之处,我们需要注意的是:
1、当k为0或者数组为空时什么都不返回,即返回[],就是return int[0];这个操作!
2、由于我们在建大根堆时用到的优先级队列其底层是一个小根堆(默认),所以我们要重写comparator(比较器)接口中的compar方法,使之变成大根堆。这里我们可以用到几种方法:
(1)匿名内部类(我就是采用的这种方法)
(2)直接自己实现一个比较器重写comparator(比较器)接口中的compar方法
这两种都是可以的。
拓展:
那如果是要获取第k小的或者第k大的数(这里以第k小的数为例),我们已经求得了最小的k个数,那么我们只需要在这里面找到最大的那个数(也就是第k小的数即可)。