散列

基本概念

  1. 散列是一種用於以常數平均時間執行插入、刪除和查找的技術
  2. 理想的散列表數據結構是一個包含有關鍵字的具備固定大小的數組,表的大小記做TableSize,習慣上使表從0TableSize - 1變化,每一個關鍵字被映射到0TableSize - 1範圍中的某個數並被放在合適的單元中,這個映射就叫散列函數
  3. 當兩個關鍵字散列到同一個值的時候就會產生衝突,由於單元數目有限而關鍵字無窮多,處理衝突的方法有分離連接法和開放定址法等
  4. 散列的通常作法是對\(Key\)作一些運算,最後獲得一個\(HashVal\),而後使\(Key\) \(mod\) \(TableSize\),困難之處在於須要使得\(HashVal\)分佈的儘可能均勻,通常考慮將\(TableSize\)儘量爲一個素數
  • 好比,考慮將字符串的ASCII值相加(一種簡單的散列)
typedef unsigned int Index;

Index Hash(const char *Key, int TableSize)
{
    unsigned int HashVal = 0;
    while (*key != '\0') HashVal += *Key++;
    return HashVal % TableSize;
}
  • 好比,取前三個字符,每一個給予不一樣的權重(一種不太好的散列)
Index Hash(const char *Key, int TableSize)
{
    return (Key[0] + 27 * Key[1] + 729 * Key[2]) % TableSize;
}
  • 好比,使用一個更復雜的公式\(\sum^{KeySize - 1}_{i = 0}{Key[KeySize - i -1] * 32^i}\)得到\(HashVal\)
Index Hash(const char *Key, int TableSize)
{
    unsigned int HashVal = 0;
    while (*Key != '\0') HashVal = (HashVal << 5) + *Key++;
    return HashVal % TableSize;
}

解決衝突

分離連接法

它的作法就是將散列到同一個值的全部元素保留到一個如圖所示的鏈表數組中數組

//分離連接散列表類型聲明
struct ListNode;
typedef struct ListNode *Position;
struct Hashbl;
typedef struct Hashbl *HashTable;

struct ListNode
{
    ElementType Element;
    Position Next;
};

typedef Position List;
struct Hashbl
{
    int TableSize;
    List* TheLists;
};

//初始化
HashTable InitializeTable(int TableSize)
{
    HashTable H;
    int i;

    if (TableSize < MiniTableSize)
    {
        Error("Table size too small");
        return NULL;
    }

    H = malloc(sizeof(struct HashTbl));
    if (H == NULL) FatalError("out of space");
    H->TableSize = NextPrime(TableSize);
    H->TheLists = malloc(sizeof(List) * H->TableSize);
    if (H->TheLists == NULL) FatalError("out of space");

    for (i = 0; i < H->TableSize; i++)
    {
        H->TheLists[i] = malloc(sizeof(struct ListNode));
        if (H->TheLists[i] == NULL) FatalError("out of space");
        else H->TheLists[i]->Next = NULL;
    }
    return H;
}

//尋找一個值
Position Find(Element Key, HashTable H)
{
    Position P;
    List L;
    L = H->TheLists[Hash(Key, H->TableSize)];
    P = L->Next;
    while (P != NULL && P->Element != Key) P = P->Next;
    return P;
}

//插入一個值
void Insert(ElementType Key, HashTable H)
{
    Position Pos, NewCell;
    List L;
    
    Pos = Find(Key, H);
    if (Pos == NULL)
    {
        NewCell = malloc(sizeof(struct ListNode));
        if (NewCell == NULL) FatalError("out of space");
        else
        {
            L = H->TheLists[Hash(Key, H->TableSize)];
            NewCell->Next = L->Next;
            NewCell->Element = Key;
            L->Next = NewCell;
        }
    }
}

如上面的代碼所示,它將新元素插入到表的最前面,不只是由於方便,更是由於新插入的元素有可能最早被訪問,這裏使用了鏈表,天然還能夠想到用二叉查找樹,甚至另外一個散列表來實現,這裏使用了表頭,在空間不足時能夠考慮不使用表頭數據結構

定義散列表的裝填因子\(\lambda\)爲散列表中的元素個數與散列表大小的比值函數

開放定址法

簡介

開放定址散列法是另外一種不用鏈表解決衝突的辦法,在開放定址法中,若是有衝突發生,就選擇嘗試另外的單元,直到找到空的單元爲止,單元\(h_0(X)\)\(h_1(X)\)\(h_2(X)\)等相繼被試選,其中\(h_i(X) = (Hash(X) + F(i))\) \(mod\) \(TableSize\)\(F(0) = 0\),函數\(F\)爲衝突解決方法spa

線性探測法

\(F(i) = i\)的情形,就是逐個探測每一個單元格以查找出一個空單元,這種方法的一個問題就是即便表格相對較空,佔據的單元也會造成一些區塊,其結果稱爲一次聚焦code

平方探測法

\(F(i) = i^2\)的情形,它消除了線性探測的一次聚焦問題,關於它有一個定理:若是使用平方探測,且表的發小是素數(兩個條件都要知足),那麼當表至少有一半是空的時候,總可以插入一個新的值。雖然平方探測消除了一次聚焦,可是散列到同一位置的元素將探測相同的備選單元,稱爲二次聚焦,模擬結果指出,它通常要引發另外的少於一半的探測blog

雙散列

再散列

可擴散列

相關文章
相關標籤/搜索