第八章雜湊
雜湊的含義
散列表(Hash table,也叫雜湊表),是根據關鍵碼值(Key value)而直接進行訪問的資料結構。也就是說,它通過把關鍵碼值對映到表中一個位置來訪問記錄,以加快查詢的速度。這個對映函式叫做雜湊函式,存放記錄的陣列叫做散列表。
對不同的關鍵字可能得到同一雜湊地址,即k1≠k2,而f(k1)=f(k2),這種現象稱為碰撞(英語:Collision)。具有相同函式值的關鍵字對該雜湊函式來說稱做同義詞。綜上所述,根據雜湊函式f(k)和處理碰撞的方法將一組關鍵字對映到一個有限的連續的地址集(區間)上,並以關鍵字在地址集中的“像”作為記錄在表中的儲存位置,這種表便稱為散列表,這一對映過程稱為雜湊造表或雜湊,所得的儲存位置稱雜湊地址。
雜湊函式
(1)直接定址法
取關鍵字的某個線性函式為雜湊地址,Hash(Key)= Key 或 Hash(Key)= A*Key + B,A、B為常數。
(2)除數留餘法
設散列表中允許的地址數為m,取一個不大於m,但接近或者等於m的質數p作為除數,按照雜湊函式:Hash(key) = key mod p p<=m,將關鍵碼轉換成雜湊地址。對p的選擇很重要,一般取素數或m,若p選的不好,容易產生同義詞。
(3)平方取中法
平方後取中間的,每位包含資訊比較多。假設關鍵字是1234,那麼它的平方就是1522756,再抽取中間的3位就是227作為雜湊地址;再比如關鍵字是4321,那麼它的平方就是18671041,抽取中間的3位就可以是671或者710用作雜湊地址。
(4)摺疊法
有時關鍵碼所含的位數很多,採用平方取中法計算太複雜,則可將關鍵碼分割成位數相同的幾部分(最後一部分的位數可以不同),然後取這幾部分的疊加和(捨去進位)作為雜湊地址,這方法稱為摺疊法。
比如:關鍵字是9876543210,散列表表長為三位,我們將它分成四組987|654|321|0|,然後將它們疊加求和987+654+321+0=1962,再求後3位得到雜湊地址為962。有時可能這還不能夠保證分佈均勻,不妨從一段向另一端來回摺疊後對齊相加。比如將987和321反轉,再與654和0相加,程式設計789+654+123+0=1566,此時的雜湊地址為566。
雜湊衝突
解決衝突的技術可以分為兩類:開雜湊方法(open hashing,也稱單鏈方法,separate chaining)和閉雜湊方法(closed hashing,也稱開地址方法,open addressing)。開雜湊方法解決衝突是將衝突記錄在表外,而閉雜湊方法是將衝突記錄在表內的另一個空槽。
(1)開雜湊方法:運用單鏈表儲存方式,不產生堆積現象,但因為附加了指標域而增加了空間開銷。
程式碼如下:
function HashTable()
{
this.table=new Array(137);
this.simpleHash=simpleHash;
this.betterHash=betterHash;
this.showDistro=showDistro;
this.put=put;
this.get=get;
this.buildChains=buildChains;//開鏈法
}
function put(key,data){
// let pos=this.simpleHash(key);
let pos=this.betterHash(key);
let index=0;
while(this.table[pos][index]!=undefined){
index++;
}
this.table[pos][index]=data;
}
function get(key){
// let pos=this.simpleHash(key);
let pos=this.betterHash(key);
let index=0;
if(this.table[pos][index]==key){
return this.table[pos][index];
}else{
while(this.table[pos][index]&&this.table[pos][index]!=key){
index++;
}
return this.table[pos][index];
}
}
//容易發生碰撞
function simpleHash(data){
let total=0;
for(let i=0;i<data.length;i++){
total +=data.charCodeAt(i);
}
return total%this.table.length;
}
//使用霍納演算法的雜湊函式
function betterHash(string){
const H=37;
let total=0;
for(let i=0;i<string.length;i++){
total +=H*total+string.charCodeAt(i);
}
total=total%this.table.length;
if (total<0) {
total +=this.table,length-1;
}
return parseInt(total);
}
function showDistro(){
let n=0;
for(let i=0;i<this.table.length;i++){
if (this.table[i][0] !=undefined) {
console.log(i+":"+this.table[i]);
}
}
}
//開鏈法
function buildChains(){
for(let i=0;i<this.table.length;i++){
this.table[i]=new Array();
}
}
//測試
let someNames=["David","Jennifer","Donnie","Raymond",
"Cynthia", "Mike", "Clayton", "Danny",
"Jonathan","Tom","Jack","Divad","Caylton"];
let hTable=new HashTable();
hTable.buildChains();
for(let i=0;i<someNames.length;i++){
hTable.put(someNames[i],someNames[i]);
}
hTable.showDistro();
(2)閉雜湊方法:運用順序表儲存,儲存效率較高,但容易產生堆積,查詢不易實現,需要用到二次再查詢。閉雜湊方法有桶式雜湊、線性探查、二次探查、雙雜湊方法。
//線性探測法
function HashTable()
{
this.table=new Array(137);
this.values=[];
this.betterHash=betterHash;
this.showDistro=showDistro;
this.put=put;
this.get=get;
}
function put(key,data){
let pos=this.betterHash(key);
while(this.table[pos]!=undefined){
pos++;
}
this.table[pos]=key;
this.values[pos]=data;
}
function get(key){
let pos=this.betterHash(key);
if(this.table[pos]==key){
return this.values[pos];
}else{
while(this.table[pos]&&this.table[pos]!=key){
pos++;
}
return this.values[pos];
}
}
//使用霍納演算法的雜湊函式
function betterHash(string){
const H=37;
let total=0;
for(let i=0;i<string.length;i++){
total +=H*total+string.charCodeAt(i);
}
total=total%this.table.length;
if (total<0) {
total +=this.table,length-1;
}
return parseInt(total);
}
function showDistro(){
let n=0;
for(let i=0;i<this.table.length;i++){
if (this.table[i] !=undefined) {
console.log(i+":"+this.values[i]);
}
}
}
//測試
let someNames=["David","Jennifer","Donnie","Raymond",
"Cynthia", "Mike", "Clayton", "Danny",
"Jonathan","Tom","Jack","Divad","Caylton"];
let hTable=new HashTable();
for(let i=0;i<someNames.length;i++){
hTable.put(someNames[i],someNames[i]);
}
hTable.showDistro();
console.log(hTable.get('Town'));//undefined
console.log(hTable);