雜湊表也叫散列表,散列儲存結構主要是面向尋找的。
散列技術是在記錄的儲存位置和它的關鍵字之間建立一個確定的對應關係f,使得每個關鍵字key對應一個儲存位置f(key)。 散列地址/儲存位置 = f(關鍵字)
雜湊表/散列表的構造主要依賴兩個問題的解決: 1、散列函數的構造方法; 2、散列衝突的處理方法。
構造散列函數的兩個原則: 1、計算簡單; 2、散列地址/儲存位置/f()函數值均勻分布。
散列函數的構造方法:
| 構造方法 |
散列函數 |
應用情境 |
| 直接定址法 |
f(key)= a * key +b (a、b為常數) |
適合於知道關鍵字分布,且尋找表較小且連續的情況。 |
| 數字分析法 |
抽取部分數字進行諸如:反轉、左環位移、右環位移、前兩數與後兩數相加 |
適合於知道關鍵字分布,且關鍵字位元較多,關鍵字的若干位分布比較均勻的情況。 |
| 平方取中法 |
數字平方後取中間的幾位 |
適合於不知道關鍵字分布,且關鍵字位元不多的情況。 |
| 摺疊法 |
將數字串平均分幾個部分後求和 |
適合於不知道關鍵字分布,且關鍵字位元較多的情況。 |
| 除留餘數法(最常用) |
f(key)= key mod p(p <= m) |
NA |
| 隨機數法 |
f(key)= random(key) |
適合於關鍵字長度不等的情況。 |
散列衝突的處理方法:
| 處理方法 |
散列函數 |
| 開放定址法(線性探測) |
fi(key)= (f(key)+ di) MOD m
di = 1, 2, 3, ...., m-1 |
| 開放定址法(二次探測) |
fi(key)= (f(key)+ di) MOD m di = 1^2, -1^2, 2^2, -2^2, ..., q^2, -q^2, q<=m/2 |
| 開放定址法(隨機探測) |
fi(key)= (f(key)+ di) MOD m di是一個偽隨機數列,即:用同樣的隨機種子,每次得到的數列是相同的。 |
| 再散列函數法 |
fi(key)= RHi(key) i = 1, 2, ..., k RHi是不同散列函數,可以使用不同的散列函數構造方法構造。 |
| 鏈地址法 |
散列表中只儲存指標,指標指向同義字子表。 |
| 公用區溢出法 |
散列表中只儲存一個記錄,其他同義字儲存在公用溢出區。 |
散列表尋找效能的分析角度: 1、散列函數是否均勻; 2、處理衝突的方法; 3、散列表的裝填因子:裝填因子 = 填入表中的記錄個數 / 散列表的長度。裝填因子越大,衝突的可能性越大。通常做法是將散列表的長度設定得比尋找集合大,以空間換時間,提高尋找效率。
// Filename: hash.h#ifndef HASH_H_INCLUDED#define HASH_H_INCLUDED#define HASH_TABLE_SIZE 10 // Hash表大小,根據實際情況修改typedef int HElemType; // Hash表中儲存的元素類型,根據實際情況修改#define HASH_ELEM_INVALID_VALUE 0x7FFFFFFF // 無效的元素值,假設為最大正整數typedef struct HashTable{ HElemType *pElem; // Hash表,數組形式,動態分配, 每個元素初始值為HASH_ELEM_INVALID_VALUE int totalnum; // Hash表中能夠儲存的元素,Hash表申請後固定不變 int existnum; // Hash表中已經儲存的元素,初始為0,每插入一個加一}HashTable;#define HASH_ERROR_INIT 1#define HASH_ERROR_MALLOC 2#define HASH_ERROR_KEY 3#define HASH_ERROR_FULL 4#define HASH_ERROR_NOT_FOUND 5int hash_main();#endif // HASH_H_INCLUDED
// Filename: hash.c#include <stdio.h>#include <stdlib.h>#include "public.h"#include "hash.h"// 雜湊表初始化,雜湊表長度為sizeint InitHashTable(HashTable *pH, int size){ int i = 0; if ((!pH) || (size <= 0)) { return HASH_ERROR_INIT; } pH->pElem = malloc(sizeof(HElemType) * size); if (!pH->pElem) { return HASH_ERROR_MALLOC; } for (i = 0; i < size; i++) { pH->pElem[i] = HASH_ELEM_INVALID_VALUE; } pH->totalnum = size; pH->existnum = 0; return OK;}// 散列函數:計算散列地址int Hash(int key){ return key % HASH_TABLE_SIZE;}// 尋找關鍵字,返回散列地址int SearchHash(HashTable *pH, int key, int *pAddr){ if ((!pH) || (!pAddr)) return ERROR; // key值不能和Hash表元素的初始預設值相同,否則就會被後面的元素覆蓋 if (HASH_ELEM_INVALID_VALUE == key) return HASH_ERROR_KEY; // 進行尋找操作 *pAddr = Hash(key); while (pH->pElem[*pAddr] != key) { *pAddr = (*pAddr + 1) % HASH_TABLE_SIZE; if ((HASH_ELEM_INVALID_VALUE == pH->pElem[*pAddr]) || (*pAddr == Hash(key))) { return HASH_ERROR_NOT_FOUND; } } return OK;}// 插入元素,衝突處理方法:開放定址法線性探測int InsertHash(HashTable *pH, int key){ int addr; if (!pH) return ERROR; // key值不能和Hash表元素的初始預設值相同,否則就會被後面的元素覆蓋 if (HASH_ELEM_INVALID_VALUE == key) return HASH_ERROR_KEY; // 表滿則無法插入,返回錯誤 if (pH->existnum >= pH->totalnum) return HASH_ERROR_FULL; // 已經插入的元素,不能重複插入 if (OK == SearchHash(pH, key, &addr)) { printf("該元素已經在雜湊表中,無需重複插入\n"); return OK; } // 進行插入操作 addr = Hash(key); while (HASH_ELEM_INVALID_VALUE != pH->pElem[addr]) { addr = (addr + 1) % HASH_TABLE_SIZE; } pH->pElem[addr] = key; pH->existnum++; return OK;}// 刪除關鍵字,有則刪除,沒有不做操作int DeleteHash(HashTable *pH, int key){ int addr; if (!pH) return ERROR; // key值不能和Hash表元素的初始預設值相同,否則就會被後面的元素覆蓋 if (HASH_ELEM_INVALID_VALUE == key) return HASH_ERROR_KEY; if (OK == SearchHash(pH, key, &addr)) { if (pH->existnum <= 0) pH->existnum = 1; // 此處應提示異常,說明程式實現有問題; pH->pElem[addr] = HASH_ELEM_INVALID_VALUE; pH->existnum--; } //printf("DeleteHash(): 未找到待刪除元素%d.", key); return OK;}// 列印雜湊表void PrintHash(HashTable *pH){ int i; if (!pH) return; printf("雜湊表: 空間大小: %d, 元素數量: %d.\n", pH->totalnum, pH->existnum); printf("-----------------------------------\n"); printf("下標 元素值\n"); printf("-----------------------------------\n"); for (i = 0; i < pH->totalnum; i++) { printf("%-8d%-6d\n", i, pH->pElem[i]); } printf("-----------------------------------\n"); return;}int hash_main(){ int input; int addr; int key; int retCode; HashTable H;while (1){printf("----------------------------------\n");printf("雜湊表基本操作\n");printf("----------------------------------\n");printf("0.建立/初始化雜湊表\n");printf("1.插入元素\n");printf("2.刪除元素\n");printf("3.尋找元素\n");printf("4.列印雜湊表\n");printf("9.退出系統\n");printf("----------------------------------\n");printf("請選擇:");scanf("%d", &input);getchar();switch (input){ case 0: retCode = InitHashTable(&H, HASH_TABLE_SIZE); if (OK == retCode) { printf("建立/初始化雜湊表成功!\n"); } else { printf("建立/初始化雜湊表失敗!!!\n"); } break; case 1: printf("請輸入待插入元素: --> "); scanf("%d", &key); retCode = InsertHash(&H, key); if (OK == retCode) { printf("插入元素成功!\n"); } else { printf("插入元素失敗!!!\n"); } break; case 2: printf("請輸入待刪除元素: --> "); scanf("%d", &key); retCode = DeleteHash(&H, key); if (OK == retCode) { printf("刪除元素成功!\n"); } else { printf("刪除元素失敗!!!\n"); } break; case 3: printf("請輸入待尋找元素: --> "); scanf("%d", &key); retCode = SearchHash(&H, key, &addr); if (OK == retCode) { printf("尋找元素成功, 所在位置為: %d.\n", addr); } else { printf("尋找元素失敗!!!\n"); } break; case 4: PrintHash(&H); break; case 9: printf("系統已退出。\n"); exit(0); default: printf("無效,請重新選擇操作。\n"); exit(0);}} return 0;}