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

本文介绍堆排序的基本概念,包括大根堆与小根堆的建立及调整过程,并通过两个实例详细展示了如何利用堆排序来找出数组中的最小或最大k个数。

关联:http://blog.youkuaiyun.com/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]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值