堆问题:求n个整数中最小的K个数

来源:互联网 发布:新疆网络管制原理 编辑:程序博客网 时间:2024/06/03 11:16

关联:http://blog.csdn.net/tonghuawanli/article/details/70140441

建堆过程

堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i]。在数组的非降序排序中,需要使用的就是大根堆,因为根据大根堆的要求可知,最大的值一定在堆顶。

既然是堆排序,自然需要先建立一个堆,而建堆的核心内容是调整堆,使二叉树满足堆的定义(每个节点的值都不大于其父节点的值)。调堆的过程应该从最后一个非叶子节点开始,假设有数组A = {1, 3, 4, 5, 7, 2, 6, 8, 0}。
建堆过程如下:

这里写图片描述

例题:输入4,5,1,6,2,7,3,8这8个数字,查找最小的4个数字是1,2,3,4
题目分析:
  在找出前k个最小(或最大)的元素时,如果元素个数较少,可以采用简单选择排序;如果元素较多,可以采用堆排序;如果元素基本有序,可以采用冒泡排序。本文采用的是堆排序O(nlogk)

方法1:利用java的PriorityQueue

想求最小的k个数,可以用一个大根堆过滤大的数;
如果想求k个最大的数,可以用小根堆过滤小的数;

public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {        ArrayList<Integer> list = new ArrayList<>();        if(input.length<1 || k<1 || input.length<k)            return list;        // 小根堆        //PriorityQueue<Integer> minHeap = new PriorityQueue<>();        //  建个大根堆        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new Comparator<Integer>() {        // 如果上面的写法出错就用下面的这个        // PriorityQueue<Integer> maxHeap = new PriorityQueue<>(1,new Comparator<Integer>() {            @Override            public int compare(Integer o1, Integer o2) {                // TODO Auto-generated method stub                return o2-o1;            }                   });        for(int i=0;i<input.length;i++){            maxHeap.offer(input[i]);            //  保证堆中元素数目始终是k个            if(i>=k)                maxHeap.poll();        }        int[] a = new int[k];        // 暂时求的数组是降序的        for(int i=0;i<k;i++)            a[i]=maxHeap.poll();        for(int i=k-1;i>=0;i--)            list.add(a[i]);        return list;    }

时间复杂度分析:
PriorityQueue的peek()和element操作是常数时间,add(), offer(), 无参数的remove()以及poll()方法的时间复杂度都是log(N)
因为在程序中限制了堆中元素数目始终<=k个,循环了n次,时间复杂度O(nlogk)

方法2:网友写的创立小根堆与大根堆。

(1)小根堆

代码结合着建堆过程图看

import java.util.ArrayList;public class Main {    public static void main(String[] args) {        Main main = new Main();        int[] b = {98,15,12,8,8,4,43,11,15,9,64,2,76,35,11,3,1};        int k = 5;         System.out.println(main.sort(b, k));            }    //在数组b中查找前k个最小的数据    public ArrayList<Integer> sort(int[] b, int k) {        ArrayList<Integer> list = new ArrayList<>();        if(b.length<k)                       return list;        int n=b.length;        //移除堆顶,将最后一个元素推入堆顶        for(int i=0;i<k;i++){                int k1 = (n-i)/2-1;  //k1为待调整的初始堆最后一个非叶子节点            for(int j=k1;j>=0;j--){      //建堆                smallHeap(b, j, n-i-1);             }            //System.out.print(b[0]+" ");            list.add(b[0]);             int temp = b[0];     //此处是将堆顶与待排序的最后一个元素交换            b[0] = b[n-i-1];            b[n-i-1] = temp;        }        return list;    }    //建小根堆,从最后一个非终端节点至顶点,参数是(b[], 开始比较的节点下标,最后一个待比较的元素的下标)    // 注:就是调整第k个结点(结点从0开始编号)为根的那棵树。    public static void smallHeap(int[] b, int k, int end){        int i=k, j=2*i+1;//i为要筛选的节点的下标,从0开始,j为 i的左孩子        while(j<=end){                   //当还没有比较到叶子节点            if(j<end && b[j]>b[j+1]){    //j保存i的左右孩子中较大的孩子的下标                j++;            }            if(b[i]<b[j]){     //如果i比左右孩子都小,则结束本轮比较                break;            }            else{             // i节点与j节点交换,                int temp = b[i];                b[i] = b[j];                b[j] = temp;                i = j;                j=2*i+1;            }        }    }}//  输出:[1, 2, 3, 4, 8]

(2)大根堆

如果求最大的k个数,就建立大根堆。

import java.util.ArrayList;public class Main3 {    public static void main(String[] args) {        Main3 main3 = new Main3();        int[] b = {98,15,12,8,8,4,43,11,15,9,64,2,76,35,11,3,1};        int k = 5;         System.out.println(main3.sort(b, k));    }    //在数组b中查找前k个最大的数据    public ArrayList<Integer> sort(int[] b, int k) {        ArrayList<Integer> list = new ArrayList<>();        if(b.length<k)                       return list;        int n=b.length;        //移除堆顶,将最后一个元素推入堆顶        for(int i=0;i<k;i++){                int k1 = (n-i)/2-1;  //k1为待调整的初始堆最后一个非叶子节点            for(int j=k1;j>=0;j--){      //建堆                bigHeap(b, j, n-i-1);             }            //System.out.print(b[0]+" ");            list.add(b[0]);             int temp = b[0];     //此处是将堆顶与待排序的最后一个元素交换            b[0] = b[n-i-1];            b[n-i-1] = temp;        }        return list;    }    //建大根堆      // 注:就是调整第k个结点(结点从0开始编号)为根的那棵树。    public static void bigHeap(int[] b, int k, int end){        int i=k, j=2*i+1;//i为要筛选的节点的下标,从0开始,j为 i的左孩子        while(j<=end){                   //当还没有比较到叶子节点            if(j<end && b[j]<b[j+1]){    //j保存i的左右孩子中较大的孩子的下标                j++;            }            if(b[i]>b[j]){     //如果i比左右孩子都大,则结束本轮比较                break;            }            else{             // i节点与j节点交换,                int temp = b[i];                b[i] = b[j];                b[j] = temp;                i = j;                j=2*i+1;            }        }    }}// 结果:[98, 76, 64, 43, 35]
0 0
原创粉丝点击