从一组无序的整数中找出前N大的数

    经常会遇见这样的问题,如何从一组序列中找出最大的N个数,比如从一个班级的成绩中找出总成绩的前三名。可能会有一个比较简单的做法就是先将这组序列排序,然后前N个值自然而然就得到了。这对于比较少的序列,是可行的,比如前面说的一个班的前三名,但是对于数据量特别庞大的现实应用中,就不太现实了,例如我们经常用到的搜索引擎,它应该不会对她搜到的所有的页面先进行排序然后再返回前N个搜索结果吧,这样的话花费在排序上的时间消耗的也太大了。


    对于这样一个问题应该怎么做呢,可以使用一种数据结构--堆。假如问题是求一个很大的整数序列中的前M个,我们就可以建立一个最多包含M + 1个元素的堆,然后逐个将序列中的元素插入堆中,元素插入堆后堆的元素个数大于M,就执行删除最小元素的操作。


    那么应该建立一个什么样的堆呢,是大根堆还是小根堆呢,当然是小跟堆了,要不怎么删除最小的元素呢。有了上面的分析,我们就可以明确一些问题了,那就是这个小程序的api了。

public class TopM{
	private class MinHeap{
		private int M; //堆中需要保存多少个元素
		private int N; //堆中实际存在的元素个数
		private int[] heap = null; 
		//heap[0]作为哨兵, heap[M +1]作为最后最后插入的元素
		public MinHeap(int M){this.M = M; heap = new int[M + 2];}
		private void insert(int x){}//向堆中插入一个元素x
		private int delMin(){} //删除堆中最小的元素,并返回最小元素的值
		private void swim(int k){} //上浮操作,从k处开始向上将堆调整成小根堆
		private void sink(int k){}//下沉操作,从k处向下将堆调整成小根堆
		private int size(){} //返回元素的堆中元素的个数
		private String toString(){}//返回堆内元素的情况
		private void exch(int i, int j){} //交换i和j位置的元素
	}
	public void topM(int[] a, int M){} //a代表大数组, M表示去前多少个元素, 找到前M的元素,然后打印输出
	public static int[] generateArray(int length){} //随机生成长度为length的整形数组
	public static void main(String[] args){
		int[] a = generateArray(length);
		new TopM().topM(a, M);
	}
}

 

api有了,咱们就开始一步步实现吧。
1.首先是小根堆的插入函数的实现,很简单,先将元素插入N后,然后再从N向上调整堆,如果插入元素后N到达堆数组的最后一个位置,即N == M + 1,先将堆调整好后,再删除最小的元素

private void insert(int x){
	heap[++N] = x;
	swim(N);
	if(N > M)
		delMin();
}


2.删除最小元素的操作,delMin,先将堆顶元素返回,然后用堆底元素代替堆顶元素,将N-1,从底部开始向下调整堆,

private int delMin(){
	int min = heap[1];
	heap[1] = heap[N];
	N--;
	sink(1);
	return min;
}


3.向上调整堆

private void swim(int k){
	while(k > 1 && heap[k] < heap[k / 2]){
		exch(k, k/2);
		k = k/2;
	}
}

 

4.向下调整堆。

private void sink(int k){
	while(2 * k <= N){
		int j = 2 * k;
		if(j < N && heap[j] > heap[j + 1]) j++;
		if(heap[k] < heap[j]) break;
		exch(k, j);
		k = j;
	}
}

 

5.exch的实现

private void exch(int i, int j){
	if(i < 0 || j < 0 || i > heap.length || j > heap.length || i == j)
		return;
	int t = heap[i];
	heap[i] = heap[j];
	heap[j] = t;
}

 

6.返回堆内元素的个数

private int size(){
	return N;
}


7.toString的实现

public String toString(){
	String str = N + ":[";
	for(int i = 1; i <= N; i++){
		str += heap[i] + ",";
	}
	str +="]";
	return str;
}

 

8.topM的实现

public void topM(int[] a, int M){
	MinHeap minHeap = new MinHeap(M);
	for(int i = 0; i < a.length; i++){
		minHeap.insert(a[i]);
	}
	System.out.println(minHeap);
}

 

9.generateArray的实现

public static int[] generateArray(int length){
	Random rand = new Random(47);
	int[] a = new int[length];
	for(int i = 0; i < length; i++){
		a[i] = rand.nextInt(length);
	}
	return a;
}

 

好了,将上面的程序模块组合在一块,就是一个完整的程序了,程序的功能很清楚了,就是输出一个随机序列的前M个值。最后整个程序的样子就出来了。

import java.util.Arrays;
import java.util.Random;

public class TopM {
	
	private class MinHeap {
		private int M; // 堆中需要保存多少个元素
		private int N; // 堆中实际存在的元素个数
		private int[] heap = null; 
		public MinHeap(int M) {
			this.M = M;
			heap = new int[M + 2];
		}

		private void insert(int x){
			heap[++N] = x;
			swim(N);
			if(N > M)
				delMin();
		}

		private int delMin(){
			int min = heap[1];
			heap[1] = heap[N];
			N--;
			sink(1);
			return min;
		}

		private void swim(int k) {
			while (k > 1 && heap[k] < heap[k / 2]) {
				exch(k, k / 2);
				k = k / 2;
			}
		}

		private void sink(int k) {
			while (2 * k <= N) {
				int j = 2 * k;
				if (j < N && heap[j] > heap[j + 1])
					j++;
				if (heap[k] < heap[j])
					break;
				exch(k, j);
				k = j;
			}
		}

		private int size() {
			return N;
		}

		private void exch(int i, int j){
			if(i < 0 || j < 0 || i > heap.length || j > heap.length || i == j)
				return;
			int t = heap[i];
			heap[i] = heap[j];
			heap[j] = t;
		}

		public String toString() {
			String str = N + " : [";
			for (int i = 1; i <= N; i++) {
				str += heap[i] + ",";
			}
			str += "]";
			return str;
		}
	}

	public void topM(int[] a, int M) {
		MinHeap minHeap = new MinHeap(M);
		for (int i = 0; i < a.length; i++) {
			minHeap.insert(a[i]);
		}
		System.out.println(minHeap);
	}

	public static int[] generateArray(int length) {
		Random rand = new Random(47);
		int[] a = new int[length];
		for (int i = 0; i < length; i++) {
			a[i] = rand.nextInt(length);
		}
		return a;
	}

	public static void main(String[] args) {
		int length = 20;
		int M = 3;
		int[] a = generateArray(length);
		System.out.println(Arrays.toString(a));
		new TopM().topM(a, M);
	}
}


 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章