原創:從海量數據中查找出前k個最小或最大值的算法(java)


現在有這么一道題目:要求從多個的數據中查找出前K個最小或最大值

分析:有多種方案可以實現。一、最容易想到的是先對數據快速排序,然后輸出前k個數字。

               二、先定義容量為k的數組,從源數據中取出前k個填充此數組,調整此數組的最大值maxValue到首位,然后對剩下的n-k個數據迭代,對於每個遍歷到的數字x,如果x < maxValue,用x把maxValue替換掉,然后調整數組最大值的位置。

             三、基於二的思路,維護容量為k的堆,從源數據中取出前k個填充實例化堆,調整此堆中的最大值maxValue到堆頂,然后對剩下的n-k個數據迭代,對於每個遍歷到的數字x,如果x < maxValue,用x把maxValue替換掉,然后調整堆最大值的位置。

             還有其他的方案,省略。

下面分別計算時間復雜度和空間復雜度。

                     時間復雜度                                    空間復雜度

方案一         O( n*lgn + k)          在棧中定義數組,幾乎不占用堆內存

方案二         O(K + (n-k)*k)          在棧中定義數組,幾乎不占用堆內存 

方案三         O(K + (n-k)*lgk)         O(k)

當n趨於無窮大的時候,很顯然,方案三是最有選擇,而且,當數據量非常的時候,方案一根本行不通,因為一個數組根本存不下海量數據,實際上,也幾乎沒有一個人這樣寫算法。快排的時間復雜度是n*lgn,如果把數據放入堆中,事實證明,在堆中對數據的操作,時間復雜度均為lgk,其中k為堆的容量。今天寫了方案三的java代碼,分享如下:

package findMinNumIncludedTopN;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

/**
* 從海量數據中查找出前k個最大值,精確時間復雜度為:K + (n - K) * lgk,空間復雜度為 O(k),目前為所有算法中最優算法
*
* @author TongXueQiang
* @date 2016/03/08
* @since JDK 1.7
*/
public class FindMinNumIncluedTopN {
/**
* 從海量數據中查找出前k個最大值
*
* @param k
* @return
* @throws IOException
*/
public int[] findMinNumIncluedTopN(int k) throws IOException {
Long start = System.nanoTime();

int[] array = new int[k];
int index = 0;
// 從文件導入海量數據
BufferedReader reader = new BufferedReader(new FileReader(new File("F:/number.txt")));
String text = null;
// 先讀出前n條數據,構建堆
do {
text = reader.readLine();
if (text != null) {
array[index] = Integer.parseInt(text);
}
index ++;
} while (text != null && index <= k - 1);

MinHeap heap = new MinHeap(array);//初始化堆
for (int i : heap.heap) {
System.out.print(i + " ");
}

heap.BuildMinHeap();//構建小頂堆
System.out.println();
System.out.println("構建小頂堆之后:");
for (int i : heap.heap) {
System.out.print(i + " ");
}
System.out.println();
// 遍歷文件中剩余的n(文件數據容量,假設為無限大)-k條數據,如果讀到的數據比heap[0]大,就替換之,同時更新堆
while (text != null) {
text = reader.readLine();
if (text != null && !"".equals(text.trim())) {
if (Integer.parseInt(text) > heap.heap[0]) {
heap.heap[0] = Integer.parseInt(text);
heap.Minify(0);//調整小頂堆
}
}
}
//最后對堆進行排序(降序)
heap.HeapSort();

Long end = System.nanoTime();
long time = end - start;
System.out.println("用時:"+ time + "納秒");
for (int i : heap.heap) {
System.out.println(i);
}
return heap.heap;
}
}

package findMinNumIncludedTopN;
/**
* 大頂堆
* @author TongXueQiang
* @date 2016/03/09
* @since JDK 1.7
*/
public class MaxHeap {
int[] heap;
int heapsize;

public MaxHeap(int[] array) {
this.heap = array;
this.heapsize = heap.length;
}

public void BuildMaxHeap() {
for (int i = heapsize / 2 - 1; i >= 0; i--) {
Maxify(i);// 依次向上將當前子樹最大堆化
}
}

public void HeapSort() {
for (int i = 0; i < heap.length; i++) {
// 執行n次,將每個當前最大的值放到堆末尾
swap(heap,0,heapsize-1);
heapsize--;
Maxify(0);
}
}

public void Maxify(int i) {
int l = 2*i + 1;
int r = 2*i + 2;
int largest;

if (l < heapsize && heap[l] > heap[i])
largest = l;
else
largest = i;
if (r < heapsize && heap[r] > heap[largest])
largest = r;
if (largest == i || largest >= heapsize)// 如果largest等於i說明i是最大元素
// largest超出heap范圍說明不存在比i節點大的子女
return;
swap(heap,i,largest);
Maxify(largest);
}

private void swap(int[] heap, int i, int largest) {
int tmp = heap[i];// 交換i與largest對應的元素位置,在largest位置遞歸調用maxify
heap[i] = heap[largest];
heap[largest] = tmp;
}

public void IncreaseValue(int i, int val) {
heap[i] = val;
if (i >= heapsize || i <= 0 || heap[i] >= val)
return;
int p = Parent(i);
if (heap[p] >= val)
return;
heap[i] = heap[p];
IncreaseValue(p, val);
}

private int Parent(int i) {
return (i - 1) / 2;
}
}

 

package findMinNumIncludedTopN;
/**
* 小頂堆
* @author TongXueQiang
* @date 2016/03/09
* @since JDK 1.7
*/
public class MinHeap {
int[] heap;
int heapsize;

public MinHeap(int[] array) {
this.heap = array;
this.heapsize = heap.length;
}

/**
* 構建小頂堆
*/
public void BuildMinHeap() {
for (int i = heapsize / 2 - 1; i >= 0; i--) {
Minify(i);// 依次向上將當前子樹最大堆化
}
}

/**
* 堆排序
*/
public void HeapSort() {
for (int i = 0; i < heap.length; i++) {
// 執行n次,將每個當前最大的值放到堆末尾
swap(heap,0,heapsize-1);
heapsize--;
Minify(0);
}
}

/**
* 對非葉節點調整
* @param i
*/
public void Minify(int i) {
int l = 2*i + 1;
int r = 2*i + 2;
int min;

if (l < heapsize && heap[l] < heap[i])
min = l;
else
min = i;
if (r < heapsize && heap[r] < heap[min])
min = r;
if (min == i || min >= heapsize)// 如果largest等於i說明i是最大元素
// largest超出heap范圍說明不存在比i節點大的子女
return;
swap(heap,i,min);
Minify(min);
}

private void swap(int[] heap, int i, int min) {
int tmp = heap[i];// 交換i與largest對應的元素位置,在largest位置遞歸調用maxify
heap[i] = heap[min];
heap[min] = tmp;
}

public void IncreaseValue(int i, int val) {
heap[i] = val;
if (i >= heapsize || i <= 0 || heap[i] >= val)
return;
int p = Parent(i);
if (heap[p] >= val)
return;
heap[i] = heap[p];
IncreaseValue(p, val);
}

private int Parent(int i) {
return (i - 1) / 2;
}

}

從一個14.2M的文件中讀取數據(大約有130多萬條數據),找出前4個最小值,耗時平均為0.6秒,效果很好,而且本人的電腦硬件配置相當爛,CPU已經老化,雙核,雜牌的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM