標籤:style blog color 使用 io 資料 for ar
什麼是雜湊表
雜湊表(Hash table,也叫散列表),是根據關鍵碼值(Key value)而直接進行訪問的資料結構。也就是說,它通過把關鍵碼值對應到表中一個位置來訪問記錄,以加快尋找的速度。這個映射函數叫做雜湊函數,存放記錄的數組叫做雜湊表。雜湊表作為一種高效的資料結構,有著廣泛的應用。如果雜湊函數設計合理,理想情況下每次查詢的時間花費僅僅為 O(h/r),即和雜湊表容量與剩餘容量的比值成正比。只要雜湊表容量達到實際使用量的大約 1.5 倍以上,查詢花費的時間基本就可以認為恒為 O(1)。
一個通俗的例子是,為了尋找電話簿中某人的號碼,可以建立一個按照人名首字母順序排列的表(即建立人名x到首字母F(x)的一個函數關係),在首字母為W的表中尋找“王”姓的電話號碼,顯然比直接尋找就要快得多。這裡使用人名作為關鍵字,“取首字母”F()是這個例子中的雜湊函數,存放首字母的表對應雜湊表。關鍵字和雜湊函數可以任意選擇。在競賽中的雜湊函數一般是直接把關鍵字對一個素數模數(簡單粗暴)。
與平衡二叉樹的類比
Map是STL的一個關聯容器,它提供一對一的資料處理能力(其中第一個可以稱為關鍵字,每個關鍵字只能在map中出現一次,第二個可能稱為該關鍵字的值)。它的內部實現是一個紅/黑樹狀結構(紅/黑樹狀結構是平衡二叉樹的一種),平衡二叉樹中的部所有的資料都是有序的,並且可以通過O(logn)的複雜度尋找、插入或刪除一個關鍵字。而在理想情況下,雜湊表可以用O(1)的複雜度尋找、插入一個關鍵字。
處理碰撞
對不同的關鍵字可能得到同一散列地址,即k_1 != k_2,而f(k_1)=f(k_2),這種現象稱碰撞(Collision)。具有相同函數值的關鍵字對該散列函數來說稱做同義字。
處理碰撞的方法有很多,在競賽中一般採用單獨鏈表法:將雜湊到同一個儲存位置的所有元素儲存在一個鏈表中。在實現中一般用一個數組來代替鏈表。
演算法實現
我們通過山寨一個STL Map來加深對雜湊表的理解。
map常用操作有兩種:
- 查詢map中是否含有給定關鍵字Key;
- 將(Key,Value)插入到map中。如果map中已經存在Key,那麼就用新的Value覆蓋舊的值。
1 typedef long long LL; 2 typedef LL KT;// Key的類型 3 typedef LL VT;// Value的類型 4 class Hash_Map { 5 private: 6 static const int SEED = 29989;// 一個比實際容量稍大的素數 7 static const int MAXN = 30000;// 雜湊表最大的元素個數 8 struct ELEM {// 定義鏈表的資料類型 9 KT key;// 元素的關鍵字10 VT value;// 元素的值11 int next;// 指向鏈表的下一個位置12 }elems[MAXN];// 鏈表所使用的數組13 int head[SEED], cur;// 表頭與元素個數14 int hash(KT key){// 雜湊函數15 return key % SEED;16 }17 public:18 Hash_Map() {// 建構函式19 clear();20 }21 void clear() {// 初始化雜湊表22 cur = 0;23 memset(head,-1,sizeof(head));24 }25 void insert(KT key, VT value) {// 插入一個關鍵字26 int h = hash(key);// 用雜湊Function Compute關鍵字在雜湊表中的地址27 for (int i=head[h]; i!=-1; i=elems[i].next) {// 遍曆雜湊值為h的鏈表中的所有元素28 if (key==elems[i].key) {// 如果key已經存在29 elems[i].value = value;// 覆蓋舊的value30 return;31 }32 }33 // key不存在,則將其插入鏈表34 elems[cur].value = value;35 elems[cur].key = key;36 elems[cur].next = head[h];37 head[h] = cur++;38 }39 VT find(KT key) {40 int h = hash(key);41 for (int i=head[h]; i!=-1; i=elems[i].next) {42 if (key==elems[i].key) {43 return elems[i].value;44 }45 }46 return -1;47 }48 }hs;