问题: 一个数组长度为n, 求出其最小的k个元素并从小到大输出
2017-04-07 23:18
435 查看
遇到这样一个问题: 一个数组长度为n, 求出其最小的k个元素并从小到大输出
想到三种解决方法:
1. 最简单的, 给数组排个序, 然后把前面k个元素输出, OK.
优点是简单明了. 缺点是做了很多额外的工作: k个元素后面的没有必要.
2. 比上面好一些, 就是使用一个堆, 或者排序树. 首先把数组中前k个元素插入这个堆(或者排序二叉树中). 然后, 对后面所有的元素,
a. 将这个元素插入堆或者排序二叉树中;
b. 从这个堆或者排序二叉树中移除最大的元素.
如果是堆来实现的话,可以参考堆排序; 如果是排序树来实现的话,可以用现成的TreeSet: (注:这里有个要求,就是输入数组中的元素均不相同)
因为堆和排序树的插入删除都比较快, 所以它的性能肯定强于整个数组排序.
我的测试结果, 用一亿条数据, 时间长度对比为
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小的代码如下:
使用这个算法, 找到第k小的.找到后, 肯定就可以保证前面的元素都是小于k的(但是无序), 所以还得对前面的元素再次排个序. 因为这次要排序的元素很少, 速度就快了.
对比下来,对于要找的元素个数, 远远小于数组长度的情况, 这种算法是最快的,
当数组的长度是一亿的时候, 列出最小的前100个元素, 时间分别为:
ms used for treeset 9315
ms used for sort 2373
ms used for selectk 201
想到三种解决方法:
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】 设一个长度为10的整型数组,  0)要求每个元素的值通过scanf输入,输入完成后,  1)请顺序输出这些整数,  2)请倒序输出这些整数,  3)输出这些数中的最大值,最小值
- 设一个长度为10的整型数组, 0)要求每个元素的值通过scanf输入,输入完成后, 1)请顺序输出这些整数, 2)请倒序输出这些整数, 3)输出这些数中的最大值, 4)输出这些数中的最小值
- 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个排好序的数组的一个旋转,输出旋转数组的最小元素。
- 输入一个数组长度,动态创建数组,所有元素随机生成,输出元素中的最大值
- 3.输入一个数组长度,动态创建数组,所有元素随机生成,输出元素中的最大值
- // 1、输入一个数组长度,动态创建数组,所有元素随机生成,输出元素中的最大值
- 函数调用输出一个一维数组中的最大值、最小值、全部元素的和,并将此数组中的值按逆序重新存放。
- 输出一个一维数组中的最大值、最小值、全部元素的和,并将此数组中的值按逆序重新存放。例如原先a[0]~a[4]存放2 8 7 5 3,逆序存放后 a[0]~a[4]变为存放3 5 7 8 2
- [经典面试题]输入一个排好序的数组的一个旋转,输出旋转数组的最小元素。
- Java编程:定义一个int型的一维数组,包含10个元素,分别赋一些随机整数,然后求出所有元素的最大值,最小值,平均值,和值,并输出出来。
- 输入一个数组长度,动态创建数组,所有元素随机生成,输出元素中最大值
- 1,随机一个数组长度,动态创建数组,所有元素随机生成,输出元素中的最大值
- 问题描述:一个长度为2n的(整型)数组元素为 a1 a2 ... an b1 b2 ... bn 要求: 用O(1)的空间代价, 在O(n)时间内把数组变成 a1 b1 a2 b2 a3 b3 ... an bn
- 【c语言】输入一个递增排序的数组的一个旋转,输出旋转数组中的最小元素
- 输入一个数组长度,动态创建数组,所有元素随机生成,输出元素中的最大值
- 输入一个数组长度,动态创建数组,所有元素随机生成,输出元素中的最大值
- 定义一个int型的一维数组,包含10个元素,分别赋一些随机整数,然后求出所有元素的最大值,最小值,平均值,和值,并输出出来。
- 已知元素从小到大排列的两个数组x[]和y[],请写出一个程序算出两个数组彼此之间差的绝对值中最小的一个,这叫做数组的距离
- 输出一个一维数组中的最大值、最小值、全部元素的和,并将此数组中的值按逆序重新存放
- 输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素