【編程學習】淺談哈希表及用C語言構建哈希表!


哈希表:通過key-value而直接進行訪問的數據結構,不用經過關鍵值間的比較,從而省去了大量處理時間。

哈希函數:選擇的最主要考慮因素——盡可能避免沖突的出現

構造哈希函數的原則是:

①函數本身便於計算;

②計算出來的地址分布均勻,即對任一關鍵字k,f(k) 對應不同地址的概率相等,目的是盡可能減少沖突。

 

1.直接定址法:

       如果我們現在要對0-100歲的人口數字統計表,那么我們對年齡這個關鍵字就可以直接用年齡的數字作為地址。此時f(key) = key。


 

       這個時候,我們可以得出這么個哈希函數:f(0) = 0,f(1) = 1,……,f(20) = 20。這個是根據我們自己設定的直接定址來的。人數我們可以不管,我們關心的是如何通過關鍵字找到地址。 

       如果我們現在要統計的是80后出生年份的人口數,那么我們對出生年份這個關鍵字可以用年份減去1980來作為地址。此時f (key) = key-1980。

       假如今年是2000年,那么1980年出生的人就是20歲了,此時 f(2000) = 2000 - 1980,可以找得到地址20,地址20里保存了數據“人數500萬”。

       也就是說,我們可以取關鍵字的某個線性函數值為散列地址,即:f(key) = a × key + b

       這樣的散列函數優點就是簡單、均勻,也不會產生沖突,但問題是這需要事先知道關鍵字的分布情況,適合査找表較小且連續的情況。由於這樣的限制,在現實應用中,直接定址法雖然簡單,但卻並不常用。

 

2.數字分析法:

       分析一組數據,比如一組員工的出生年月日,這時我們發現出生年月日的前幾位數字大體相同,這樣的話,出現沖突的幾率就會很大;

       但是我們發現年月日的后幾位表示月份和具體日期的數字差別很大,如果用后面的數字來構成散列地址,則沖突的幾率會明顯降低。因此數字分析法就是找出數字的規律,盡可能利用這些數據來構造沖突幾率較低的散列地址。

 

3.折疊法:

       將關鍵字分割成位數相同的幾部分,最后一部分位數可以不同,然后取這幾部分的疊加和(去除進位)作為散列地址。

 

4.除留余數法:

       (常用:取關鍵字被某個不大於散列表表長m的數p除后所得的余數為散列地址。即 H(key) = key MOD p, p<=m。不僅可以對關鍵字直接取模,也可在折疊、平方取中等運算之后取模。對p的選擇很重要,一般取素數或m,若p選的不好,容易產生同義詞。

 

5. 平方取中法

       當無法確定關鍵字中哪幾位分布較均勻時,可以先求出關鍵字的平方值,然后按需要取平方值的中間幾位作為哈希地址。這是因為:平方后中間幾位和關鍵字中每一位都相關,故不同關鍵字會以較高的概率產生不同的哈希地址。

 

6. 偽隨機數法:

       采用一個偽隨機函數做哈希函數,即h(key)=random(key)。

解決沖突方法

● 開放定址法:

       當發生地址沖突時,按照某種方法繼續探測哈希表中的其他存儲單元,直到找到空位置為止。這個過程可用下式描述: H i ( key ) = ( H ( key )+ d i ) mod m ( i = 1,2,…… , k ( k ≤ m – 1)) 其中: H ( key ) 為關鍵字 key 的直接哈希地址, m 為哈希表的長度, di 為每次再探測時的地址增量。 

       采用這種方法時,首先計算出元素的直接哈希地址 H ( key ) ,如果該存儲單元已被其他元素占用,則繼續查看地址為 H ( key ) + d 2 的存儲單元,如此重復直至找到某個存儲單元為空時,將關鍵字為 key 的數據元素存放到該單元。 

       增量 d 可以有不同的取法,並根據其取法有不同的稱呼: 

( 1 ) d i = 1 , 2 , 3 , …… 線性探測再散列;

( 2 ) d i = 1^2 ,- 1^2 , 2^2 ,- 2^2 , k^2, -k^2…… 二次探測再散列;

( 3 ) d i = 偽隨機序列 偽隨機再散列;

● 鏈地址法:

       鏈地址法解決沖突的做法是:如果哈希表空間為 0 ~ m - 1 ,設置一個由 m 個指針分量組成的一維數組 ST[ m ], 凡哈希地址為 i 的數據元素都插入到頭指針為 ST[ i ] 的鏈表中。這種方法有點近似於鄰接表的基本思想,且這種方法適合於沖突比較嚴重的情況。

● 公共溢出區法:

       將哈希表分為基本表和溢出表兩部分,凡是和基本表發生沖突的元素,一律填入溢出表

 

C語言實現

       定義一些宏與結構體

#define HashMaxSize 1000 //哈希表最大容量

#define LoadFactor 0.8  //負載因子,表示哈希表的負載能力

typedef int KeyType;

typedef int ValueType;

typedef size_t(*HashFunc)(KeyType key)//定義HashFunc是一個指向函數的指定,它可以指向函數類型有size_t且有一個int參數的函數;重定義哈希函數

typedef enum Stat{  //表示每個元素的狀態

    Empty,  //空,當前沒有值

    Valid,  //當前的值有效

    Invalid //非空但無效,表示當前節點被刪除

}Stat;

typedef struct HashElem    //哈希表的元素結構體

{

    /* data */

    KeyType key;

    ValueType value;

    Stat stat;

}HashElem;

typedef struct HashTable    //哈希表

{

    HashElem data[HashMaxSize];

    size_t size;    //當前有效的元素個數

    HashFunc hashfunc;

}HashTable;

       函數聲明

void HashTableInit(HashTable *ht,HashFunc hashfunc);

//初始化哈希表

int HashTableInsert(HashTable *ht,KeyType key,ValueType value);

int HashTableFind(HashTable *ht,KeyType key,ValueType *value,size_t *cur);

//哈希表的查找,找到返回1,並返回這個節點的value值,未找到返回0

void HashRemove(HashTable *ht,KeyType key);

//刪除值為key的結點

int HashEmpty(HashTable *ht);

//判斷哈希表是否為空

size_t HashSize(HashTable *ht);

//求哈希表的大小

void HashTableDestroy(HashTable *ht);

//銷毀哈希表

       函數實現

size_t HashFuncDefault(KeyType key){

    return key%HashMaxSize;//檢查是否超出最大儲存量

}

void HashTableInit(HashTable *ht){

    if (ht == NULL) //非法輸入

        return;

    ht->size = 0;

    ht->hashfunc = HashFuncDefault;//錯誤賦值

    for (size_t i =0;i<HashMaxSize;i++){

        ht->data[i].key = 0;

        ht->dota[i].stat = Empty;

        ht->data[i].value = 0;

    }

 

 

 

}

int HashTableInsert(HashTable *ht,KeyType key,ValueType value){

    if (ht == NULL)

        return 0;

    if (ht->size >= HashMaxSize*LoadFactor) //當哈希表的size超出了負載

        return 0;

    size_t cur = ht->hashfunc(key); //根據哈希函數將key轉換,求得key在哈希表中的下標

    while(1){//判斷當前下標是否被占用

        if (ht->data[cur].key ==key) //保證不會用重復的數字存入哈希表

            return 0;

        if(ht->data[cur].stat != Valid){

            ht->data[cur].key =key;

            ht->data[cur].value = value;

            ht->data[cur].stat = Valid;

            ht->size++;

            return 1;

        }

        cur++;

    }

}

int HashTableFind(HashTable *ht,KeyType key,ValueType *value){//哈希表的查找,找到返回1,沒找到返回0

    if(ht == NULL)

        return 0;

    size_t offset=ht->hashfunc(key);//通過哈希函數找到key的下標

    if(ht->data[offset].key == key && ht->data[offset].stat == Valid){//若當前下標所對應的值正好是key並且當前的狀態必須為valid才返回

        *value = ht->data[offset].value;

        return 1;

    } else{//值不為key,根據沖突解決方法查找(當前解決方法為逐個往后查),直到找到stat等於empty

        while (ht->data[offset].stat !=Empty){

            if(ht->data[offset].key !=key){

                offset++;

                if(offset >= HashMaxSize//判斷下標是否超出最大值)

                    offset = 0;

            } else{

                if(ht->data[offset].stat == Valid){

                    *value =ht->data[offset].value;

                    return 1;

                } else

                    offset++;

            }

        }

        return 0;

    }

}

int HashTableFindCur(HashTable *ht,KeyType key,size_t *cur){//刪除節點

    if(ht == NULL)

        return 0;

    for(size_t i = 0;i < HashMaxSize;i++){

        if(ht->data[i].key == key && ht->data[i].stat == Valid){

            *cur = i;//?

            return 1;

        }

    }

    return 0;

}

void HashRemove(HashTable *ht,KeyType key){

    if (ht == NULL) //非法輸入

        return;

    ValueType value = 0;

    size_t cur =0;

    int ret = HashTableFindCur(ht,key,&cur);//通過find函數得到key是否存在哈希表中

    if(ret == 0)

        return;

    else{

        ht->data[cur].stat = Invalid;

        ht->size--;

    }

}

int HashEmpty(HashTable *ht){

    if(ht == NULL)

        return 0;

    else

        return ht->size >0?1:0

}

size_t HashSize(HashTable *ht){//求哈希表大小

    if(ht == NULL) {

        ht->data->stat = Empty;

        return ht->size;

    } else

    {

        return ht->size;

    }

}

void HashPrint(HashTable *ht, const char *msg){

    if(ht ==NULL || ht->size = 0)

        return;

    printf("%s/n",msg);

    for(size_t i =0;i< HashMaxSize;i++){

        if(ht->data[i],stat != Empty)

            printf("[%d] key=%d value=%d stat =%d\n",i,ht->data[i].key,ht->data[i].value,ht->data[i].stat);

    }

}

— END —
看到這里你是不是又學到了很多新知識呢~

如果你很想學編程,小編推薦我的C語言/C++編程學習基地【點擊進入】!

都是學編程小伙伴們,帶你入個門還是簡簡單單啦,一起學習,一起加油~

還有許多學習資料和視頻,相信你會喜歡的!

涉及:游戲開發、常用軟件開發、編程基礎知識、課程設計、黑客等等......


 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM