hash表的實現(xiàn)和hash桶的示例(c實現(xiàn))

hash是以空間換時間的結(jié)構(gòu)围橡,現(xiàn)在空間越來越大暖混,而且對性能要求越來越高的年代,這絕對是值得的翁授。

hash含義就是散列拣播,就是把我們本來想?查找的一大群結(jié)構(gòu)體數(shù)據(jù)分散開,更容易查找收擦。一個好的hash函數(shù)應(yīng)該做到對所有元素平均分散排列贮配,盡量避免或者降低他們之間的沖突(Collision)。hash函數(shù)的選擇必須慎重塞赂,如果不幸所有的元素之間都產(chǎn)生了沖突泪勒,那么hash表將退化為鏈表,其性能會大打折扣宴猾,時間復(fù)雜度迅速降為O(n)圆存,絕對不要存在任何僥幸心理,因為那是相當(dāng)危險的仇哆。歷史上就出現(xiàn)過利用Linux內(nèi)核hash函數(shù)的漏洞沦辙,成功構(gòu)造出大量使hash表發(fā)生碰撞的元素,導(dǎo)致系統(tǒng)被DoS讹剔,所以目前內(nèi)核的大部分hash函數(shù)都有一個隨機(jī)數(shù)作為參數(shù)進(jìn)行摻雜油讯,以使其最后的值不能或者是不易被預(yù)測。這又對 hash函數(shù)提出了第二點安全方面的要求:hash函數(shù)最好是單向的辟拷,并且要用隨機(jī)數(shù)進(jìn)行摻雜撞羽。提到單向,你也許會想到單向散列函數(shù)md4和md5衫冻,很不幸地告訴你诀紊,他們是不適合的,因為hash函數(shù)需要有相當(dāng)好的性能隅俘。

散列函數(shù): 將字符串等傳入我們的散列函數(shù)邻奠,而散列函數(shù)的職責(zé)就是給我們返回一個value值笤喳,我們通過這個值引做hash表下標(biāo)去訪問我們想要查找的數(shù)據(jù);例如這樣的函數(shù):

int Hash(char *key, int TableSize)
{
     unsigned int HashVal = 0;
     while(*key != '\0')
             HashVal += *key++;
     return HashVal % TableSize;
}

這就是一個簡單的hash函數(shù)碌宴,就是把我們傳入過來的key(由我們的數(shù)據(jù)中一個或者多個結(jié)構(gòu)體成員的成員來作為key)來得到一個返回值杀狡,這個返回值就是我們的value值。

一個好的hash?函數(shù)就是把我們的說有數(shù)據(jù)盡可能均勻的分散在我們預(yù)設(shè)的TableSize大小的hash表中贰镣。哈希表的幾種方法:

1呜象、直接定址法:取關(guān)鍵字key的某個線性函數(shù)為散列地址,如Hash(key) = key 或 Hash(key) = A*key+B碑隆;A,B為常數(shù)

2恭陡、除留取余法:關(guān)鍵值除以比散列表長度小的素數(shù)所得的余數(shù)作為散列地址。Hash(key) = key % p;

3上煤、平均取中法:先計算構(gòu)成關(guān)鍵碼的標(biāo)識符的內(nèi)碼的平方休玩,然后按照散列表的大小取中間的若干位作為散列地址。

4劫狠、折疊法:把關(guān)鍵碼自左到右分為位數(shù)相等的幾部分拴疤,每一部分的位數(shù)應(yīng)與散列表地址位數(shù)相同,只有最后一部分的位數(shù)可以短一些独泞。把這些部分的數(shù)據(jù)疊加起來呐矾,就可以得到具有關(guān)鍵碼的記錄的散列地址。分為移位法和分界法阐肤。

5凫佛、隨機(jī)數(shù)法:選擇一個隨機(jī)函數(shù),取關(guān)鍵字的隨機(jī)函數(shù)作為它的哈希地址孕惜。

6愧薛、數(shù)學(xué)分析法:設(shè)有N個d位數(shù),每一位可能有r種不同的符號衫画。這r種不同的符號在各位上出現(xiàn)的頻率不一定相同毫炉,可能在某些位上分布均勻些,每種符號出現(xiàn)的機(jī)會均等削罩;在某些位上分布不均勻瞄勾,只有某幾種符號經(jīng)常出現(xiàn)∶旨ぃ可根據(jù)散列表的大小进陡,選取其中各種符號分布均勻的若干位作為散列地址。

但是無論我們怎么樣去選擇這個函數(shù)微服,都不可能完全避免不同key值的hash[value]?指向會映射到同一散列地址上禀横。這樣就會造成哈希沖突/哈希碰撞纵顾。所以我們需要找到處理這種沖突的方法竟痰,大概分為這兩種:分離鏈接法和開放定址法滥玷。

分離鏈接法:其實就是我們說的hash桶的含義了妇蛀。哈希桶就是盛放不同key鏈表的容器(即是哈希表),在這里我們可以把每個key的位置看作是一個桶,桶里放了一個鏈表

image.png

相信大家可以看出來,使用一個數(shù)組來存放記錄方法的哈希沖突太多魄缚,基于載荷因子的束縛,空間利用率不高焚廊,在需要節(jié)省空間的情況下冶匹,我們可以用哈希桶來處理哈希沖突。

哈希桶是使用一個順序表來存放具有相同哈希值的key的鏈表的頭節(jié)點咆瘟,利用這個頭節(jié)點可以找到其它的key徙硅。

下面把完整的一套函數(shù)分開講:

1、首先是創(chuàng)建我們需要的結(jié)構(gòu)體:

數(shù)據(jù)的結(jié)構(gòu)體(也就是我們表中需要存放的數(shù)據(jù)):

 typedef struct node_s
{
        int key;    //這個值是我們得到我們的value值的依據(jù)搞疗,當(dāng)然也可以能使字符串等,看你的數(shù)據(jù)類型了须肆;

       struct node *next;

}NODE;

hash表的節(jié)點:?

typedef struct hash_s
{
        NODE **list;   //這個就是我們所有的hash桶的首個節(jié)點了匿乃,我們用它來查找到我們的桶的位置,為什么是**類型呢 豌汇,因為這是地址的地址幢炸,例如某個桶 i 的位置*list[i];這樣就找到我們的桶了拒贱;然后再在桶下面看看有沒有我們要查找的NODE節(jié)點了

}HASH;

2宛徊、初始化hash表:

HashTable InitializeTable(int TableSize)
{
        Hash H;
        int i = 0;
        H = calloc(1, sizeof(HASH));
        if (H ==  NULL)
                return -1;

        H->TableSize = NextPrime();   //就是和TableSize最近的下一個素數(shù);
        H->hlist = calloc(1, sizeof(&NODE) * H->TableSize);
        if (H->hlist == NULL)
                return -1;

        for (i = 0; i < H->TableSize; i ++)     
        {
                *(H->hlist[i)] = calloc(1, sizeof(NODE));
                if (*hlist[i] == NULL)
                        return -1;
                else
                        *(H->hlist[i])->next = NULL;
        }
}

3逻澳、查找NODE:

NODE *Find(int key, HASH H)
{
        NODE *p;
        NODE *list;

        list = H->hlist[Hash(key, H->TableSize)];
        p = list->next;
        while(p != NULL && p->key != key)
                p = p->next;
        return p;

}    //先找到這個桶的頭結(jié)點list,然后再往后面遍歷查找闸天,這時候基本是鏈表查找操作了;

4斜做、插入NODE:

void Insert(int key, HASH H)
{
        NODE *p;
        NODE *list;
        NODE *tmp;

        if (Find(key, H) == NULL)
        {
                tmp = calloc(1, sizeof(NODE));
                if (tmp == NULL)
                        return -1;

                list = H->hlist[Hash(key, H->TableSize)];
                tmp->key = key;
                tmp->next = list->next;//這里是直接把我們的節(jié)點插入到桶的首節(jié)點苞氮;
                list->next = tmp;
        }
        return;
}

以上基本完成以hash桶?為處理沖突的hash表的實現(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市瓤逼,隨后出現(xiàn)的幾起案子笼吟,更是在濱河造成了極大的恐慌,老刑警劉巖霸旗,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贷帮,死亡現(xiàn)場離奇詭異,居然都是意外死亡诱告,警方通過查閱死者的電腦和手機(jī)撵枢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人诲侮,你說我怎么就攤上這事镀虐。” “怎么了沟绪?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵刮便,是天一觀的道長。 經(jīng)常有香客問我绽慈,道長恨旱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任坝疼,我火速辦了婚禮搜贤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘钝凶。我一直安慰自己仪芒,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布耕陷。 她就那樣靜靜地躺著掂名,像睡著了一般。 火紅的嫁衣襯著肌膚如雪哟沫。 梳的紋絲不亂的頭發(fā)上饺蔑,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機(jī)與錄音嗜诀,去河邊找鬼猾警。 笑死,一個胖子當(dāng)著我的面吹牛隆敢,可吹牛的內(nèi)容都是我干的发皿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼筑公,長吁一口氣:“原來是場噩夢啊……” “哼雳窟!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起匣屡,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤封救,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后捣作,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體誉结,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年券躁,在試婚紗的時候發(fā)現(xiàn)自己被綠了惩坑。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掉盅。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖以舒,靈堂內(nèi)的尸體忽然破棺而出趾痘,到底是詐尸還是另有隱情,我是刑警寧澤蔓钟,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布永票,位于F島的核電站,受9級特大地震影響滥沫,放射性物質(zhì)發(fā)生泄漏侣集。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一兰绣、第九天 我趴在偏房一處隱蔽的房頂上張望世分。 院中可真熱鬧,春花似錦缀辩、人聲如沸臭埋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽斋泄。三九已至,卻和暖如春镐牺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背魁莉。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工睬涧, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人旗唁。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓畦浓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親检疫。 傳聞我的和親對象是個殘疾皇子讶请,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359