赞
踩
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代码实现:
- /*
- * $filename: HeapSort.java,v $
- * $Date: 2014-3-17 $
- * Copyright (C) ZhengHaibo, Inc. All rights reserved.
- * This software is Made by Zhenghaibo.
- */
- package edu.njupt.zhb;
- /*
- *@author: ZhengHaibo
- *web: http://blog.csdn.net/nuptboyzhb
- *mail: zhb931706659@126.com
- *2014-3-9 Nanjing,njupt,China
- */
- public class HeapSort {
-
- /**
- * @param args
- */
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- int []a = {12,5,6,98,45,2,4,9};
- myHeapSort(a);
- for(int e:a){
- System.out.println(e);
- }
- }
-
- /**
- * 最大堆调整
- * 将data当中范围为[i~high]的元素调整为
- * 以i为根的子树调整为最大堆
- * @param data
- * @param i
- * @param high
- */
- public static void maxHeapify(int []data,int i,int high){
- int left=2*i+1;//左子树
- int right=2*i+2;//右子树
- int largest=i;//定义一个最大值
- if(left<=high&&data[left]>data[i]){
- largest=left;
- }
- if(right<=high&&data[right]>data[largest]){
- largest=right;
- }
- if(largest!=i){//如果最大值不是根节点,将根节点和最大值交换
- int temp=data[i];
- data[i]=data[largest];
- data[largest]=temp;
- //由于交换,可能其子树不满足最大堆,继续向后调整,使其子树成为最大堆
- maxHeapify(data,largest,high);
- }
- }
- /**
- * 堆排序
- * 1.建立堆
- * 2.逐步输出堆中的最大值
- * @param data
- */
- public static void myHeapSort(int []data){
- int n=data.length;
- //建立堆,由于n/2及以后的元素肯定是叶子节点,可看为大小为1的最大堆
- //因此从最后一个叶子节点逐步向前调整
- //调整结果:data[0]为根节点的最大堆
- for(int i=n/2-1;i>=0;i--){
- maxHeapify(data,i,n-1);
- }
- //此时,data是一个data[0]为根节点的最大堆
- //逐步将data[0]与最后一个元素交换,然后再将0~n-2调整为最大堆
- //相当于逐步将堆的最大元素放到数组最后的过程
- for(int i=n-1;i>=1;i--){
- int temp=data[0];
- data[0]=data[i];
- data[i]=temp;
- maxHeapify(data,0,i-1);
- }
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
3.优先队列
优先队列和队列很像,只是优先队列的出列是按照优先级大小出列的。优先队列可以用对来实现。
- package edu.njupt.zhb;
- /*
- *@author: ZhengHaibo
- *web: http://blog.csdn.net/nuptboyzhb
- *mail: zhb931706659@126.com
- *2014-3-15 Nanjing,njupt,China
- */
- /**
- *使用二叉堆实现优先队列
- *说明:
- *为了演示优先队列的功能,该优先队列只支持整型的数据
- */
- public class MyPriorityQueue {
- final int INIT_LEN=10;
- int size;
- int datas[];
- public MyPriorityQueue(){
- datas=new int[INIT_LEN];
- size=0;
- }
- public boolean isEmpty(){
- return size<=0;
- }
- /**
- * 获取堆顶的元素
- * 最大值
- * @return
- */
- public int peek(){
- if(datas.length<1){
- System.out.println("ERROR");
- return -1;
- }
- return datas[0];
- }
- /**
- * 入列
- * 向堆中添加一个元素,添加后的仍为一个堆
- * @param data
- */
- public void add(int data){
- if(size>=datas.length){//扩充数组
- int []temp=new int[datas.length+INIT_LEN];
- for(int i=0;i<datas.length-1;i++){
- temp[i]=datas[i];
- }
- datas=temp;
- }
- datas[size++]=data;//将数据添加到末尾
- for(int i=size/2;i>=0;i--){//建立堆
- maxHeapify(datas, i, size-1);
- }
- }
- /**
- * 出列
- * 取出堆中最大的元素,并删除之
- * @return
- */
- public int remove(){
- int data=datas[0];//将堆顶数据取出
- for(int i=1;i<size;i++){//平移
- datas[i-1]=datas[i];
- }
- size--;
- for(int i=size/2-1;i>=0;i--){//将新的数组建立堆
- maxHeapify(datas, i, size);
- }
- return data;
- }
- /**
- * 最大堆调整
- * 将data当中范围为[i~high]的元素调整为
- * 以i为根的子树调整为最大堆
- * @param data
- * @param i
- * @param high
- */
- public void maxHeapify(int []data,int i,int high){
- int left=2*i+1;//左子树
- int right=2*i+2;//右子树
- int largest=i;//定义一个最大值
- if(left<=high&&data[left]>data[i]){
- largest=left;
- }
- if(right<=high&&data[right]>data[largest]){
- largest=right;
- }
- if(largest!=i){//如果最大值不是根节点,将根节点和最大值交换
- int temp=data[i];
- data[i]=data[largest];
- data[largest]=temp;
- maxHeapify(data,largest,high);//继续调节最大值的子树,使其成为最大堆
- }
- }
-
- public static void main(String[] args) {
- MyPriorityQueue queue=new MyPriorityQueue();
- int []datas={2,54,23,56,8,65,3};
- for(int data:datas){
- queue.add(data);
- }
- while(!queue.isEmpty()){
- System.out.println(queue.remove());
- }
- }
-
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
3.堆排序的应用:TopK
1.首先,我们看一下这个问题:输出一个数组中第二大的值
我们分析的思路是:初始化一个最大值和第二大的值,然后逐步扫描。
1)如果当前值a[i]>maxV,则将secV=maxV;maxV=a[i];
2) 如果当前值a[i]>secV或者secV==maxV&&a[i]<maxV,则secV=a[i]
代码如下:
- /**
- * 查找第二大的值
- * 时间复杂度O(n)
- * 空间复杂度O(1)
- * @param data
- */
- public static void findSecondMax(int []data){
- int maxValue=data[0];
- int secValue=data[0];
- for(int i=1;i<data.length;i++){
- if(data[i]>maxValue){
- secValue=maxValue;
- maxValue=data[i];
- }else if(data[i]>secValue||(secValue==maxValue&&data[i]<secValue)){
- secValue=data[i];
- }
- }
- if(maxValue>secValue){
- System.out.println(secValue);
- return;
- }
- System.out.println("不存在第二大的值");
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
2.如果要查找很大的一组数(n很大)中的前K个最大值(k<<n),我们应该怎么办呢?显然,上面的思路,我们代码写起来就会比较麻烦,而且效率还不高。我们可以根据堆排序的特点,借助堆排序。
1)首先建立堆,时间复杂度为O(n)
2)只需要输出前K个最大值即可,也就是调整k次即可
空间复杂度O(1)
代码如下:
- /**
- * 查找data中的最大的前K个值
- * @param data
- * @param k
- */
- public static void findTopK(int []data,int k){
- if(k>=data.length){
- return;
- }
- int n=data.length;
- for(int i=n/2-1;i>=0;i--){//建堆的时间复杂度为O(n)
- maxHeapify(data,i,n-1);
- }
- for(int i=n-1;i>=n-k;i--){//只需要调整k次即可
- System.out.println(data[0]);
- int temp=data[0];
- data[0]=data[i];
- data[i]=temp;
- maxHeapify(data,0,i-1);
- }
- }
- /**
- * 堆调整
- * @param data
- * @param low
- * @param high
- */
- private static void maxHeapify(int[] data, int low, int high) {
- // TODO Auto-generated method stub
- int left=2*low+1;
- int right=2*low+2;
- int largest=low;
- if(left<=high&&data[left]>data[largest]){
- largest=left;
- }
- if(right<=high&&data[right]>data[largest]){
- largest=right;
- }
- if(largest!=low){
- int temp=data[low];
- data[low]=data[largest];
- data[largest]=temp;
- maxHeapify(data, largest, high);
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
3.当data数据数据量有1T时。且存储在硬盘上,显然无法一次性读入到内存中进行排序,此时,求解这些数据的TopK。
我们的思路是:初始化一个大小为K的数组result,然后建立最小堆,使得result[0]最小,然后逐个遍历data中的数据,如果比result[0]大,就将其替换result[0],然后调整最小堆,依次遍历结束。
- /**
- * 查找topK
- * 思路:result为存放topK的数组,首先将data中的前K个元素复制到result中
- * 对result建立最小堆,使result[0]为最小值。
- * 其次,遍历data中剩余的元素,如果data[i]大于result[0],那么就用data[i]代替result[0]
- * 然后调整堆,使result[0]成为最小值。依次遍历结束
- * 优点:当data很大时,无法将data直接读入到内存时,用该方式很合适!
- * 时间复杂度:O(K)+O(K)+O(n*logK)
- * @param data
- * @param result
- */
- public static void findTopK(int []data,int []result){
- if(result.length>=data.length){
- return;
- }
- for(int i=0;i<result.length;i++){//初始化result O(K)
- result[i]=data[i];
- }
- for(int i=result.length/2-1;i>=0;i--){//对result数组建堆 O(K)
- minHeapify(result, i, result.length-1);
- }
- for(int i=result.length;i<data.length;i++){//O(n*logK)
- if(data[i]>result[0]){
- result[0]=data[i];
- minHeapify(result,0,result.length-1);
- }
- }
- }
- /**
- * 最小堆调整
- * @param data
- * @param low
- * @param high
- */
- private static void minHeapify(int []data,int low,int high){
- int left=2*low+1;
- int right=2*low+2;
- int small=low;
- if(left<=high&&data[left]<data[small]){
- small=left;
- }
- if(right<=high&&data[right]<data[small]){
- small=right;
- }
- if(small!=low){
- int temp=data[low];
- data[low]=data[small];
- data[small]=temp;
- minHeapify(data, small, high);
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
查找nums中第K大的数
- public int findKthLargest(int[] nums, int k) {
- PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
- for (int item : nums) {
- if (priorityQueue.size() < k) {
- priorityQueue.offer(item);
- } else {
- Integer min = priorityQueue.peek();
- if (item > min) {
- priorityQueue.offer(item);
- }
- }
- }
- return priorityQueue.peek();
- }
参考博文:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。