哈希表之一初步原理了解
考慮如下場景:小明住在芳華小區,芳華小區中有很多幢房子,每個房子有十幾二十層,每層有4個住戶;
現在小紅要去找小明玩耍,現在假設小紅只知道小明住在芳華小區,但是不知道住在哪一幢,哪一層和哪一戶,】、
那麽小紅要怎麽才能找到小明呢?
那麽毫無疑問,小紅只有在芳華小區中一家一家地敲門問小明住在哪兒?
(此時不允許小紅叫一個收破爛的拿著喇叭在樓下喊:"小明,額想你!")
這個時候,如果小紅有芳華小區的所有住戶的明細的話,就可以從明細上找到小明住的地方,然後根據這個地方在芳華小區中找到小明,
而不需要敲遍小區所有住戶的門。
同樣,現在內存中有一數組,我們需要判斷在數據中是否存在某個元素的時候,如果沒有其他額外的信息,唯一的辦法就是遍歷這個數組,
因為這個時候我們只有這個這個數組的下標可用。
那麽聯想到上面小紅找小明的例子,其實有類似之處,數組中的元素和下標現在是沒什麽聯系的;不能根據元素值來定位到元素如果存在
在數組中的時候應該在哪個位置,要是我們想小紅一樣,也有一個"住戶的明細"的話,任意給一個元素,我們根據元素的值找到其對應
在數組中的下標的值,然後直接根據這個下標的值直接去訪問數組,便可以知道這個元素是否存在了。
現在出現了兩個需要註意的東西:
(1)"住戶的明細"
(2)找到
"住戶的明細"其實從數學上來說可以看做一個對應關系,一個映射,也就是現在要討論的哈希表。這個表記錄了元素和其在數組中下標的
對應關系,待會我們就會對某個數組做一張這樣的表;
"找到",其實就是根據元素的值計算出其在數組中的下標(位置)。
哈希表中有個兩需要解決的問題:
(1)找到一個盡可能將元素分散的放到數組中去,不要讓不同的元素都分到同一個位置去了
(2)萬一確實通過hash函數計算之後分到了同一個地方,我們得有相應的措施來處理這種情況
現在有如下幾個數:1,2,4,5,7,8,10,11;現在為了方便查找,我們可以這樣放
這裏的數據放置的規則很簡單:
1%7=1,放到數組的第1個位置;
2%7=2,放到數組的第2個位置;
......
11%7=4,放到數組的第4個位置;
這裏元素的值對7求余就是所選擇的hash函數。如果我們要找數組中是否存在某個數x,可以用x%7求得一個數,
這個數就是在這個數組中應該存在的位置,此時直接通過這個位置定位數組中的這個元素,就可以很快的判斷這個數在數組中
存不存,而不需要一個一個遍歷數組。
圖中可以看到1和8對7求余的余數是相同的,導致了它們被放到了同一個位置上,這就是屬於沖突的情況。
哈希表中解決沖突的情況有兩種方法:
一種方法就是如果沖突了的話就縱向的添加沖突的元素,如圖中那樣,先用hash算出元素應該出現的坑,然後在通過鏈表進行普通查找;
另一種方法就是繼續橫向的添加,具體怎麽添加還沒摸清楚~,反正hash基本原理就是這樣的。
哈希表的如果建的好的話,查找元素的時間復雜度是O(n)哦,典型的時間換取空間。
有助於理解的圖:
下面用C來寫一個簡單的hash表:
1 #include<stdio.h> 2 #include<stdlib.h> 3 4 #define LIST_LENGTH 100 5 6 typedef struct Item Item; 7 8 struct Item { // 用鏈表來解決沖突 9 int value; 10 int index; 11 Item * next; 12 }; 13 14 typedef struct List { 15 Item *head; 16 Item *end; 17 } List; 18 19 void insertItem(List *list, Item *item) { 20 21 int key = (unsigned int)(item->value) % LIST_LENGTH; // 鍵的計算方法 22 // 判斷list中是否存在元素 23 if(!list[key].head) { // 這個槽之前沒有元素 24 list[key].head = item; 25 list[key].end = item; 26 } else { // 已經有元素放到了這個位置 27 list[key].end->next = item; 28 list[key].end = item; 29 } 30 } 31 32 int searchValue(List *list, int value) { 33 // 計算出元素的鍵值 34 int key = (unsigned int)(value) % LIST_LENGTH; 35 Item *p = list[key].head; 36 37 while(p) { 38 if(value == p->value) { 39 return p->index+1; // 萬一元素在數組中的索引就是在0處, 直接返回0的話和後面返回的0沖突了 40 } 41 p = p->next; 42 } 43 return 0; 44 } 45 46 int main(void) { 47 48 int array[] = {2, 3, 5, 7, 8, 9, -3, -8, 22, 13}; 49 int num = 10; // 數組元素的個數 50 int i = 0; 51 List list[LIST_LENGTH]; // 直接分配了這麽一個結構體數組存放鏈表和key的對應關系 52 memset(list, 0, sizeof(List) * LIST_LENGTH); 53 54 Item item[10]; // 直接分配了一個結構體數組存放元素 55 memset(item, 0, sizeof(Item) * num); 56 57 // 遍歷數組, 先將元素組裝為一個結構體元素, 你可以看做面向對象編程中的對象 58 for(i=0; i<num; i++) { 59 item[i].value = array[i]; 60 item[i].index = i; 61 insertItem(list, &item[i]); // 把對象的引用放到hash表中 62 } 63 64 int test_arr[] = {-34, 2, 5, 42, -32, 12, 6, -8}; 65 int num2 = 8; 66 67 printf("數組元素為:"); 68 for(i=0; i<num; i++) { 69 printf("%d ", array[i]); 70 } 71 printf("\n"); 72 printf("測試數組元素為:"); 73 for(i=0; i<num2; i++) { 74 printf("%d ", test_arr[i]); 75 } 76 printf("\n\n"); 77 78 for(i=0; i<num2; i++) { 79 int position = searchValue(list, test_arr[i]); 80 if(position) { 81 printf("數組array中存在元素%d, 下標為%d\n", test_arr[i], position-1); 82 } else { 83 printf("數組array中不存在元素%d\n", test_arr[i]); 84 } 85 } 86 87 return 0; 88 }
後續會關註的有:
1. 哈希表的變種,不過目前也就知道又一個一致性hash,這個實在memcached這個緩存框架中會用到;
2. java中其實已經實現了哈希表這種數據結構,用起來很方便,到時候可以跟一下源碼;
3. java中的equals方法和hashcode方法的關系是什麽樣的,以及這兩個方法應該如何去寫才比較好;
哈希表之一初步原理了解