查找N个数中第K大的数

来源:互联网 发布:js验证 是否阅读协议 编辑:程序博客网 时间:2024/04/30 11:40
方法一:将一组数放入数组中,升排序。并返回第 ( length - k )个元素
这里消耗的时间就是排序用的时间,用快速排序则为:O( N log N )


代码:

/** * 先排序后获取 * @return * * Date  :2012-7-4 * Author :GongQiang */public int sortThenGet( int k ){Collections.sort( list );return list.get( list.size() - k );}


方法二:构造一个 K 长度的数组,将前K位数复制过来并排序(降序)。然后依次将 K+1 到 N 位的数比较并插入 K 长度的数组中。返回最后一个即可。

这时间度为:O( N*K ) 如果 K = N/2  则复杂度为 O( N*N )


代码:

/** * 先取出前K个数排序,再从后面依次插入 * @param k * @return * * Date  :2012-7-4 * Author :GongQiang */public int kSortThenCompare( int k ){List<Integer> result = new ArrayList<Integer>( k );for( int i=0 ; i<k ; i++ ){result.add( list.get(i) );}//前K位数,按照从大到小排序Collections.sort( result, new Comparator<Integer>(){public int compare(Integer o1, Integer o2) {if( o1 > o2 ){return -1;}if( o1< o2 ){return 1;}return 0;}});// 后 K+1 位数与前面的有序数组比较,插入适当的位置for( int i=k ; i<list.size() ; i++ ){int j = k-1;while( j>=0 && list.get(i) > result.get(j) ){j--;}if( 0<=j && j<k-1 && list.get(i) < result.get(j) ){result.add( j+1, list.get(i) );}else if( j==-1 ){ //结束条件是 j==-1result.add( 0, list.get(i) );}}return result.get( k-1 );}


方法三:将 N 个数构造成一个“大堆”,然后删除堆的根 K 次,最后一次即为结果。

 构造堆的最坏用时:O( N )

每次删除根用时:O( log N )

则中的运行时间为: O( N + k*log N )

如果 K = O(  N/ log N ) ,则总共用时就是构造堆的时间,即 O( N )

如果 K 很大,则总时间为 O( K* log N)

如果 K = N/2,则总时间为Θ( N*log N )


代码:

/** * 先构建一个堆,然后获取 * @param k * @return * * Date  :2012-7-4 * Author :GongQiang */public int buildHeapThenGet( int k ){PriorityQueue<Integer> heapQueue = new PriorityQueue<Integer>( NUMBER_COUNT, new Comparator<Integer>(){ //这里是取第K大的元素,因而要改变排序规则public int compare(Integer o1, Integer o2) {if( o1 > o2 ){return -1;}if( o1< o2 ){return 1;}return 0;}});for( int i=0 ; i<list.size() ; i++ ){heapQueue.add( list.get(i) );}int result=0;for( int i=0 ; i<k ; i++ ){result = heapQueue.remove();}return result;}


方法四:思路和方法二一样,只不过用堆来实现。

构造堆的时间:O( K )

处理每个其余元素的时间:O( 1 )

检测是否进入堆的时间:O( log K )

总时间:O( K + (N-K)*log K ) = O( N* log K )

该算法找出中位数的时间界面:Θ( N*log N )

注意:这里使用优先队列提供的堆来操作,这样

检测的时间为O( log K )

删除的时间为O( log K )

插入的时间为O( log K )

所以,这里的代码时间复杂度比单纯在堆上操作要多很多。


代码:

/** * 前K个数构造一个堆,然后进行比较插入 * @param k * @return * * Date  :2012-7-4 * Author :GongQiang */public int heapOfFistKThenSert( int k ){PriorityQueue<Integer> heapQueue = new PriorityQueue<Integer>( k );for( int i=0 ; i<k ; i++ ){heapQueue.add( list.get(i) );}for( int j = k ; j<list.size() ; j++ ){int queueLength = k;int flag = 0;while( queueLength >0 ){if( heapQueue.peek() < list.get(j) ){flag =1;break;}queueLength--;}if( flag == 1 ){heapQueue.poll();heapQueue.offer( list.get(j) );}}return heapQueue.peek();}

四种算法在 10 万个数值中,查找第100大,1000大,10000大实际用时

先排序在获取:用时间:69547064, 100大数:99903985用时间:64447309, 1000大数:98963233用时间:63601693, 10000大数:89862625先排序前K个数,然后插入,最后获取:用时间:14299865, 100大数:99903985用时间:94025432, 1000大数:98963233用时间:4053122244, 10000大数:89857698先构建一个堆,然后获取:用时间:29191262, 100大数:99903985用时间:11743673, 1000大数:98963233用时间:21491442, 10000大数:89862625先构建一个堆(前K个数),依次比较插入,最后获取:用时间:199040953, 100大数:99903985用时间:1786736869, 1000大数:98963233用时间:11739682175, 10000大数:89862625

可以看出,方法三最优。

方法五:随机选择(参考快速排序的思想)

/** * 随机选择 * @param list * @param start * @param end * @param i * @return * * Date  :2012-10-25 * Author :GongQiang */int randomSelect( List<Integer> list, int start, int end, int i ){if( start == end ){return list.get(start);}int q = randomPartition(list, start, end);int k = end - q + 1;if( i == k ){return list.get( q );}else if( i<k ){return randomSelect(list, q+1, end, i);}else{return randomSelect(list, start, q-1, i-k);}}private int randomPartition( List<Integer> list, int start, int end ){int i = (int)(random.nextFloat()*( end-start )) + start;swap( list, i, end );return partition(list, start, end);}private int partition( List<Integer> list, int start, int end ){int temp = list.get(end);int i = start - 1;for( int j=start; j<end; j++ ){if( list.get(j) <= temp ){i++;swap( list, i, j);}}swap( list, i+1, end);return i+1;}private void swap( List<Integer> list, int i, int j ){int temp = list.get(i);list.set(i, list.get(j));list.set(j, temp);}

运行性能比较(一百万中查询):

先排序在获取:用时间:1073446509, 100大数:99991468用时间:1086298487, 1000大数:99897550用时间:995448554, 10000大数:98988059用时间:996302124, 550000大数:45011888先构建一个堆,然后获取:用时间:98659237, 100大数:99991468用时间:92088339, 1000大数:99897550用时间:114273553, 10000大数:98988059用时间:1590825451, 550000大数:45011888随机选择用时间:113330378, 100大数:99991468用时间:121606562, 1000大数:99897550用时间:123834509, 10000大数:98988059用时间:231643029, 550000大数:45011888

可以看出,【随机选择】性能很稳定,而且较优!