hash,分离链接法,探测散列表(二次探测),再散列的代码

9 篇文章 0 订阅

数据结构与算法分析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;
};
*/

//第三种,平方探测排除了一次聚集(就是用线性探测,会使插入的元素很可能都挨着),但是造成了二次聚集(就是散列到同一位置上的元素将探测相同的备选单元)
//用双散列,可以消除二次聚集
//但是实践中一般用平方探测能更简单更快



//再散列,在平方探测中,如果插入的大于表的二分之一了,就要再建一个大约两倍大的表,然后把原来表的元素算新的散列值再插到新表







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值