hash,分離鏈接法,探測散列表(二次探測),再散列的代碼

數據結構與算法分析C++版的搬運工......

#include<string>
#include<vector>
#include<list>
using namespace std;
//字符串hash,把每個字符的ASCII碼加起來,然後對tablesize取模,tablesize注意選擇,一種策略是選素數,這樣能比較均勻
//一種很簡單的hash,但是如果tableSize很大的話,分佈就不會均勻
/*
int hash(const string & key, int tableSize)
{
	int hashVal = 0;
	for (int i = 0; i < key.length(); i++)
		hashVal += key[i];
	return hashVal % tableSize;
}
*/
//另一種很簡單的,假設字符串字符至少有三個,然後只看前三個,27表示26個字母加空格。
//如果前三個字符均勻分佈,那麼就可以比較隨機的分配,然而統計顯示並不是隨機的分配...所以也不合適......
/*
int hash(const string & key, int tableSize)
{
	return (key[0] + key[1] * 27 + key[2] + 729) % tableSize;
}
*/

//第三種,利用多項式計算的Horner法則,可能會溢出,結尾處處理。此函數就表的分佈而言未必是最好的,但是確實具有極其簡單的優點而且速度很快。
//鍵很長的話,計算時間會很長,解決辦法是根據情況,不使用所有的字符。
/*
int hash(const string & key, int tableSize)
{
	int hashVal = 0;
	for (int i = 0; i < key.length(); i++)
		hashVal = 37 * hashVal + key[i];
	hashVal %= tableSize;
	if (hashVal < 0)
		hashVal += tableSize;
	return hashVal;
}
*/
//下面解決,如果說產生了衝突(一個元素散列之後發現已經有了)要怎樣做

//第一種,分離鏈接法,就是將散列到同一個值的所有元素保留到一個鏈表中。
//儘可能讓鏈表的長是1(裝填因子儘可能是1)
/*
template <typename HashedObj>     //這裏的HashedObj是一種對象,這裏指提供散列函數和相等性操作符的對象,就像二叉搜索樹指適用於Comparable的對象,就是隻適合能比較的
class HashTable
{
public:
	explicit HashTable(int size = 101);    //explicit必須顯式調用,不能隱式轉換,()賦值是對的,=隱式轉換就不能了。隱式轉換容易造成一些,編譯器沒有報錯,但是確實出錯了的錯誤
	//http://blog.csdn.net/chollima/article/details/3486230  這個網址說了explicit的東西
	bool contains(const HashedObj & x)const;
	void makeEmpty();
	
	void insert(const HashedObj & x);
	void remove(const HashedObj & x);

private:
	vector<list<HashedObj> > theLists;   //>中間放個空格,防止被誤認成一個運算符
	int currentSize;
	void rehash()                      //再散列
	{
		vector<list<HashedObj> > oldLists = theLists;
		theLists.resize(nextPrime(2 * theLists.size()));
		for (int i = 0; i < theLists.size(); i++)
			theLists[j].clear();
		currentSize = 0;
		for (int j = 0; j < oldLists.size(); j++)
		{
			list<HashedObj>::iterator itr = oldLists[i].begin();
			while (itr != oldLists[i].end())
				insert(*itr++);
		}
	}
	int myhash(const HashedObj & x) const;  //將結果分配到一個合適的數組索引中

	void makeEmpty()
	{
		for (int i = 0; i < theLists.size(); i++)
			theLists[i].clear();
	}

	bool contains(const HashedObj &x) const        //查這個表裏是不是有x這個鍵
	{
		const list<HashedObj> & whichList = theLists[myhash(x)];   //取出來x哈希之後的位置的鏈表
		return find(whichList.begin(), whichList.end(), x) != whichList.end();

	}

	bool remove(const HashedObj & x)
	{
		list<HashedObj> & whichList = theLists[myhash(x)];
		list<HashedObj>::operator itr = find(whichList.begin(), whichList.end(), x);
		if (itr == whichList.end())
			return false;
		whichList.erase(itr);
		--currentSize;
		return true;
	}

	bool insert(const HashedObj & x)
	{
		List<HashedObj> & whichList = theLists[myhash(x)];
		if (find(whichList.begin(), whichList.end(), x) != whichList.end())
			return false;
		whichList.push_back(x);
		if (++currentSize > theLists.size())
			rehash();
		return true;
	}
	int myhash(const HashedObj & x) const
	{
		int hashVal = hash(x);
		hashVal %= theLists.size();
		if (hashVal < 0)
			hashVal += theLists.size();
		return hashVal;
	}
};


//提供一個Employee類,該類使用name成員作爲鍵,提供了==和!=相等性操作,還有一個散列函數來實現HashedObj的需求
class Employee
{
public:
	const string & getName() const
	{
		return name;
	}
	bool operator==(const Employee & rhs)const
	{
		return getName() == rhs.getName();
	}
	bool operator!=(const Employee & rhs)const
	{
		return !(*this == rhs);
	}

private:
	string name;
	double salary;
	int seniority;
};

int hash(int key);

int hash1(const string & key);

int hash(const Employee & item)
{
	return hash1(item.getName());        //一個問題,把1去掉之後,說hash不明確,是重載的問題,然而,問題到底出在哪.....???希望能被解答
}



*/

//第二種,不使用鏈表的散列表
//用一個解決衝突的函數,這個函數能在發生衝突的時候再選址。這個方案需要的表要比分離鏈接法的大,裝填因子低於0.5。這樣的表叫探測散列表
//探測散列表中不能執行標準刪除,因爲相應的但願可能引起過沖突,元素繞過它存儲在別處,把這個刪了就找不到後面的了,所以要刪除的話要懶惰刪除
/*
template <typename HashedObj>
class HashTable
{
public:
	explicit HashTable(int size = 101) :array(nextPrime(size))
	{
		makeEmpty();
	}
	bool contains(const HashedObj & x)const
	{
		return isActive(findPos(x));
	}
	void makeEmpty()
	{
		currentSize = 0;
		for (int i = 0; i < array.size(); i++)
			array[i].inof = EMPTY;
	}
	bool insert(const HashedObj & x)
	{
		int currentPos = findPos(x);
		if (isActive(currentPos))
			return false;
		array[currentPos] = HashEntry(x, ACTIVE);
		if (++currentSize>array.size() / 2)
			rehash();
		return true;
	}
	bool remove(const HashedObj & x)
	{
		int currentPos = findPos(x);
		if (!isActive(currentPos))
			return false;
		array[currentPos].info = DELETE;
		return true;
	}
	enum EntryType{ ACTIVE, EMPTY, DELETED };    //枚舉類型。C++中如果要定義常量的話,最好用枚舉類型或const,這樣會有一個類型檢查,define只是簡單的替換,就不要用了

	//const HashedObj & e=HashedObj()這個是一個初始化,具體的用,const int & e=int()就好理解了,就是初始化成一個int類型
	//如果看一個class看的暈,就別看函數,看這樣子不是函數的成員,就知道這個類是有什麼的,函數是一些功能。比如這裏,hashTable把所有的單元用一個vector表示,每個單元是HashEntry類型的。用currentSize表示這個hashTable的大小
	//這裏一個很迷的問題,只要加註釋就會有錯,不知道爲什麼
	//可能是上面的干擾太多
private:
	struct HashEntry
	{
		HashedObj element;
		EntryType info;
		HashEntry(const HashedObj & e = HashedObj(), EntryType i = EMPTY):element(e), info(i){}

	};
	vector<HashEntry> array;
	int currentSize;
	bool isActive(int currentPos) const
	{
		return array[currentPos].info == ACTIVE;
	}
	int findPos(const HahsedObj & x)const           //這裏用平方探測,平方探測f(x)=f(x-1)+2*x-1,用這個公式逐個探測
	{
		int offset = 1;
		int currentPos = myhash(x);
		while (array[currentPos].info != EMPTY&&array[currentPos].element != x)   //順序不能反,反了的話如果是這個元素從來沒插入過,是空的這種會檢查不出來,循環會死.....
		{                                                                         //關於delete的情況,這裏如果元素被delete了,這個元素是還被留着的,比如48插入了,然後刪除了,再插入58,它也不能插入到48這個位置
			curentPos += offset;
			offset += 2;
			if (currentPos >= array.size())
				currentPos -= array.size();
		}
		return currentPos;
	}
	void rehash()
	{
		vector<HashEntry> oldArray = array;
		array.resize(nextPrime(2 * oldArray.size()));
		for (int i = 0; i < array.size(); i++)
		{
			array[i].info = EMPTY;
		}
		currentSize = 0;
		for (int j = 0; j < oldArray.size(); j++)
		{
			if (oldArray[j].info == ACTIVE)
			{
				insert(oldArray[j].element);
			}
		}
	}
	int myhash(const HashedObj & x)const;
};
*/

//第三種,平方探測排除了一次聚集(就是用線性探測,會使插入的元素很可能都挨着),但是造成了二次聚集(就是散列到同一位置上的元素將探測相同的備選單元)
//用雙散列,可以消除二次聚集
//但是實踐中一般用平方探測能更簡單更快



//再散列,在平方探測中,如果插入的大於表的二分之一了,就要再建一個大約兩倍大的表,然後把原來表的元素算新的散列值再插到新表







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章