首頁 > 軟體

C++資料結構雜湊表詳解

2022-07-19 18:02:22

實現

雜湊表,即雜湊表,可以快速地儲存和查詢記錄。理想雜湊表的儲存和查詢時間都是 O(1)。

本《資料》中雜湊表分以下幾部分:雜湊函數、儲存和查詢時的元素定位、儲存、查詢。刪除操作因為不常用,所以只給出思想,不給出程式碼。

根據實際情況,可選擇不同的雜湊方法。

以下程式碼假設雜湊表不會溢位。

// N表示雜湊表長度,是一個素數,M表示額外空間的大小,empty代表「沒有元素」。
const int N=9997, M=10000, empty=-1;
int a[N];
void init() // 初始化雜湊表
{
memset(a,empty,sizeof(a)); // 注意,只有empty等於0或-1時才可以這樣做!
memset(bucket,empty,sizeof(bucket));
memset(first,0,sizeof(first));
}
inline int h(int); // 雜湊函數
int *locate(int, bool); // 用於儲存和查詢的定位函數,並返回對應位置。
// 如果用於儲存,則第二個引數為true,否則為false①。
void save(int x) // 儲存資料
{
int *p = locate(x, true);
if (p!=NULL) *p=x;
}
bool isexist(int x) // 查詢資料
{
int *p = locate(x,false);
return (p!=NULL && *p==x);
}

雜湊函數

為了達到快速儲存和查詢的目的,就必須在記錄的儲存位置和它的關鍵字之間建立一個確定的對應關係 h。

這個關係 h 叫做雜湊函數。

雜湊表存取方便但儲存時容易衝突:即不同的關鍵字可以對應同一雜湊地址。如何確定雜湊函數和解決衝突是關鍵。以下是幾種常見的雜湊函數的構造方法:

1. 取餘數法:h(x) = x%p(p≤N,且最好是素數)

2. 直接定址法:h(x)=x 或 h(x)=a*x+b

3. 數位分析法:取關鍵字的若干數位(如中間兩位數)組成雜湊地址。

4. 平方取中法:關鍵字平方後取中間幾位陣列成雜湊地址。

5. 摺疊法:將關鍵數位分割成位數相同的幾部分(最後一部分的位數可以不同)然後取幾部分的疊加和(捨去進位)作為雜湊地址。

6. 偽亂數法:事先產生一個亂數序列 r[],然後令 h(x)=r[x]。

設計雜湊函數時,要注意

對關鍵碼值的分佈並不瞭解——希望選擇的雜湊函數在關鍵碼範圍內能夠產生一個大致平均的關鍵碼值隨機分佈,同時避免明顯的聚集可能性,如對關鍵碼值的高位或低位敏感的雜湊函數。

對關鍵碼值的分佈有所瞭解——應該使用一個依賴於分佈的雜湊函數,避免把一組相關的關鍵碼值對映到雜湊表的同一個槽中。

開雜湊方法

雜湊表中難免會發生衝突。使用開雜湊方法可以解決這個問題。常用操作方法是“拉鍊法”,即相同的地址的關鍵字值均鏈入對應的連結串列中。

如果雜湊函數很差,就容易形成長長的連結串列,從而影響查詢的效率。

下面是用“拉鍊法”處理衝突時的定位函數:

int size=-1;
struct node {int v; node * next;} *first[N], mem[M];
#define NEW(p) p=&mem[++size]; p->next=NULL
int * locate(int x, bool ins=false)
{
int p=h(x);
if (a[p]==x && !ins) return &a[p];
// 處理衝突
node *q = first[p];
if (ins)
if (q==NULL)
{
NEW(q);
first[p]=q;
return &q->v;
}
else
{
while (q->next!=NULL) q=q->next;
node *r; NEW(r);
q->next=r;
return &r->v;
}
else
while (q!=NULL)
{
if (q->v == x) return &q->v;
q=q->next;
}
return NULL;
}

閉雜湊方法(開地址方法)

處理衝突的另一種方法是為該關鍵字的記錄找到另一個“空”的雜湊地址。在處理中可能得到一個地址序列 g(i)(i=1,2,…,k;0≤g(i)≤n-1),即在處理衝突時若得到的另一個雜湊地址 g(1)仍發生衝突,再

求下一地址 g(2),若仍衝突,再求 g(3)……怎樣得到 g(i)呢?

溢位桶法:設一個溢位桶,不管得到的雜湊地址如何,一旦發生衝突,都填入溢位桶。

再雜湊法:使用另外一種雜湊函數來定位。

線性探查:g(i)=(h(x)+di) % N,其中 h(x)為雜湊函數,N 為雜湊表長,di 為增量序列。

1. 線性探測再雜湊:di=1,2,3,…,m-1

2. 二次探測再雜湊:

3. 偽隨機探測序列:事先產生一個亂數序列 random[],令 di=random[i]。

下面是用溢位桶處理衝突時的定位函數:

int bucket[M], top=-1; // 用於閉雜湊方法(溢位桶)
int * locate(int x, bool ins=false)
{
int p=h(x);
if (a[p]==x && !ins) // 在查詢模式下碰到了所需的元素
return &a[p];
else if (ins)
{
if (a[p]==empty) // 可以插入
return &a[p];
else // 處理衝突
return &bucket[++top];
}
else // 到溢位桶中尋找元素
for (int i=0; i<=top; i++)
if (bucket[i]==x) return &bucket[i];
return NULL;
}

下面是用線性探查處理衝突的定位函數,當然,它也可以用於再雜湊法處理衝突

inline int g(int p, int i) {return (p+i)%N;} // 根據需要來設計
int * locate(int x, bool ins=false)
{
int p=h(x);
int p2, c=0;
if (a[p]==x && !ins)
return &a[p];
else if (ins)
{
do
{
p2 = g(p, c++);
} while (a[p2]!=empty);
return &a[p2];
} else {
do
{
p2 = g(p, c++);
} while (a[p2]!=x && a[p2]!=empty);
if (a[p2]==x) return &a[p2];
}
return NULL;
}

閉雜湊方法的優點是節省空間。不過,無論是溢位桶,還是線性探查,都會在定址過程中浪費時間。線性

探查的探查序列如果太長,就會使一些其他元素被迫雜湊在其他位置,從而影響了其他元素的查詢效率。

刪除*

如果使用開雜湊方法,那麼可以直接刪除元素。然而,使用閉雜湊方法,是不可以直接刪除元素的。假如

直接刪除,很有可能會影響其他元素的查詢。

在這種情況下,有兩種刪除方法:一種是交換法,另一種是標記法。

交換法:在刪除某元素時,不要立刻把它清除。按照線性探查函數繼續尋找,直到沒有數值為止。將遇到

的最後一個數值與它交換。當然,交換之前還要進行類似的操作,可謂“牽一髮而動全身”。

標記法:開一個標記陣列 flag[]。如果第 i 個元素被刪除了,就將 flag[i]設為 true。

1. 插入元素時,如果所在位置有標記,就把元素放到這裡,並把標記清除。

2. 查詢元素時,如果經過標記,就跳過去繼續查詢。

3. 為了雜湊表的效率,應該定期清理表中的標記(或重新雜湊所有元素)。

到此這篇關於C++資料結構雜湊表詳解的文章就介紹到這了,更多相關C++雜湊表內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com