一、概述
這是哈希的一種算法,算是衝突比較少的,但是也難免會有。
這是我們需要探究的問題,開鏈法就是好方法之一。
開鏈法原理比較簡單,代碼比較玄學,大家要在學習的過程之中動手模擬,才能完全掌握它。
int hash(string h)
{
int seed=37,p=10007;
int ans=0;
int len=h.length();
for(int i=0;i<n;i++)
{
ans=(ans*seed+h[i]-'0')%p;
}
return ans;
}
二、原理分析
結構假如要用形象來表示,就如下圖所示:
假如說我們要對以下一些數據進行哈希儲存(%53):
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
0 | 3 | 51 | 53 | 55 | 105 | 106 | 2 | 52 |
0 | 3 | 51 | 0 | 2 | 52 | 0 | 2 | 52 |
*第一行是編號,第二行是原始數據,第三行是哈希後結果。
按開鏈法儲存之後如圖所示:
三、代碼講解
原理十分簡明,而代碼卻不好實現,需要大家細細揣摩。
操作時代碼從下向上看的!
首先,關於hash的大多數程序實現都離不開兩個數組:
first[i]:表示哈希值爲i的第一個數的下標(用來記錄第i列最末的數據是第幾個輸入的)
next[i]:表示第i個數據的下一個數據(用來記錄第i個數的上方)
就拿第一列來說吧
當讀入0時 first[0]=1;next1]=0;
當讀入53時 first[0]=2;next[2]=1;
當讀入106時 first[0]=3;next[3]=2;
比如說我要進行查找,慧聰first[hash(h)]開始,逐個向上查找。
四、程序設計
對於一個程序來說,有一些基本操作是需要掌握的,難度主要是while的操作和兩個數組的相關性。
部分代碼爲玄學操作,建議帶入模擬一下。
給個哈希簡單代碼,之後的程序圍繞它來展開:
int hash(string h)
{
int seed=37,p=10007;
int ans=0;
int len=h.length();
for(int i=0;i<n;i++)
{
ans=(ans*seed+h[i]-'0')%p;
}
return ans;
}
1、插入
尤其注意兩個數之間的關係,建議帶數據模擬!
int add(string h)
{
int sum=hash(h);//用sum來記錄該數據所存的數組
int u=first[sum];//這個主要用於下一步的去重
while(u)//這一步用來去重,原理是借組next數組枚舉查找,這個while是比較巧妙的寫法
{
if(data[u]==h)//一模一樣
return 0;//返回
u=next[u];//使用next數組接着往下(也就是上)找
}
++cnt;//這是添加的第幾個數據
data[cnt]=h;//將原始數據保存在data數組中,也就是在for循環時就直接對輸入進行操作
//下面兩步個人認爲有點鏈式結構的感覺 ,難點就是這裏
next[cnt]=first[sum];//第cnt個數據的下(也就是上)一個數據是第幾個cnt
first[sum]=cnt;//更新本數列最下端的數爲第幾cnt
return 1;//成功添加
}
2、查找
插入的翻版,對while判定的改變。
int find(string h)
{
int sum=hash(h);//同上
int u=hash[sum];//同上
while(u)//注意改變
{
if(data[u]==h)//如果有
return 1;//ture
u=next[u];//接着找
}
return 0;//都沒有就爲假
}
3、刪除
本板塊的難點在pre上。
void delete(string h)
{
int sum=hash(h);//找到它的列
int pre,u;//pre的作用是判斷刪除的是不是頭結點,u還是記錄它上面的值
pre=u=first[sum];//記錄
while(u)
{
if(data[u]==h) break;//找到了這個數據,往下走
//這兩步可以使u比pre早一個版本
pre=u;
u=next[u];
}
if(pre==u) first[sum]=next[u];//刪除的節點是頭結點,隊列末尾位次編號修改一下
else next[pre]=next[u];//刪除的節點不是頭結點 ,鏈表中撿摘除一部分後重新鏈接
}