1. 程式人生 > >數據結構(六)查找---散列表(哈希表)查找

數據結構(六)查找---散列表(哈希表)查找

很多 分析 add 進行 erro and 散列 ESS 下一個

一:概述

散列表(Hash table,也叫哈希表),是根據關鍵碼值(Key value)而直接進行訪問的數據結構。也就是說,它通過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度。
這個映射函數叫做散列函數,存放記錄的數組叫做散列表
                  存儲位置=f(關鍵字)
散列技術是在記錄的存儲位置和他的關鍵字之間建立一個確定的對應關系f,使得每個關鍵字key對應一個存儲位置f(key)。
查找時,根據這個確定的對應關系找到固定值key的映射f(key),若查找集合中存在這個記錄,則必定在f(key)的位置上。
我們把這種對應關系f稱為散列函數
,又稱為哈希函數(Hash)。采用散列技術將記錄存儲在一塊連續的存儲空間中,這塊連續存儲空間稱為散列表或者哈希表

二:散列表查找步驟

(1)在存儲時,通過散列函數計算記錄的散列地址,並按此散列地址存儲該記錄;

(2)在查找時,通過同樣的散列函數計算記錄的散列地址,按此散列地址訪問該記錄。

所以說,散列技術既是一種存儲方法,也是一種查找方法。
散列技術最適合的求解問題就是查找與給定關健值對應的記錄(一對一)。不過散列表不適合範圍查找,比如查找一個班級18-22歲的同學,在散列表中沒法進行。想獲得表中記錄的排序也不可能,像最大值、最小值等結果也都無法從散列表中計算出來。

三:散列函數的構造方法

要求:

1.計算簡單

若是設計了一個算法可以保證所有關鍵字都不會發生沖突,但是這個算法需要大量的復雜計算,耗費很多時間,這對於頻繁的查找來說,會大大的降低查找效率。
至少散列函數的計算時間不應該超過其他查找技術與關鍵字比較的時間;

2.散列地址分布均勻

這樣可以保證存儲空間的有效利用,並減少為處理沖突而耗費的時間。分布越不均勻,越易產生數據重疊等問題

(一)直接定址法(使用某個線性函數值作為散列地址<f(key)=a*key+b>)

f(key)=key              例如年齡和人口數

技術分享圖片

f(key)=a*key+b    (a,b為常數)  例如出生年份和人口

技術分享圖片

簡單,均勻,但是需要事先知道關鍵字分布,適合查找表較小且連續的情況。不適合分布不均數據

(二)數字分析法(取關鍵字)

假設某公司的員工登記表以員工的手機號作為關鍵字。
手機號一共11位。前3位是接入號,對應不同運營商的子品牌;中間4位表示歸屬地;最後4位是用戶號。
不同手機號前7位相同的可能性很大,所以可以選擇後4位作為散列地址,或者對後4位反轉(1234 -> 4321)、循環右移(1234 -> 4123)、循環左移等等之後作為散列地址。
數字分析法通常適合處理關鍵字位數比較大的情況,如果事先知道關鍵字的分布且關鍵字的若幹位分布比較均勻,就可以考慮這個方法。

(三)平方取中法

假設關鍵字是1234、平方之後是1522756、再抽取中間3位227,用作散列地址。
平方取中法比較適合於不知道關鍵字的分布,而位數又不是很大的情況。

(四)折疊法

將關鍵字從左到右分割成位數相等的幾部分,最後一部分位數不夠時可以短些,然後將這幾部分疊加求和,並按散列表表長,取後幾位作為散列地址。

比如關鍵字是9876543210,散列表表長是3位,將其分為四組,然後疊加求和:987 + 654 + 321 + 0 = 1962,取後3位962作為散列地址。
折疊法事先不需要知道關鍵字的分布,適合關鍵字位數較多的情況。

(五)除留余數法

技術分享圖片

m為散列表長。這種方法不僅可以對關鍵字直接取模,也可在折疊、平方取中後再取模。根據經驗,若散列表表長為m,通常p為小於或等於表長(最好接近m)的最小質數,可以更好的減小沖突
此方法為最常用的構造散列函數方法。

技術分享圖片

技術分享圖片

(六)隨機數法

f(key) = random(key),這裏random是隨機函數。當關鍵字的長度不等時(種子不同,生成數唯一),采用這個方法構造散列函數是比較合適的。

總結:

實際應用中,應該視不同的情況采用不同的散列函數。
如果關鍵字是英文字符、中文字符、各種各樣的符號,都可以轉換為某種數字來處理,比如其unicode編碼。
下面這些因素可以作為選取散列函數的參考:
1)計算散列地址所需的時間;
2)關鍵字長度;
3)散列表大小;
4)關鍵字的分布情況;
5)查找記錄的頻率。

四:處理散列沖突的方法

沖突:

兩個關鍵字key1和key2不同,但是通過我們的散列函數,得出的散列值相同,這種現象我們稱為沖突,並且把key1和key2稱為這個散列函數的同義詞

(一)開放定址法(線性探測法)

一旦發生了沖突,就去尋找下一個空的散列地址,只要只要散列表足夠大,空的散列地址總能找到,並將記錄存入。

技術分享圖片

技術分享圖片

技術分享圖片

技術分享圖片

技術分享圖片

堆積:

例如上面的48和37這種本來都不是同義詞f(key)不同,卻要爭奪一個地址的情況,我們稱這種現象為堆積。顯然,堆積的出現,使得我們的沖突增加,而且查找和插入的效率降低

二次探測法(雙向):

技術分享圖片

增加平方運算的目的是為了不讓關鍵字都聚集在某一塊區域,使用正負更可以雙向進行探測。我們稱之為二次探測法。

隨機探測法:

技術分享圖片

在沖突時,對於位移量di采用隨機函數計算得到,我們稱之為隨機探測法

總之:開放定址法只要在散列表未填滿時,總是能夠找到不發生沖突的地址,是我們常用的解決沖突的方法

(二)再散列函數法

我們事先準備多個散列函數,當第一個失效,我們就選擇下一個去測試

技術分享圖片

這裏RHi就是不同的散列函數。我們可以把我們之前的講的所有方法全部用上。每當發生散列地址沖突,我們就換一個散列函數來計算。
可以使得關鍵字不聚集,但是增加了計算的時間

(三)鏈地址法

產生沖突不換地方,將所有的關鍵字為同義詞的記錄存儲在一個單鏈表中,我們稱之為同義詞子表,在散列表中只存儲所有同義詞子表的頭指針。無論有多少個沖突,都只是在給單鏈表增加結點
{12,67,56,16,25,37,22,29,15,4748,34}

技術分享圖片

不會產生地址沖突,但是查找時需要遍歷單鏈表會帶來性能損耗

(四)公共溢出區法

將產生沖突的數據帶走,為所有沖突的關鍵字建立一個公共的溢出區來存放
{12,67,56,16,25,37,22,29,15,4748,34}

技術分享圖片

在沖突的數據很少的情況下,其性能還不錯

五:散列表查找實現

技術分享圖片

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

#define SUCCESS 1
#define UNSUCCESS 0
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

#define HASHSIZE 12    //定義初始散列表長為數組的長度
#define NULLKEY -32768

typedef int Status;

typedef struct
{
    int *elem;    //數據元素存儲基址
    int count;    //當前數據元素個數
}HashTable;

int m=0;    //全局存放散列表長

//初始散列表
Status InitHashTable(HashTable* H)
{
    int i;
    m = HASHSIZE;
    H->count = m;
    H->elem = (int *)malloc(sizeof(int)*m);
    for (i = 0; i < m; i++)
        H->elem[i] = NULLKEY;
    return OK;
}

//設置散列函數,使用除留余數法
int Hash(int key)
{
    return key % m;
}

//插入關鍵字進入散列表
void InsertHash(HashTable* H, int key)
{
    int addr = Hash(key);    //求散列地址
    while (H->elem[addr]!=NULLKEY)
        //使用開放定址法解決沖突
        addr = (addr + 1) % m;
    H->elem[addr] = key;
}

//散列表查找數據
Status SearchHash(HashTable H, int key, int *addr)
{
    *addr = Hash(key);
    while (H.elem[*addr]!=key)
    {
        *addr = (*addr + 1) % m;
        if (H.elem[*addr] == NULLKEY || *addr == Hash(key))
            return UNSUCCESS;    //若是循環回到源點或者找不到數據,查找失敗
    }
    return SUCCESS;
}

int main()
{
    HashTable H;
    int i,addr;
    int a[12] = { 12, 67, 56, 16, 25, 37, 22, 29, 15, 47, 48, 34 };
    char ch;

    InitHashTable(&H);
    for (i = 0; i < 12;i++)
        InsertHash(&H,a[i]);
    while (1)
    {
        printf("input cmd to operate:(S:Search Q:Quit)\n");
        scanf("%c", &ch);
        if (ch == Q)
            break;
        printf("input number to search:\n");
        scanf("%d", &i);
        if (SearchHash(H, i, &addr))
            printf("find number in %d\n", addr);
        else
            printf("not find number\n");
        getchar();
    }

    system("pause");
    return 0;
}

技術分享圖片

六:散列表查找性能分析

技術分享圖片

技術分享圖片

數據結構(六)查找---散列表(哈希表)查找