您的位置:首页 > 其它

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

2017-04-07 23:18 435 查看
遇到这样一个问题: 一个数组长度为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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐