数据结构与算法分析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;
};
*/
//第三种,平方探测排除了一次聚集(就是用线性探测,会使插入的元素很可能都挨着),但是造成了二次聚集(就是散列到同一位置上的元素将探测相同的备选单元)
//用双散列,可以消除二次聚集
//但是实践中一般用平方探测能更简单更快
//再散列,在平方探测中,如果插入的大于表的二分之一了,就要再建一个大约两倍大的表,然后把原来表的元素算新的散列值再插到新表