今天剛買到《編程珠璣》於是開始翻,第一章引出的磁碟排序問題。
問題描述:一個最多包含n個正整數的檔案,每個數都小於n,其中n=10^7,且所有正整數都不重複。求如何將這n個正整數升序排列。
條件:最多有1MB的記憶體空間可用,有充足的磁碟儲存空間。
方法一:多通道法
思想描述:記憶體1MB可以儲存的int(4byte)有10^3*10^3/4=250 000個號碼。而包含正整數的檔案約為10^7個int大小。這意味著無法將所有檔案中的正整數一次讀取進入到記憶體空間中去進行排序演算法,所以一次一次讀出來進行排序。
多通道方法:
第1遍遍曆檔案,將檔案中範圍在1~ 249 999的正整數讀取進入1MB記憶體,排序(可以使用各種排序方法),將排序後的正整數儲存在磁碟檔案temp中
第2遍遍曆檔案,將檔案中範圍在250 000~499 999的正整數讀取進入1MB記憶體,排序,將排序後的正整數加入儲存在磁碟檔案temp中
….
第40遍遍曆檔案,將檔案中範圍在10^7-250 000~10^7的正整數讀取進入1MB記憶體,排序,將排序後的整數加入儲存在磁碟檔案temp中
輸出temp檔案 。
此方法缺點是非常明顯的:需要遍曆40次檔案,意味著讀取輸入檔案40次,並且需要一個和中間檔案temp。
方法二:bit-map方法
我們想使用hash映射,將對應的正整數映射到位元影像集合中。即將正整數映射到bit集合中,每一個bit代表其映射的正整數是否存在。比如{1,2,3,5,8,13}使用下列集合表示:
0 1 1 1 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 0
我們可以使用具有10^7位的字串來表示這個檔案。其中,若且唯若整數i在檔案中存在時候,第i位為1
bitmap需要的儲存空間大小為10^7/8=1.25M,基本可以滿足程式需求,
解決此問題的步驟為:
建立有個10^7位(10^7/8/1024/1024≈1MB)的字串,並將其每一bit設定為0;
讀取包含正整數的檔案,對每一個i,將記憶體中bit[i] 位設定成1.
按位順序讀取字串。當讀取到bit[j] 為1時輸出(int)j。
虛擬碼如下:
for i= [0,n] bit[i]=0;for each i in the input file bit[i]=1;for i=[0,n] if bit[i] == 1 write i on the output file
位元影像的核心在於用位儲存(因為資料無重複,1bit已經足夠表示)加上位映射int原數,立馬解決了記憶體不足的問題,其實還可以用來解決一些海量資料問題。
位元影像法的C語言實現:
/* Copyright (C) 1999 Lucent Technologies *//* From 'Programming Pearls' by Jon Bentley *//* bitsort.c -- bitmap sort from Column 1 * Sort distinct integers in the range [0..N-1] */#include <stdio.h>#define BITSPERWORD 32#define SHIFT 5#define MASK 0x1F#define N 10000000int a[1 + N/BITSPERWORD];void set(int i) { a[i>>SHIFT] |= (1<<(i & MASK)); }void clr(int i) { a[i>>SHIFT] &= ~(1<<(i & MASK)); }int test(int i){ return a[i>>SHIFT] & (1<<(i & MASK)); }int main(){int i;for (i = 0; i < N; i++)clr(i);while (scanf("%d", &i) != EOF)set(i);for (i = 0; i < N; i++)if (test(i))printf("%d\n", i);return 0;}
STL中的bistset封裝了一些位的寫法,上面代碼用C++的bitset修改如下:
#include <iostream>#include <bitset>using namespace std;const int N = 10000000;int main(){bitset<N> bit;int i;while (cin >> i)bit.set(i);for (i = 0; i < N; i++)if (bit.test(i))cout << i << endl;return 0;}
書後提的問題也蠻好
1、如何產生0至n-1之間的k個不同的隨即順序的隨機整數,程式盡量簡短有效。
個人解答:如果稍微偷懶一點,採用java或者c+stl中的set類型,產生隨機數,插入即可;如果是c語言實現,構建紅/黑樹狀結構,插入時比較,相同則捨棄(後來發現這方法是錯的)
2、我們的程式需要1.25M,如果嚴格限制1M呢?
貌似除了跑兩次還真想不出其他法子了
3、航天先驅意識到,需要在外太空極端條件下實現順利書寫,民間流傳美國花費100w美元研發了一種特殊鋼筆解決這個問題,那麼,前蘇聯會怎麼解決這個問題?
這個問題才是喜感萬分啊
關於bit-map的一些面試題:
1、2.5億個整數中找出不重複的整數的個數,記憶體空間不足以容納這2.5億個整數
解決方案:將bit-map擴充一下,用2bit表示一個數即可,0表示未出現,1表示出現一次,2表示出現2次及以上,在遍曆這些數的時候,如果對應位置的值是0,則將其置為1;如果是1,將其置為2;如果是2,則保持不變
2、給40億個不重複的unsigned int的整數,沒排過序的,然後再給一個數,如何快速判斷這個數是否在那40億個數當中
解決方案:我們使用625M的字串。每一位設定為0. 將40億個unsign int 遍曆一遍。使用位元影像法將字串中的對應位轉化為1。 讀取“再給一個數i” 查看bit[i] 是否為1,1則有存在,0則不存在。
關於bit-map,個人感覺原理感覺與計數排序挺像的,不過與計數排序的資料有個區別:沒有重複。畢竟可能有重複資料情況下,只能用int統計數字個數,位元組退化為計數排序的計數數組C[]
就這樣吧,每天消滅一點。。。
參考網址:
1、http://www.cnblogs.com/yjf512/archive/2010/11/04/1868899.html
2、海量儲存的牛人部落格貌似網域名稱失效,就不貼了