问题: 一个数组长度为n, 求出其最小的k个元素并从小到大输出

遇到这样一个问题: 一个数组长度为n, 求出其最小的k个元素并从小到大输出

想到三种解决方法:

1. 最简单的, 给数组排个序, 然后把前面k个元素输出, OK.

优点是简单明了. 缺点是做了很多额外的工作: k个元素后面的没有必要.

2. 比上面好一些, 就是使用一个堆, 或者排序树. 首先把数组中前k个元素插入这个堆(或者排序二叉树中). 然后, 对后面所有的元素,

    a. 将这个元素插入堆或者排序二叉树中;

    b. 从这个堆或者排序二叉树中移除最大的元素.

如果是堆来实现的话,可以参考堆排序; 如果是排序树来实现的话,可以用现成的TreeSet: (注:这里有个要求,就是输入数组中的元素均不相同)

private static void solveSet(int[] data, int count) {
        TreeSet set = new TreeSet();
        for (int i=0; i<count; i++) {
            set.add(data[i]);
        }
        for (int i=count+1; i<data.length; i++) {
            set.add(data[i]);
            set.pollLast();
        }
        System.out.println("By set     " + set);
    }

因为堆和排序树的插入删除都比较快, 所以它的性能肯定强于整个数组排序.

我的测试结果, 用一亿条数据, 时间长度对比为

ms used for treeset 660
ms used for sort 2153

OK, 那么还有没有更快的方法? 想到了以前的,从数组中选择第k小的元素的Hoare算法.

也是类似的,从数组中选择第k小的元素, 没有必要对整个数组进行排序, 而是一个类似于快速排序的过程:

   a) 首先从数组中选择一个数m

   b) 把数组中的元素整理, 比m小的放在左边, 比m大的放在右边. 小的部分和大的部分顺序无所谓, 只要求都比m小(大)就可以;

   c) 整理之后, 如果m的位置恰好是k, 那么第k小的就是m. 直接返回m并结束.

   d) 否则, 如果m的位置 > k , 说明要查找的元素位于左边那部分. 则对左边的部分进行同样的处理;

        如果m的位置 < k , 说明要查找的元素位于右边那部分, 则对右边的部分进行同样的处理


回到最开始的问题. 有了这个思路, 那么类似的, 我们可以对数组, 找到第k小的元素. 这时上面的算法已经保证了再k左边的元素都是比k小的. 那么简单了, 对左边的元素排个序就OK了. 当然, 这比较适合要找的元素个数, 远远小于数组长度的情况(比如从一亿个元素中找到最小的10个).

首先, 找到第k小的代码如下:

class Hoare {

    public static void main(String[] argu) {
        int testSize = 100;
        int[] t1 = new int[100];
        Random r = new Random();
        for (int i=0; i<testSize; i++) {
            t1[i] = r.nextInt();
        }
        int[] s = Arrays.copyOf(t1, testSize);
        Arrays.sort(s);
        for (int i=0; i<testSize; i++) {
            int[] d = Arrays.copyOf(t1, testSize);
            int k = select(d, i, 0, testSize - 1);
            boolean verified = (s[i] == k);
            System.out.println(s[i] + " " + k + " " + verified);
            if (!verified) {
                System.err.println("ERRRRR!!!!!!");
                return;
            }
        }
        System.out.println("All verified!");

    }

    public static int partition(int[] list, int left, int right, int pivotIndex) {
        int pivotValue = list[pivotIndex];
        swap (list, pivotIndex, right);
        int storeIndex = left;
        for ( int i=left; i<=right; i++) {
            if (list[i] < pivotValue) {
                swap (list, storeIndex, i);
                storeIndex = storeIndex + 1;
            }
        }
        swap (list, right, storeIndex);
        //Sysstem.out.println("partion finished:" + storeIndex);
        return storeIndex;
    }

    private static void swap(int[] list, int pivotIndex, int right) {
        int t = list[pivotIndex];
        list[pivotIndex] = list[right];
        list[right] = t;
    }

    public static int select(int[] list, int k, int left, int right) {
        //select a pivot value list[pivotIndex]
        while ( true ) {
            int pivotIndex = (left + right) / 2;
            int pivotNewIndex = partition (list, left, right, pivotIndex);
            if (k == pivotNewIndex) {
                return list[k];
            }
            if (k < pivotNewIndex) {
                right = pivotNewIndex - 1;
            } else {
                left = pivotNewIndex + 1;
            }
        }
    } //end func
}

使用这个算法, 找到第k小的.找到后, 肯定就可以保证前面的元素都是小于k的(但是无序), 所以还得对前面的元素再次排个序. 因为这次要排序的元素很少, 速度就快了.

private static void solveHoare(int[] data, int count) {
        int k = Hoare.select(data, count, 0, data.length - 1);
        int[] temp = Arrays.copyOfRange(data, 0, count);
        Arrays.sort(temp);
        System.out.println("Final data " + Arrays.toString(temp));
    }


对比下来,对于要找的元素个数, 远远小于数组长度的情况, 这种算法是最快的,

当数组的长度是一亿的时候, 列出最小的前100个元素, 时间分别为:


ms used for treeset 9315
ms used for sort 2373
ms used for selectk 201

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值