標籤:演算法導論 散列函數 hashtable 雜湊表 直接定址
雜湊表,又名散列表,hashtable。。。云云,看似很高大上,其實不過是直接定址的延伸而已。直接定址為何物,看一個數組:a[10],那麼取其中一個元素a[1],這就是直接定址,直接去這個a+1的地址上,就找到了這個數值,時間複雜度為O(1)。而雜湊表的目的就是要讓尋找的時間複雜度盡量往O(1)上靠。
一、雜湊表的最簡單形式
假如有10000個數,比如0~9999,是可能出現的數位集合,我們現在要將一段時間內,出現的數字,全部儲存起來。如果出現的數字都不重複的情況下,我們可以使用一個長度為10000的數組a[10000]來儲存,如果數字987出現了,那麼我們直接將其儲存在a[987]元素上,以此類推。當我們要查詢某個數是否出現的時候,比如1002這個數,是否出現,我們可以直接去找a[1002],看看值是否是1002,為了保險起見,也可以在初始化的時候將數組做-1初始化。這樣,尋找的時間複雜度就是O(1)。
二、改進的雜湊表
上面的做法感覺是很快,但是卻有問題,太浪費空間了。還是上面的例子,假如可能出現的集合為0~9999這一萬個數,但是我們可以預知,實際會出現的數字只有最多10個。如果按照上面的做法,就需要為尋找這10個數,而花費10000個空間?是不是太奢侈了點?
其次,上面的做法,基礎在於所有出現的數組,都不重複,如果我們人品太好,總共出現兩個888怎麼辦?我們又想知道,888出現了幾次?顯然這時候,一個a[888]空間是不夠的。
為瞭解決上面兩個問題,分兩步來做:
1、壓縮儲存空間。
如何壓縮?這裡要用到通常所說的雜湊函數,雜湊函數的作用,就是將大的集合資料,印射到一個相對較小的,我們能夠接受的集合範圍內,使得速度和記憶體空間達到一個平衡。比如這裡,0~9999一萬個可能出現的數字集合,而最多實際只會出現10個。我們就可以使用a%b(取餘)操作來處理,比如這裡,我們可以使用a%10,來讓所有出現的資料的範圍由0~9999,變成0~9這十個數,然後就可以使用一個a[10]的數組,去搞定直接定址。
2、使用鏈表解決重複出現資料的問題
像上面說的,出現兩個888怎麼搞?那麼我們在a[888]這個位置上,不放元素,我們將a數組作為一個鏈表數組,a[888]放鏈表的位置,這樣,出現兩個888,每次都從鏈表的頭部插入,這樣就能放的下了。
如果尋找的時候,時間複雜度就不能是單單的O(1)了。我們考慮最壞的情況,比如n個元素的集合,數組的長度為m,當然(n>m)。這時候,除卻雜湊函數的取餘操作的O(1),還要加上(n/m)的鏈表長度的尋找,這是在所有位置鏈表的長度都相同的情況。如果鏈表的情況很極端。。。這就不好了。。
所以根據上面的分析,不難發現,這個雜湊函數(散列函數)很關鍵,最好是能讓資料,平均的分布到各個位置的鏈表上,而不要集中到一個或某幾個,因為這樣會造成某一個鏈表的長度很長,那麼查詢起來,時間複雜度就不理想了。
關於散列函數(雜湊函數)的取法,有很多種,這裡就不再討論,下面給出除法(取餘法)的散列函數,實現簡單的雜湊表代碼:
#include <iostream>using namespace std;/** * @作者:Alex/苦咖啡 * @時間:2015.03.18 * @部落格:http://blog.csdn.net/cyp331203 */struct node {int key;node* next;node* pre;};struct List {node* head;List() :head(NULL) {}~List() {node* tmp = NULL;while (head != NULL) {tmp = head->next;delete head;head = tmp;}}void print() {node* tmp = head;while (tmp != NULL) {cout << tmp->key << ' ';tmp = tmp->next;}cout << endl;}void insertHead(int key) {if (head != NULL) {node* n = new node();n->key = key;n->next = head;head->pre = n;head = n;} else {head = new node();head->key = 5;}}node* search(int key) {node* pre = NULL;node* curr = head;while (curr != NULL && curr->key != key) {pre = curr;curr = curr->next;}if (curr == NULL) {return NULL;} else if (pre == NULL) {return this->head;} else {return pre->next;}}void deleteNode(node* n) {if (n != NULL) {n->pre->next = n->next;n->next->pre = n->pre;delete n;}}};//void deleteList(List* l) {//if (l != NULL && (l->head) != NULL) {//node* pre = NULL;//node* curr = l->head;//while (curr != NULL) {//pre = curr;//delete curr;//curr = pre->next;//}//}//}struct HashTable {List* arr;HashTable() {arr = new List[100];}//析構~HashTable() {for (int i = 0; i < 100; i++) {if (arr + i != NULL) {//deleteList(arr + i);delete (arr+i);}}}void CHAINED_HASH_INSERT(int key) {(arr + (key % 100))->insertHead(key);}node* CHAINED_HASH_SEARCH(int key) {return (arr + (key % 100))->search(key);}void CHAINED_HASH_DELETE(node* n) {if (n != NULL) {(arr + (n->key % 100))->deleteNode(n);}}};int main() {HashTable* ht = new HashTable();ht->CHAINED_HASH_INSERT(5);node* n = ht->CHAINED_HASH_SEARCH(5);//cout << n->key << endl;ht->CHAINED_HASH_INSERT(205);//n.print();ht->arr[5].print();return 0;}
【演算法導論】簡單雜湊表的除法實現