C語言實現簡單的哈希表

 

這是一個簡單的哈希表的實現,用c語言作的。html

哈希表原理

這裏不講高深理論,只說直觀感覺。哈希表的目的就是爲了根據數據的部份內容(關鍵字),直接計算出存放完整數據的內存地址。算法

試想一下,若是從鏈表中根據關鍵字查找一個元素,那麼就須要遍歷才能獲得這個元素的內存地址,若是鏈表長度很大,查找就須要更多的時間.數組

void* list_find_by_key(list,key)
{
    for(p=list;p!=NULL; p=p->next){
        if(p->key == key){
            return p;
        }
        return p;
    }
}

爲了解決根據關鍵字快速找到元素的存放地址,哈希表應運而生。它經過某種算法(哈希函數)直接根據關鍵字計算出元素的存放地址,因爲無需遍歷,因此效率很高。數據結構

void* hash_table_find_by_key(table, key)
{
    void* p = hash(key);
    return p;
}

固然,上面的僞代碼忽略了一個重要的事實:那就是不一樣的關鍵字可能產生出一樣的hash值。函數

hash("張三") = 23;
hash("李四") = 30;
hash("王五") = 23;

這種狀況稱爲「衝突」,爲了解決這個問題,有兩種方法:一是鏈式擴展;二是開放尋址。這裏只講第一種:鏈式擴展。佈局

也就是把具備相同hash值的元素放到一塊兒,造成一個鏈表。這樣在插入和尋找數據的時候就須要進一步判斷。測試

void* hash_table_find_by_key(table, key)
{
    void* list = hash(key);
    return list_find_by_key(list, key);
}

須要注意的是,只要hash函數合適,這裏的鏈表一般都長度不大,因此查找效率依然很高。spa

下圖是一個哈希表運行時內存佈局:操作系統

 

先說一下原理。
先是有一個bucket數組,也就是所謂的桶。.net

哈希表的特色就是數據與其在表中的位置存在相關性,也就是有關係的,經過數據應該能夠計算出其位置。

這個哈希表是用於存儲一些鍵值對(key -- value)關係的數據,其key也就是其在表中的索引,value是附帶的數據。

經過散列算法,將字符串的key映射到某個桶中,這個算法是肯定的,也就是說一個key必然對應一個bucket

而後是碰撞問題,也就是說多個key對應一個索引值。舉個例子:有三個key:key1,key3,key5經過散列算法keyToIndex獲得的索引值都爲2,也就是這三個key產生了碰撞,對於碰撞的處理,採起的是用鏈表鏈接起來,而沒有進行再散列。

包含的頭文件

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUCKETCOUNT 16

哈希表和節點數據結構的定義

struct hashEntry
{
    const char* key;
    char* value;
    struct hashEntry* next;
};

typedef struct hashEntry entry;

struct hashTable
{
    entry bucket[BUCKETCOUNT];  //先默認定義16個桶
};

typedef struct hashTable table;

初始化和釋放哈希表

//初始化哈希表
void initHashTable(table* t)
{
    int i;
    if (t == NULL)return;

    for (i = 0; i < BUCKETCOUNT; ++i) {
        t->bucket[i].key = NULL;
        t->bucket[i].value = NULL;
        t->bucket[i].next = NULL;
    }
}

//釋放哈希表
void freeHashTable(table* t)
{
    int i;
    entry* e,*ep;
    if (t == NULL)return;
    for (i = 0; i<BUCKETCOUNT; ++i) {
        e = &(t->bucket[i]);
        while (e->next != NULL) {
            ep = e->next;
            e->next = ep->next;
            free(ep->key);
            free(ep->value);
            free(ep);
        }
    }
}

哈希散列算法

//哈希散列方法函數
int keyToIndex(const char* key)
{
    int index , len , i;
    if (key == NULL)return -1;

    len = strlen(key);
    index = (int)key[0];
    for (i = 1; i<len; ++i) {
        index *= 1103515245 + (int)key[i];
    }
    index >>= 27;
    index &= (BUCKETCOUNT - 1);
    return index;
}

輔助函數strDup

這是比較多餘的作法,由於C標準庫中string.h中有一系列這樣的函數。

//在堆上分配足以保存str的內存
//並拷貝str內容到新分配位置
char* strDup(const char* str)
{
    int len;
    char* ret;
    if (str == NULL)return NULL;

    len = strlen(str);
    ret = (char*)malloc(len + 1);
    if (ret != NULL) {
        memcpy(ret , str , len);
        ret[len] = '\0';
    }
    return ret;
}

string.h中的相關函數

#include <string.h>

       char *strdup(const char *s);

       char *strndup(const char *s, size_t n);
       char *strdupa(const char *s);
       char *strndupa(const char *s, size_t n);

哈希表的插入和修改

這個了插入和修改是一個方法,若是key在哈希表中已經存在,那麼就是修改value,不然就是插入一個節點。

//向哈希表中插入數據
int insertEntry(table* t , const char* key , const char* value)
{
    int index , vlen1 , vlen2;
    entry* e , *ep;

    if (t == NULL || key == NULL || value == NULL) {
        return -1;
    }

    index = keyToIndex(key);
    if (t->bucket[index].key == NULL) {
        t->bucket[index].key = strDup(key);
        t->bucket[index].value = strDup(value);
    }
    else {
        e = ep = &(t->bucket[index]);
        while (e != NULL) { //先從已有的找
            if (strcmp(e->key , key) == 0) {
                //找到key所在,替換值
                vlen1 = strlen(value);
                vlen2 = strlen(e->value);
                if (vlen1 > vlen2) {
                    free(e->value);
                    e->value = (char*)malloc(vlen1 + 1);
                }
                memcpy(e->value , value , vlen1 + 1);
                return index;   //插入完成了
            }
            ep = e;
            e = e->next;
        } // end while(e...

        //沒有在當前桶中找到
        //建立條目加入
        e = (entry*)malloc(sizeof (entry));
        e->key = strDup(key);
        e->value = strDup(value);
        e->next = NULL;
        ep->next = e;
    }
    return index;
}

哈希表中查找

由於這個哈希表中保存的是鍵值對,因此這個方法是從哈希表中查找key對應的value的。要注意,這裏返回的是value的地址,不該該對其指向的數據進行修改,不然可能會有意外發生。

//在哈希表中查找key對應的value
//找到了返回value的地址,沒找到返回NULL
const char* findValueByKey(const table* t , const char* key)
{
    int index;
    const entry* e;
    if (t == NULL || key == NULL) {
        return NULL;
    }
    index = keyToIndex(key);
    e = &(t->bucket[index]);
    if (e->key == NULL) return NULL;//這個桶尚未元素
    while (e != NULL) {
        if (0 == strcmp(key , e->key)) {
            return e->value;    //找到了,返回值
        }
        e = e->next;
    }
    return NULL;
}

哈希表元素的移除

這個函數用於將哈希表中key對應的節點移除,若是其不存在,那就返回NULL。若是存在,就返回這個節點的地址。注意,這裏並無釋放節點,若是不須要了,應該手動釋放它。

//在哈希表中查找key對應的entry
//找到了返回entry,並將其從哈希表中移除
//沒找到返回NULL
entry* removeEntry(table* t , char* key)
{
    int index;
    entry* e,*ep;   //查找的時候,把ep做爲返回值
    if (t == NULL || key == NULL) {
        return NULL;
    }
    index = keyToIndex(key);
    e = &(t->bucket[index]);
    while (e != NULL) {
        if (0 == strcmp(key , e->key)) {
            //若是是桶的第一個
            if (e == &(t->bucket[index])) {
                //若是這個桶有兩個或以上元素
                //交換第一個和第二個,而後移除第二個
                ep = e->next;
                if (ep != NULL) {
                    entry tmp = *e; //作淺拷貝交換
                    *e = *ep;//至關於鏈表的頭節點已經移除
                    *ep = tmp;  //這就是移除下來的鏈表頭節點
                    ep->next = NULL;
                }
                else {//這個桶只有第一個元素
                    ep = (entry*)malloc(sizeof(entry));
                    *ep = *e;
                    e->key = e->value = NULL;
                    e->next = NULL;
                }
            }
            else {
                //若是不是桶的第一個元素
                //找到它的前一個(這是前面設計不佳致使的多餘操做)
                ep = &(t->bucket[index]);
                while (ep->next != e)ep = ep->next;
                //將e從中拿出來
                ep->next = e->next;
                e->next = NULL;
                ep = e;
            }
            return ep;
        }// end if(strcmp...
        e = e->next;
    }
    return NULL;
}

哈希表打印

這個函數用於打印哈希表的內容的。

void printTable(table* t)
{
    int i;
    entry* e;
    if (t == NULL)return;
    for (i = 0; i<BUCKETCOUNT; ++i) {
        printf("\nbucket[%d]:\n" , i);
        e = &(t->bucket[i]);
        while (e->key != NULL) {
            printf("\t%s\t=\t%s\n" , e->key , e->value);
            if (e->next == NULL)break;
            e = e->next;
        }
    }
}

測試一下

用於測試的數據來自於本機相關信息。

int main()
{
    table t;
    initHashTable(&t);

    insertEntry(&t , "電腦型號" , "華碩 X550JK 筆記本電腦");
    insertEntry(&t , "操做系統" , "Windows 8.1 64位 (DirectX 11)");
    insertEntry(&t , "處理器" , "英特爾 Core i7 - 4710HQ @ 2.50GHz 四核");
    insertEntry(&t , "主板" , "華碩 X550JK(英特爾 Haswell)");
    insertEntry(&t , "內存" , "4 GB(Hynix / Hyundai)");
    insertEntry(&t , "主硬盤" , "日立 HGST HTS541010A9E680(1 TB / 5400 轉 / 分)");
    insertEntry(&t , "顯卡" , "NVIDIA GeForce GTX 850M       (2 GB / 華碩)");
    insertEntry(&t , "顯示器" , "奇美 CMN15C4(15.3 英寸)");
    insertEntry(&t , "光驅" , "松下 DVD - RAM UJ8E2 S DVD刻錄機");
    insertEntry(&t , "聲卡" , "Conexant SmartAudio HD @ 英特爾 Lynx Point 高保真音頻");
    insertEntry(&t , "網卡" , "瑞昱 RTL8168 / 8111 / 8112 Gigabit Ethernet Controller / 華碩");
    insertEntry(&t , "主板型號" , "華碩 X550JK");
    insertEntry(&t , "芯片組" , "英特爾 Haswell");
    insertEntry(&t , "BIOS" , "X550JK.301");
    insertEntry(&t , "製造日期" , "06 / 26 / 2014");
    insertEntry(&t , "主人" , "就是我");
    insertEntry(&t , "價格" , "六十張紅色毛主席");
    insertEntry(&t , "主硬盤" , "換了個120G的固態");

    entry* e = removeEntry(&t , "主板型號");
    if (e != NULL) {
        puts("找到後要釋放");
        free(e->key);
        free(e->value);
        free(e);
        e = NULL;
    }

    printTable(&t);

    const char* keys[] = { "顯示器" , "主人","沒有" , "處理器" };
    for (int i = 0; i < 4; ++i) {
        const char* value = findValueByKey(&t , keys[i]);
        if (value != NULL) {
            printf("find %s\t=\t%s\n" ,keys[i], value);
        }
        else {
            printf("not found %s\n",keys[i]);
        }
    }


    freeHashTable(&t);
    getchar();
    return 0;
}

 

原文連接:簡單的哈希表實現 C語言 - 烏合之衆 - 博客園  https://www.cnblogs.com/oloroso/p/4610109.html

C語言實現的數據結構之------哈希表 - 瘋狂老司機的博客 - CSDN博客  https://blog.csdn.net/wuxing26jiayou/article/details/79807334

相關文章
相關標籤/搜索