現在有這么一道題目:要求從多個的數據中查找出前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已經老化,雙核,雜牌的。
