1. 程式人生 > >哈希表之一初步原理了解

哈希表之一初步原理了解

現在 沖突 理解 簡單 是我 dex 技術分享 方便 兩個

考慮如下場景:小明住在芳華小區,芳華小區中有很多幢房子,每個房子有十幾二十層,每層有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方法的關系是什麽樣的,以及這兩個方法應該如何去寫才比較好;

哈希表之一初步原理了解