查找N个数中第K大的数

方法一:将一组数放入数组中,升排序。并返回第 ( 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==-1
				result.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

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值