題目一:
http://topic.csdn.net/u/20070929/14/b183cd03-d780-4c59-a666-ab127f12f7b1.html
有2.5億個整數(這2.5億個整數存儲在一個數組裏面,至於數組是放在外存還是內存,沒有進一步具體說明);
要求找出這2.5億個數字裏面,不重複的數字的個數;
另外,可用的內存限定爲600M;
要求算法儘量高效,最優;
(不重複的數字的個數,可以理解爲:可以理解爲重複的數只記一次,也可以理解爲重複的數不計算)
如果是第一種理解的話,那麼定義一個int變量count(初值爲2.5億)和位數字bit[512M],遍歷這些數,如果當前的數i已出現過,count--;遍歷完以後,count的值就是隻出現過一次的數的個數了。
如果是第二種理解,那麼首先要將2.5億個int數讀入內存,那麼需要1G,而內存只有600M,所以不可能一次將數據處理完,必須分批進行處理。如果採用位數組,那麼每一個數應該有2個位表示,因爲每一個數有3種狀態:未出現,出現一次,出現多次。
內存必須分爲兩份:1:存儲位數組 2:存儲2.5億個整數。
存儲位數組:因爲內存不夠,無法一次表示所有的整數,所以分兩次處理2.5億個數。第一次處理正整數,計算只出現一次的正整數;第二次處理負整數,計算只出現一次的負整數,那麼存儲整數的空間爲512M,那麼內存還剩下88M。
存儲整數:1個整數4B,2.5億總共10億B。因爲內存總共600M,所以1g/0.5g=2,所以分2次即可以解決。但是,還需要內存空間來存放位數組,所以必須平衡位數組的大小和存放數據的大小。
如何平衡:256M的內存存放位數組,256M的內存存放待處理數據。分4次將數據讀入內存,第一次讀入最高兩位爲11的,第二次讀入最高兩位爲10的,第三次讀入最高位爲01,第四次讀入最高位爲00的。因爲分四次讀入,每一次讀入的數據範圍爲4g/4=1g,每一個數用2位表示,那麼位數組剛好1g/8*2=256M。
Answer1:
http://topic.csdn.net/u/20070929/14/b183cd03-d780-4c59-a666-ab127f12f7b1.html
就這麼道題目,至於用平衡二叉樹嘛
他這個題目出的噁心,說什麼2.5億,其實就算說2500億,也沒有關係的,因爲它們是放在外存上的。
這個問題僅僅是需要計算特定值的數量,所以不需要用排序,也不需要大的空間來保存值本身,只要計數就可以了。
爲了判斷數是否重複,就需要用一個標記來標誌某個數是否出現過,這個可以用桶算法來完成(參考一下桶排序,很有名的,這個算法的關鍵就是在於對每一個值都進行計數,對於有限值域的數組可以有比較高的排序速度),這樣,真正影響內存使用的,是int型的字長,對於32位的int,可以取4g個不同的值,它需要的桶就是512MByte的空間,每個值一位標誌。
在這樣的情況下,會發現一位標誌不夠算法使用,因爲算法需要有3個標誌,分別是:未出現過,第一次出現,多次出現;因此,可以採用18樓的分治法,對不同值域的數,分兩次處理,這樣每個值就可以有兩位來作標誌,同樣只要512mbyte的空間
這樣算法就出來了
(這種方法是計算只有出現一次的元素個數,比如:3,2,2。 那麼出現一次的個數爲1)
1 定義一個長度爲4g的位數組flag(512M),清0,計數器cnt清0
2 循環 讀int值 x,
3
if(x>=0)
if(!flag[x*2])
flag[x*2]=1,flag[x*2+1]=0,cnt++; //當數據第一次出現時,計數
else
if(!flag[x*2+1])
flag[x*2+1]=1,cnt --; //當數據第二次出現時,減少計數,
4 如果還有數據,則回到2
5 flag[]清0
6 循環 讀int值 x,
7
if(x <0)
if(!flag[(-1-x)*2])
flag[(-1-x)*2]=1,flag[(-1-x)*2+1]=0,cnt++;
else
if(!flag[(-1-x)*2+1])
flag[(-1-x)*2+1]=1,cnt --;
8 如果還有數據,則回到6
最後,cnt值就是需要的數值
Answer2:
是隻計算個數,而不要求知道是那些數字
所以最簡單的辦法其實是這樣的
數字進行無格式處理(這樣可以一一對應地址)
定義兩個256M的位數組a[],b[],表示0-2^30位數字的對應關係,
計數器cnt=0;
flag a[2^31]={0};
flag b[a^31]={0};
初始化兩個數組全爲0
第一次掃描,
循環遍歷{
if(x <2^31){
if (a[x]==1&&b[x]==0) {b[x]=1;cnt--;}
if (a[x]==0) {a[x]=1;cnt++;}
}
}
3.循環1,2處理完所有數字,如果輸入數允許刪除,可以在這裏做已處理數字的刪除操作,不處理也沒有多大問題。
第二次掃描,
重新對a[]和b[]填充0;
第二次掃描
循環{
if(!(x <2^31)}{
if (a[x-2^31]==1&&b[x-2^31]){b[x-2^31]=1;cnt--;}
if ([x-2^31]==0 ){a[x-2^31]=1;cnt++;}
}
}
這樣只需要兩次循環就搞定了,複雜度是一個常數啊。
Answer3:(感覺有問題)
看來問題和各位的見解,其實我個人覺得不用那麼麻煩吧。用HASH映射一下,只是這個HASH不能衝突。就算2.5個數字全部都不一樣的也都能解決。
2.5億位bit標誌2.5億個數是否出現過。
2.5億bit也就不到30M的空間。
這樣剩下的500多M可以用來讀取數字,一個INT算32位的話,也就4個字節
我一次讀1.25億個數也不到500M的空間。大文件的讀取可以採用內存映射的方法,加快讀取速度,這樣減少了IO讀取時間。我的步驟是
1.分配30M內存空間,全部置0,讀取文件內容一次1.25億個具體不限制,利用HASH函數(n=k)n是數組的位置,k是讀取到的數
如果爲0則置爲1,1的話置2,2的話就不管了。
2.再掃描一次,0的表示沒出現過的,1的表示只出現一個的,2的表示至少出現過2次的。這裏只統計1的個數。
時間效率也就2n,應該可以接受。
其實如果內存限制的話,並不用讀取1.25億個一次。根據內存大小讀取多次,只是增加了IO時間而已。
至於上面各位說的,平衡數想法很好,就是效率不高吧。排序的也這裏用不着。請大家賜教。
Answer4:
兼回superdullwolf的問詢
-----------------------
其實我的算法和18樓的是一樣的,兩個數值就是爲了記錄一個數字的三種狀態的方法,b[]相當於a[]的對應高位,我用到了三種狀態
狀態 1 2 3
------------------
a[] 0 1 1
b[] 0 0 1
------------------
這樣一個數是否已經統計就涉及這樣三個狀態的轉換,要記錄一組數中只出現一次的數的個數count是這樣的過程進行統計的
if (a[x]==0) {count++;a[x]=1;}
else if (a[x]==1&b[x]==0) {count--;b[x]=1;}
else if (b[x]){;}
這樣所有的數遍歷一次後count就是所求的了,但這要求a[]和b[]的空間要求滿足數值的範圍,這正是這個題目的難點,空間是不足的,只能滿足部分啊,18樓就犯了這樣錯誤,通過計算,我們知道只需要對數值範圍分成兩組就可以了,範圍分組僅僅是涉及最高位,這就是我的算法要點了。
我前面把那個邏輯判斷進行了優化,這樣就減少了else的出現,就成了:
if (a[x]==1&&b[x]==0) {count--;b[x]=1;}
if (a[x]==0) {count++;a[x]=1;}
也可以寫成
if (a[x]&&!b[x]) {count--;b[x]=1;}
if (!a[x]) {count++;a[x]=1;}
-----------------------------------------------------------------------------------------------------------------------
所以我的算法是0(n)的算法,只需要遍歷2次。
Answer5:
2.5億,理論上結果數據(重複)最大爲500M,而全部整形的狀態表示爲512M,所以不可能利用600M可用內存一次性處理全部結果,寫外部文件是必須的
18樓的算法是可行的,考慮到是考試題,應該還有優化的可能,兩次掃描的成本(如此大的數據量)是不是太高?
提供一個想法,不一定更好
1分配512兆存儲空間(M),用一個bit表示一個整形的狀態:,初始化(M)爲0
2讀取數據,若0,置位1;若1,重複,加入隨機結果數據,必要時(count達到一定量)寫入結果文件(F)(畢竟還有幾十M空間可用)
3第二步結束,所有重複的數據(其中還有重複)已經全部寫入文件(F),理論上最壞爲2.5億-1
4重新初始化(M)爲0,讀取結果文件(F)數據,置位1,即爲最終結果
在數據均勻分佈時,效果應該優於2次掃描
在2次以上重複數據特別多時,寫文件的成本增加,但是掃描數據的成本 <2.5億*2
Answer6:
回:xwy6509
內存地址是連續的
判斷一個整數的標誌:
1、該整數值/8(二進制右移3位),即爲地址偏移值,加上分配內存的首字節地址,即爲實際存儲地址(byte)
2、該整數值%8(取該整數最後一字節的最後三位),即爲該整數在字節中flag位置(0-7 :bit)
3、通過位運算&查詢狀態,|設置狀態,位掩碼爲0x80,0x40,0x20,0x10,...0x01
簡單的加減和移位、位運算,是最高效的
排序算法不好的原因有二:
1、需要極大的存儲空間
2、需要更多的運算判斷
Answer7:
有點意思! 難度在於一維的bit 無法保持有無以外的第三狀態
我的想法
先開512BIt 然後 再開30 M bit 保存2.5 數據對應的位置
第一次 讀數據,沒有置1有的話,在30M 相應的位數上設置1
第二次 再讀一次, 30M bit 有1 的 就把512M 相應的設置爲0
讀完 512bit 裏面就是所有沒重複的數字
讀一下有多少 就有結果了。
Answer8:
按照oo的說法來做,很難去求只出現一次的數的個數,因爲 一個bit只能表示兩種狀態, 只能表示相應的數有或者沒有,假如我們爲每個數設置 2 bits 來表徵它的狀態就好了,這樣這個flag的取值可以爲 0,1,2,3,0表示該數不存在,1表示存在一次,2表示存在多次, 但是這樣的話, 需要1G的空間來存放flag, 那麼可以分開來,遍歷2.5億數據兩次,第一次我只算正數,第二次我只算負數,每次都還是申請512M空間。
1. 申請512M空間, 都清零
2. 首先遍歷2.5億數據,假如爲正數,那麼 檢查該數對應flag, 假如爲0,則置1,count++, 假如flag爲1,則置2, count--, 假如flag爲2,則繼續
3.再遍歷2.5億數據,假如爲負數,那麼使用相同方法來檢測
4. 最後的count就是結果了。
Answer9:
BYTE marks[2^29];//開一個512M的數組標記
BYTE repmarks[2^25];//32M 32M*8>2.5開一個32M的數組標記重覆數字所在傳入數組的位置
const DWORD exambufs[]={1,2,2};
const BYTE bitmarks[8]={1,2,4,8,16,32,64,128};
DWORD Calc1TimeNum(DWORD *pBuf,DWORD bufcount)
{
DWORD dw ;
DWORD count = 0 ;//計算所有出現過的數字
DWORD count2 = 0 ;//計算只重複出現的數字
memset(marks,0,sizeof(marks));
memset(repmarks,0,sizeof(repmarks));
ASSERT(sizeof(repmarks)*8>=bufcount);
for(dw=0;dw <bufcount;dw++)
{
if(marks[pBuf[dw]>>3]&bitmarks[pBuf[dw]&7])
{
repmarks[dw>>3] |= bitmarks[dw&7];//把重複的位置標出來
}
else
{
count ++;
marks[pBuf[dw]>>3] |= bitmarks[pBuf[dw]&7];
}
}
memset(marks,0,sizeof(marks));
for(dw=0;dw <bufcount;dw++)
{
if(repmarks[dw>>3] & bitmarks[dw&7])//判斷到重複標誌
{
if(marks[pBuf[dw]>>3]&bitmarks[pBuf[dw]&7])
{
}
else
{
count2 ++;
marks[pBuf[dw]>>3] |= bitmarks[pBuf[dw]&7];
}
}
}
return count-count2;
}
Answer10:
有想法還是得有編碼
閒時做做這樣的習題還是不錯的
簡單優化一下,掃描數據的時間節約了一倍:25秒!
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <memory.h>
int ParseBy512()
{
static unsigned char nBit0[8]={0x7F,0xBF,0xDF,0xEF,0xF7,0xFB,0xFD,0xFE};
static unsigned char nBit1[8]={0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};
FILE *f=fopen("g://test.dat","rb");
if(f == NULL) return -1;
unsigned char *bufA=new unsigned char[536870912];//512M
unsigned char *bufB=new unsigned char[31250000];//30M
unsigned int *bufC=new unsigned int[8];
memset(bufA,0,536870912);
memset(bufB,0,31250000);
unsigned int nNumber;
unsigned char n;
unsigned int i=0;
unsigned int j=0;
printf("buf alloc ok/r/n");
time_t tmb;
time(&tmb);
for(;i <31250000;i++)
{
fread(bufC,sizeof(unsigned int),8,f);
for(j=0;j <8;j++)
{
n = bufC[j] & 0x7;
nNumber =bufC[j] >> 3;
if(bufA[nNumber] & nBit1[n])
{
bufB[i] |= nBit1[j];
bufA[nNumber] &= nBit0[n];
}
else
{
bufA[nNumber] |= nBit1[n];
}
}
}
time_t tm1;
time(&tm1);
printf("scan data ok! %d sec/r/n",tm1-tmb);
for(i=0;i <31250000;i++)
{
for(n=0;n <8;n++)
{
if(bufB[i] & nBit1[n])
{
fseek(f,((i < <3)+n)*sizeof(unsigned int),SEEK_SET);
fread(&nNumber,sizeof(unsigned int),1,f);
bufA[nNumber >> 3] &= nBit0[nNumber & 0x7];
}
}
}
fclose(f);
time_t tm2;
time(&tm2);
printf("treat data ok! %d sec/r/n",tm2-tm1);
f=fopen("g://result.dat","w+");
if(f == NULL) return -1;
int iRsult=0;
for(i=0;i <536870912;i++)
{
for(n=0;n <8;n++)
{
if(bufA[i] & nBit1[n])
{
nNumber = (i < <3) + n;
fwrite(&nNumber,sizeof(unsigned int),1,f);
iRsult ++;
}
}
}
fclose(f);
delete bufA;
delete bufB;
delete bufC;
time_t tme;
time(&tme);
printf("time:%d sec,Rsult=%d/r/n",tme-tmb,iRsult);
return 0;
}
int GenrateData()
{
FILE *f=fopen("g://test.dat","w+");
if(f == NULL) return -1;
unsigned int nNumber;
srand(time(NULL));
time_t tmb;
time(&tmb);
for(int i=0;i <250000000;i++)
{
nNumber = rand();
nNumber < <= 16;
nNumber += rand();
fwrite(&nNumber,sizeof(unsigned int),1,f);
}
fclose(f);
time_t tme;
time(&tme);
printf("time:%d sec:i=%d",tme-tmb,i);
return 0;
}
int main(int argc, char ** argv)
{
if(argc > 1) GenrateData();
else
{
ParseBy512();
FILE *f=fopen("g://result.dat","rb");
if(f == NULL) return -1;
unsigned int nNumber;
for(int i=0;i <20;i++)
{
fread(&nNumber,sizeof(unsigned int),1,f);
printf("%u/r/n",nNumber);
}
for(i=1;i <=20;i++)
{
fseek(f,-(i*4),SEEK_END);
fread(&nNumber,sizeof(unsigned int),1,f);
printf("%u/r/n",nNumber);
}
fclose(f);
}
return 0;
}
題目二:
有10億個整數,要求選取重複次數最多的100個整數
Answer1:
http://wbfeixue.blog.163.com/blog/static/83299901200811334432834/
下面是一種解答,如果不願意看的話,很簡單,遍歷數組,然後用hash表統計,value爲出現的次數,然後findmax 100次,下面的第五問解答很好
要解答這個問題,首先要弄清楚下面幾個條件。
(1)有內存限制嗎?
(2)整數的範圍是多少?有符號,無符號,32位還是64位?
(3)整數集的內容大嗎?(即出現的整數空間的大小大嗎?)
(4)如果只需要求模糊解,怎麼解?
(5)求數組中的第k大元素?
(6)相關問題:求一個整數列中出現次數最多的整數
(7)相關問題:有一個整數數組,請求出兩兩之差絕對值最小的值,記住,只要得出最小值即可,不需要求出是哪兩個數。
(1)如果沒有內存限制,且假設是32位無符號的整數。最方便的辦法就是建立一個整形數組,int hash[2^32](贊不考慮程序的虛地址空間上限),然後對這10億個數進行一次遍歷,這樣,可以得到這2^32個數各自出現的次數,再對這個hash數組進行取第k大元素,100次後,就可以取出這出現次數最多的前100個數。遍歷10億個數的時間複雜度是O(n),n=10^10,求第k大元素的時間複雜度是O(m),m=2^32(=4294967296),那麼本算法的時間複雜度是O(n),空間複雜度是O(s),s=2^32。內存要2^32*4=16G
(2)如果有內存限制,或者必須滿足程序虛地址空間上限。那麼可以對整數空間進行分段處理,比如只提供512M內存,則將2^32個整數劃分成32個空間0~2^(27)-1,2^(27)~2^(28)-1,...,31*2^(27)~2^(32)-1。對原來的10億個數遍歷32次,每次遍歷,得到每個空間的整數的出現次數,並求出此空間中,出現次數最多的前100個整數,保存下來。這樣32次之後,就得到了出現次數前3200的整數,再對這3200個整數取第k大元素,得到出現次數最多的前100個整數。這個算法的時間複雜度也是O(n),空間複雜度降低多少不知道,但是內存使用降低不少。
(3)如果整數空間比較小,也就是說這10億個數中有很多重複的數,最方便的辦法估計就是維護一個HashTable對象ht,key就是整數值,value就是該整數值出現的次數。遍歷這10億個元素,得到ht後再對這個ht求第k大元素。那麼這個算法的時間複雜度就是O(n),n=10^10,空間複雜度是O(m),m爲整數空間大小。
(4)隨機採樣(或者將原來的順序打亂,然後再順序採樣)。對樣本中的整數進行出現次數的統計,這個時候採用HashTable的辦法最好,時間複雜度是O(n)。如果對使用的空間有所限制,那麼只能對該樣本進行排序,再對排序後的樣本進行100次遍歷得到出現次數最多的前100個整數,則時間複雜度是O(nlogn),空間複雜度是O(1)。
(5)好像有兩種算法。假設要求數組a[1...n]中第k大元素。
(a)遞歸快排算法。若n<44(經驗值)則直接排序返回第k大元素,否則,將1到n分成n/5個組,每個組5個元素,然後求這n/5個組的每組的中項元素,再求這n/5箇中項元素的中項元素mm(注意,這裏也可以用遞歸調用自身的方法)。然後對數組a根據mm分成三組,a1中的所有元素小於mm,a2中的所有元素等於mm,a3中的所有元素大於mm,如果|a1|>=k,則第k大元素在a1中,如果|a1|+|a2|>=k|a1|,則第k大元素就是mm,如果k>|a1|+|a2|,則第k大元素在a3中,再繼續遞歸調用。這個算法的時間複雜度是O(n)。(注意,這裏的中項mm也可以隨機選擇a中的元素,其時間複雜度也近似於O(n),而且係數也比較小)。
(b)基於位查找(僅對於無符號整數的查找)。將32位整數的二進制位分爲4段,每段8位,先比較a中所有元素高8位,找出第k大元素高8位的範圍,再對應這高8位的範圍在次高八位中找第k大元素的範圍,...這樣4次之後就可以找到第k大元素的。可以舉個例子便於理解,在10個3位整數中找第k大元素,將3位分成3段,每段1位,每位之可能是0,1。如果這10個數的最高位0的個數m大於等於k,則第k大元素的最高位爲0,再在最高位爲0的元素中找次高位爲第k大元素;如果10個數中最高位0的個數m大於k,則在最高位爲1的元素中找此高位爲第m-k大元素。...
(6)這個問題是前面那個問題的特例。有沒有特殊的解法使效率又提高一些呢?我覺得沒有,因爲1和100本來就是常數級,和n比它們的差別是忽略不計的。
(7)簡單的解法是對這個數組排序,然後再對排好序的數組進行一次遍歷就可得到兩兩絕對值最差的最小值,時間複雜度是O(nlogn)。網上說求a的min,max和長度n,如果Dmax = (max-min+1)/n = 0,那麼就說明數組a中有重複的元素,直接返回0。但是如果Dmax = (max-min+1)/n > 0,那麼就以Dmax爲箱的長度裝入a的元素,再在箱內和箱間比較。我不懂爲什麼,但是這個空間複雜度是O(max),而且好像如果a是1, 2, 3...100,那麼Dmax就是1了,那麼a不是沒有動嗎?還有人說夠找數組b,b[i] = a[i] - a[i+1],則a[i]-a[j]=b[i]+b[i+1]+...+b[j-1]也不知下文了,看來這個題比較搞啊。就是奧賽題,BS。