采用分離鏈接法的HashTable的實現


首先給出一個對字符串比較好的散列函數,在有些地方把該算法稱為“均勻哈希算法”。

//提供一個對string進行散列的函數
int hashString(const string &str){
	string s;
	if(str.size()>1024)		//如果str太長,則只取前1024個字符
		s=str.substr(0,1024);
	else
		s=str;
	int rect=0;
	for(int i=0;i<s.size();++i)
		rect=rect*37+s[i];		//Horner法則
	return rect;
}

這個散列函數可能會溢出,導致返回值為負數。注意這里選擇的數字是37,好在哪里我也無法證明,至少可以看到素數在散列函數中十分有用。

再好的散列函數也會產生沖突(collision),而且沖突還時有發生,所以一個HashTable必須考慮沖突如何解決。方法有:

  1. 分離鏈接法(separate chaining),后面會詳細介紹。
  2. 線性探測法。就用采用散列函數h0發現有沖突時就采用h1,如果還有沖突就采用h2……其中hi(x)=[ h(x) + f(i) ] mod tableSize。 f(i)為i的線性函數,通常取f(i)=i。
  3. 平方探測法。與線性探測法法類似, f(i)為i的二次函數,通常取f(i)=i^2。
  4. 雙散列。f(i)=i·h2(x)。

分離鏈接法就是把沖突的元素存放在一個鏈表里。當然除鏈表外也可采用其他數據結構,如二叉查找樹、甚至是另外一個散列表,它們的查找速度都比鏈表要快。但是我們期望的是散列函數足夠地好、槽足夠地多,所以對應的鏈表都應該很短,不值得去嘗試更復雜的結構。

槽數為素數時,HashTable的性能會比較好,這就引用一個問題:如何確定一個數是素數?一種快速的判別方法起源於費馬小定理

如果對於任意滿足1 < b < p的b下式都成立:

 b^(p-1)≡1(mod p)
 則p必定是一個質數。
//模c下求a的b次方
int powermod(int a,int b,int c){
	if(b==0)
		return 1;
	if(b==1)
		return a%c;
	int t=powermod(a,b/2,c);
	if(b&1){		//如果b是奇數
		return t*t*a%c;
	}
	else{
		return t*t%c;
	}
}
//判斷數p是否為素數
bool isPrime(int p){
	for(int i=0;i<100;i++){;
		if(powermod(rand()%(p-1)+1,p-1,p)!=1)
			return false;
	}
	return true;
}

  假設一個散列表能容納n個元素、具有m個槽,定義其裝載因子(load factor)α為n/m。假定散列函數足夠地好,任何一個元素散列到m個槽位的可能性是相同的,且與其他元素已被散列到什么位置上獨立無關(這個假設稱為簡單一致散列simple uniform hashing)。則平均情況下查找一個元素是否在散列表中的時間復雜度是O(1+α)。所以裝載因子α成為我們關注的焦點。

當一個HashTable太滿后,發生沖突的概率就會大大增加。我們的策略是:當達到事先設定的裝載因子時,就把槽位擴展成原先的2倍以上(取最小的素數)。這叫再散列。原先的HashTable完全釋放,申請新的更大的空間,然后把已有的元素重新散列到新的HashTable。再散列開銷很大,但我們期望的是發生再散列的次數很少。

template<typename HashObj>
void HashTable<HashObj>::rehash(){
	int oldSlot=getSlots();
	int newSlot=oldSlot*2;
	while(!isPrime((unsigned long)newSlot)){
		newSlot++;
	}
	
	vector<list<HashObj> > oldVector = table;
	for(int i=0;i<table.size();++i)
		table[i].clear();
	table.resize(newSlot);
	setSlots(newSlot);
	capacity=0;
	
	for(int i=0;i<oldVector.size();++i){
		typename list<HashObj>::iterator itr=oldVector[i].begin();
		while(itr!=oldVector[i].end())
			insert(*itr++);
	}
}

有一個語法點:注意17行,當使用含有模板類型的迭代器時,要在前面加一個typename。

再散列需要把之前散列過的key重新做一次散列,這稱為非一致性哈希。一致性哈希要求提供一個hashtable,它能在節點加入離開時不會導致映射關系的重大變化。在某些實際應用中需要使用一致性哈希

下面給出完整代碼:

hash.h

#ifndef _HASH_H
#define _HASH_H

#include<vector>
#include<list>
#include<string>

using namespace std;

//HashObj類型的數據必須提供函數:int hash();operator ==
template<typename HashObj>
class HashTable{
private:
	int capacity;		//已容納的元素的個數
	int slots;			//槽的個數(取素數散列性比較好)
	double alpha;		//裝載因子
	vector<list<HashObj> > table;		//哈希表
	int myhash(const HashObj& ele);		//內部哈希函數,負責把數據映射到[0,slots-1]
	void rehash();		//當達到裝載因子時,槽數擴大為原來的2倍以上(取最小的素數),重新進行再散列
	
public:
	//判斷一個數據是否在HashTable中
	bool contain(const HashObj &ele);
	//插入一個元素到HashTable。插入成功則返回true,如果元素原先就存在於HashTable中,則返回false
	bool insert(const HashObj& ele);
	//從HashTable中刪除一個元素。如果指定元素不在HashTable中,則刪除失敗,返回false
	bool remove(const HashObj &ele);
	HashTable(int slots=10007,double alpha=0.7):slots(slots),alpha(alpha){
		capacity=0;
		table.resize(slots);
	}
	int getSlots(){
		return slots;
	}
	void setSlots(int num){
		slots=num;
	}
};

//提供一個對string進行散列的函數
int hashString(const string &str){
	string s;
	if(str.size()>1024)		//如果str太長,則只取前1024個字符
		s=str.substr(0,1024);
	else
		s=str;
	int rect=0;
	for(int i=0;i<s.size();++i)
		rect=rect*37+s[i];		//Horner法則
	return rect;
}

//由於散列的槽數最好是素數,所以提供一個判別素數的函數
int fmod(int a, int b, int c)//快速模取冪  
{  
    if(b == 1) return a;  
    int t = fmod(a, b / 2, c);  
    t = (t * t) % c;  
    if(b & 1) t = (t * a) % c;  
    return t;  
}  
bool isPrime(int n)//米勒-拉賓算法  
{  
    for(int i = 0; i < 100; ++ i)  
    {  
        if(fmod(rand() % (n - 1) + 1, n - 1, n) != 1)//a的取值為[1,n-1],a的值需要變化,所以用到隨機函數  
            return false;  
    }  
    return true;  
}  

//提供一個滿足HashObj要求的類
class Employee{
private:
	string name;
	int id;
public:
	Employee(string name="",int id=-1):name(name),id(id){}
	bool operator == (const Employee & obj) const{
		return this->name==obj.name;
	}
	int hash()const{
		return hashString(name);
	}
	void setName(string str){
		name=str;
	}
	void setId(int i){
		id=i;
	}
};

#endif

hash.cpp

#include<iostream>
#include<cassert>
#include<algorithm>
#include"hash.h"

template<typename HashObj>
int HashTable<HashObj>::myhash(const HashObj & ele){
	int rect=ele.hash();
	rect%=slots;
	if(rect<0)
		rect+=slots;
	return rect;
}

template<typename HashObj>
bool HashTable<HashObj>::contain(const HashObj & ele){
	int index=myhash(ele);
	const list<HashObj> & whichList=table[index];
	return find(whichList.begin(),whichList.end(),ele)!=whichList.end();
}

template<typename HashObj>
bool HashTable<HashObj>::insert(const HashObj & ele){
	int index=myhash(ele);
	list<HashObj> & whichList=table[index];
	if(find(whichList.begin(),whichList.end(),ele)!=whichList.end())
		return false;
	whichList.push_back(ele);
	capacity++;
	if(capacity*1.0/slots>alpha)
		rehash();
	return true;
}

template<typename HashObj>
bool HashTable<HashObj>::remove(const HashObj & ele){
	int index=myhash(ele);
	list<HashObj> & whichList=table[index];
	typename list<HashObj>::iterator itr=find(whichList.begin(),whichList.end(),ele);
	if(itr==whichList.end())
		return false;
	whichList.erase(itr);
	capacity--;
	return true;
}

template<typename HashObj>
void HashTable<HashObj>::rehash(){
	int oldSlot=getSlots();
	int newSlot=oldSlot*2;
	while(!isPrime((unsigned long)newSlot)){
		newSlot++;
	}
	
	vector<list<HashObj> > oldVector = table;
	for(int i=0;i<table.size();++i)
		table[i].clear();
	table.resize(newSlot);
	setSlots(newSlot);
	capacity=0;
	
	for(int i=0;i<oldVector.size();++i){
		typename list<HashObj>::iterator itr=oldVector[i].begin();
		while(itr!=oldVector[i].end())
			insert(*itr++);
	}
}

int main(){
	const int arrSize=9;
	HashTable<Employee> hashTable(7,1.0);		//剛開始設槽數為7
	string names[arrSize]={"hujintao","jiangzeming","heizeming",
							"chaogai","jingchengwu","liangchaowei",
							"weijiabao","zhamgsanbao","zengxiaoxian"};
	Employee employee[arrSize];
	for(int i=0;i<arrSize;++i){
		employee[i].setName(names[i]);
		employee[i].setId(i+1);
	}

	for(int i=0;i<arrSize;++i){
		hashTable.insert(employee[i]);
	}
	assert(hashTable.getSlots()==17);		//擴容后槽數應該為17
	for(int i=0;i<arrSize;++i){
		assert(hashTable.contain(employee[i]));
		hashTable.remove(employee[i]);
		assert(!hashTable.contain(employee[i]));
	}
	
	return 0;
}

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM