數據結構與算法分析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;
};
*/
//第三種,平方探測排除了一次聚集(就是用線性探測,會使插入的元素很可能都挨着),但是造成了二次聚集(就是散列到同一位置上的元素將探測相同的備選單元)
//用雙散列,可以消除二次聚集
//但是實踐中一般用平方探測能更簡單更快
//再散列,在平方探測中,如果插入的大於表的二分之一了,就要再建一個大約兩倍大的表,然後把原來表的元素算新的散列值再插到新表