在日常的演算法中,查詢是乙個經常涉及到的話題,而如何提高查詢的速度,也是很多程式設計師、軟體研究的話題。
先看乙個例子。
有這樣乙個資料型別s :
學生姓名(name),性別(***),年齡(age)。。。,
現在假設有這樣乙個需求;
檔案a、b中分別存放大量s 的記錄,需要將a、b中重複的記錄去掉。
我們用c**來演示今天的話題:
typedef struct tagstudent
s ;
對於這樣乙個問題,簡單的做法應該是如下:
a 中讀取記錄,儲存到乙個鍊錶(指標鍊錶,陣列鍊錶都可以)lista中,b中讀取記錄,儲存到listb中,然後用兩重迴圈來實現查詢:
int i ,j;
int ncounta = lista.getcount() ;
int ncountb = listb.getcount() ;
for (i=0; i < ncounta ;i++)
} }
假設比較匹配使用的是strcmp類似的演算法,時間複雜度為o(m),
這樣的演算法,時間複雜度為o(n1*n2*m),空間複雜度為o(n1)+o(n2)。
n1為lista大小,n2為listb大小,m為姓名長度。
下面,我們來對這個演算法優化。
首先我們在這裡討論,使用指標鍊錶好還是陣列鍊錶好。
指標鍊錶的優點在於插入,刪除快,但定位慢。
陣列鍊錶的優點在於定位塊,插入,刪除,很慢。
第一感覺應該是指標鍊錶會比較好,確實,我們在匹配過程中可以找到乙個重複資料就刪除,這樣確實會比較方便。
其實,在這個地方,使用陣列鍊錶優勢會更大。
我們分析一下指標鍊錶和陣列鍊錶插入的原理。
鍊錶指標每次插入的時候,直接在尾指標處插入乙個新資料,時間複雜度為o(1),不討論其他細微的開銷。
陣列插入緩慢的原因,是因為,如果插入的資料不是最後乙個,就需要把插入點之後的資料全部向後移動乙個位置;刪除也是一樣的,時間複雜度為o(n)。但是,如果插入的資料是插入到末尾的話,那麼這種弊端就不存在。相反,插入,刪除,還會變的很快。這就如同鍊錶的尾指標一樣,可以達到o(1)時間內完成。
分析了陣列的插入原理,我們再對上面查詢的過程做修改。在每次查詢到重複資料的時候,不要立刻刪除,而先做個標記。當查詢結束後,再將沒有標記的資料複製到另外乙個陣列中,這樣就可以避開陣列的缺點,而發揮陣列的長處。c**演示如下
typedef struct tagstudent
s ;
int i ,j;
int ncounta = lista.getcount() ;
int ncountb = listb.getcount() ;
for (i=0; i < ncounta ;i++)
} }
} }
有人可能就會說了,使用鍊錶一樣也可以做標記,不刪除啊,為什麼不用鍊錶呢?是的,可以使用鍊錶,但是鍊錶的操作是對指標的操作,定位操作,最快也需要移動一次指標(p = p->next),而對於陣列,只需要i++,這兩個操作的速度還是有差別的。(當然,差別不大,但程式設計師不是經常為乙個指令週期而瘋狂嗎?)
到這裡,我們僅是對資料結構的設計,優化的重點就是暫時不刪除重複資料,而做標記,等處理完畢後,再統一做處理。這個演算法會增加空間複雜度,由o(n)變為o(2*n)。
那麼還有沒有辦法對這樣的演算法進行更進一步的優化呢?
有的,這也就是我今天要描述的重點。
可以想象,在上面的演算法中,有很多重複的步驟。例如,每次lista中取乙個資料,要去b中查詢,都需要遍歷b中一次,這就導致b中的操作太過頻繁。最終演算法緩慢。那麼現在我們就要優化在b中遍歷這一步。
我們這裡是以學生姓名做為匹配關鍵字,為簡單起見,假設資料a中的所有學生姓名不重複,資料b也滿足這個條件,後面我們再考慮複雜一點的情況。
對於每個學生姓名,既然唯一,那麼就可以使用乙個數值來代表,例如,1代表「張三」,2代表「李四」,那麼如果a中有「張三」,要在b中查詢有無「張三」,就只需要看b中有沒有1。有人會說,這樣做有什麼意義?意義有兩點:
1) 使用數值(dword)查詢,會比字串查詢速度快。因為字串查詢時間複雜度是o(n),而數值匹配,時間複雜度為o(1)
2) 理解這個思路,對於後面的優化方法才能更加容易接受。
那麼如何將「張三」,「李四」對應到數值1,2呢?
使用crc(迴圈冗餘校驗)來實現這種對應關係。假設「張三」的hex值為「d5 c5 c8 fd」,那麼就可以定義數值dword dwindex = d5 + c5 + c8 + fd,這就是crc值。當然了,這個值如何計算,完全可以根據需要來,比如你覺得加法算出的crc值重複性大,那麼可以使用乘法,移位等演算法,來使數值比較分散,降低重複的機率。但這樣還是沒有辦法避免重複,所以,每次找到匹配數值後,還應該繼續檢查學生姓名是否匹配。演算法描述就是:
typedef struct tagstudent
s ;
int i ,j;
int ncounta = lista.getcount() ;
int ncountb = listb.getcount() ;
for (i=0; i < ncounta ;i++)
} }
} }
這樣一來,演算法就可以優化到o(n1*n2)了。
到這一步,估計不少人已經看出端倪了。不錯,接下來,我們就要使用雜湊表(hashtable)來優化資料儲存。
我們在上面說過,讀取資料的時候儲存到鍊錶或者陣列中,而現在,我們要儲存到雜湊表中,而雜湊表的索引(index)就是我們上面說過的,根據學生姓名算出來的數值。雜湊表的大小可以根據情況定義。雜湊表的資料結構可以設計成雜湊鍊錶,雜湊鍊錶的優勢在於:
1) 支援的資料可以無線擴大(鍊錶可以儲存無線大的)。
2) 對於碰撞的演算法簡單快速。
typedef struct tagstudent
s ;
iterator ita = hasha.begin() ;
for (; ita != ita.end(); ++ita)
} 我們來看看現在的時間複雜度。假設雜湊表大小為m,每次匹配的演算法複雜度為
o( n/m),完成整個操作的演算法複雜度為o(n1*n2/m)。
回頭再看一下我們遺留的乙個問題,如果學生姓名有重複,如何解決?
我們可以定義雜湊鍊錶,如果有重複的資料,就放到鍊錶中,所以匹配的時候,需要
在找到index數值後,然後再進一步使用字串匹配找到符合的學生記錄。
但是,需要知道的是,如果要支援模糊匹配,這種演算法就沒有辦法進行優化。
但是,如果資料本身可以提取特徵值,那麼模糊匹配也可以支援。具體情況要根據實際的情況去看了。
針對鍊錶,雜湊表的資料結構,可以自己設計,也可以使用stl,但我覺得,自己設計的資料結構,使用起來更加方便。速度慢?呵呵,stl的也沒有使用特殊的演算法,也沒有使用彙編,自己設計的為什麼就會慢呢?如果慢,那只能說明你設計的資料結構有問題了,呵呵。
演算法 快速查詢質數
其實,找乙個質數,很簡單啊,就是全部遍歷一次嘛,但是!我們這裡講一下,快速求解的辦法好吧!對於給定的乙個數,求解這個數內的所有質數!首先,對於乙個數n,只要它根號n內的數,不能整除它,那麼它就肯定是zh質數,因為這個是乘法交換律,ab ba,哈哈哈 然後呢,我們對於n個數內,我們可以直接pai 排除...
快速查詢素數
題目 時間限制 1000 ms 記憶體限制 65535 kb 難度 3 描述 現在給你乙個正整數n,要你快速的找出在2.n這些數裡面所有的素數。輸入給出乙個正整數數n n 2000000 但n為0時結束程式。測試資料不超過100組 輸出將2 n範圍內所有的素數輸出。兩個數之間用空格隔開 樣例輸入 5...
快速查詢素數
快速查詢素數 時間限制 1000 ms 記憶體限制 65535 kb 難度 3 描述現在給你乙個正整數n,要你快速的找出在2.n這些數裡面所有的素數。輸入給出乙個正整數數n n 2000000 但n為0時結束程式。測試資料不超過100組 輸出將2 n範圍內所有的素數輸出。兩個數之間用空格隔開 樣例輸...