經常會遇見這樣的問題,如何從一組序列中找出最大的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);
}
}