C++(数据结构与算法):31---散列(哈希)冲突之线性探查法(附编码实现)

一、线性探查法

二、图示说明

  • 散列函数为f(k)=k%11。且散列表的当前状态如下图所示

  • 当要插入58关键字时,求得其起始桶为3=58%11,但是3号桶已经存放了80,那么就将其插入到4的索引处

  • 此时再插入一个关键字24,其起始桶为2=24%11,那么直接插入桶2处

  • 当再要插入35时,求得其起始桶为2=35%11,但是桶2已经有元素了,那就向后移动,一直移动到桶5处才有地方存储,那么就存储在桶5处

  • 这就是线性探查法的基本思想

三、线性探查法下关键字的增加、查询、删除

增加:

  • 就是上面图示说明所介绍的思想

查询:

  • 1.首先根据要查找的关键字k,首先搜索起始桶f(k),如果起始桶就是要查找的关键字就退出(查找的关键字不存在);如果起始桶为空就退出;如果起始桶不为空,说明产生了散列冲突就进行下一步
  • 2.接着向后面遍历,如果查找到了就返回;如果再向后遍历的时候遇到了空桶就退出
  • 3.如果向后遍历的时候循环遍历又再次回到起始桶f(k)还是没有找到就退出(查找的关键字不存在)

删除:

  • 删除一个关键字需要删除之后保证查询操作可以正常进行,例如下面如果删除了关键字58,那么35这个关键字就永远也找不到了,因为删除58之后,桶4被置位空。当查找35的时候首先查找桶2、再对比桶3、再对比桶4,发现桶4为空,那么就退出查找(此时35显示未查找到,实际上存在于散列表中)

  • 删除的方法①:删除之后,从删除的下一个桶开始,逐个检查每个桶,以确定要移动的元素,直到达到一个空桶或者回到删除位置为止就退出移动操作(但是需要注意,不要把一个数对移到它的起始桶之前,否则对这个数的查找就可能失败)。例如删除下面的58之后,就要把35移到桶4处,其余元素不动
  • 删除的方法②:为每个桶增加一个域neverUsed,思想如下:
    • 在散列表初始化时,每个域被置位true。当一个关键字被插入桶之后,域neverUsed被置为false。当关键字被删除之后,域neverUsed也不会被重新置为true
    • 在增加了域neverUsed的散列表中,查找元素在遇到空桶时不会退出查找,而是遇到一个桶为true时才退出。因此删除一个散列表的元素之后,不需要考虑移动元素了,因为查找操作遇到空桶但是桶的域neverUsed为false还继续会向后查找
    • 为了提高性能,如果桶中的所有或大多数桶的域neverUsed变为false之后,搜索操作可能会失败。因此当很多桶的域neverUsed变为false之后,就必须重新组织这个散列表

四、随机探查分析

五、选择一个除数D

六、编码实现

头文件定义

#include <iostream>
#include <string>

using std::cout;
using std::endl;
using std::cin;
using std::string;
using std::pair;

异常类的处理

  • 哈希表满时抛出
class hashTableFull
{
public:
	hashTableFull(std::string theMessage ="The hash table is full")
	{
		message = theMessage;
	}
	const char* what() {
		return message.c_str();
	}
private:
	std::string message;
};

关键字映射整数模板类

  • 如果插入的关键字为字符串,那么就需要把关键字映射为一个整数,然后运用散列函数求得起始桶,然后进行插入
  • 下面建立了一系列的模板类,用来针对传入的关键字数据类型,将其转换为一个非负的整数,然后运用于散列函数
  • 详细介绍也可以参见文章中的七:https://blog.csdn.net/qq_41453285/article/details/103517420
template<class K> class hash;

template<>
class hash<std::string>
{
public:
	std::size_t operator()(const std::string theKey)const
	{
		unsigned long hashValue = 0;
		int length = (int)theKey.length();
		for (int i = 0; i < length; i++) {
			hashValue = hashValue * 5 + theKey.at(i);
		}

		return std::size_t(hashValue);
	}
};

template<>
class hash<int>
{
public:
	std::size_t operator()(const int theKey) const
	{
		return std::size_t(theKey);
	}
};

template<>
class hash<long>
{
public:
	std::size_t operator()(const long theKey) const
	{
		return std::size_t(theKey);
	}
};

类定义

template<class K,class E>
class hashTable
{
public:
	hashTable(int theDivisor = 11);
	~hashTable();

	bool empty()const;
	int size()const;
	std::pair<const K, E>* find(const K& theKey)const;
	void insert(const std::pair<const K,E>& thePair);
	void output(std::ostream& out)const; //打印散列表
private:
	int search(const K& theKey)const;
private:
	std::pair<const K, E>** table; //散列表
	hash<K> hash; //把类型K映射为一个整数
	int dSize;    //字典中的数对个数
	int divisor;  //散列函数除数
};

构造函数

template<class K, class E>
hashTable<K, E>::hashTable(int theDivisor = 11)
{
	this->divisor = theDivisor;
	this->dSize = 0;

	//将整个散列表置空
	this->table = new std::pair<const K, E>*[theDivisor];
	for (int i = 0; i < theDivisor; ++i)
		this->table[i] = nullptr;
}

析构函数

template<class K, class E>
hashTable<K, E>::~hashTable()
{
	delete[] this->table;//释放数组
	this->table = nullptr;
}

empty()、size()函数

template<class K, class E>
bool hashTable<K, E>::empty()const
{
	return (this->dSize == 0);
}

template<class K, class E>
int hashTable<K, E>::size()const
{
	return this->dSize;
}

search函数

  • 这个函数返回一个桶序号b,有3中用途
    • ①table[b]是一个指针,指向关键字为theKey的数对
    • ②散列表没有关键字为theKey的数对,且table[b]=nullptr,则可以把关键字theKey插入散列表
    • ③散列表没有关键字为theKey的数对,但table[b]!=nullptr,table[b]!=theKey,表示表已满
template<class K, class E>
int hashTable<K, E>::search(const K& theKey)const
{
	int i = ((int)this->hash(theKey) / this->divisor);
	int j = i;

	//循环遍历
	do {
		//如果桶的关键字为要查找的关键字或者桶为空退出,返回桶索引
		if ((this->table[j]->first == theKey) || (this->table[j] == nullptr)) 
			return j;
		j = ((j + 1) % this->divisor);
	} while (j != i);

	return j;
}

find函数

template<class K, class E>
std::pair<const K, E>* hashTable<K, E>::find(const K& theKey)const
{
	int b = this->search(theKey);

	//没有查找到
	if ((this->table[b] == nullptr) || this->table[b]->first != theKey)
		return nullptr;

	//查找到,返回
	return this->table[b];
}

insert函数

template<class K, class E>
void hashTable<K, E>::insert(const std::pair<const K, E>& thePair)
{
	int b = this->search(thePair.first);

	//如果为空就插入
	if (this->table[b] == nullptr) {
		this->table[b] = new std::pair<const K, E>(thePair);
		this->dSize++;
	}
	else{
		//如果关键字与插入的关键字相同,更新至
		if (this->table[b]->first == thePair.first)
			this->table[b]->second = thePair.second;
		else//满了,抛出异常
			throw hashTableFull();
	}
}

主函数

int main()
{
	hashTable<int,int>* myHashTable = new hashTable<int, int>(11);

	myHashTable->insert(std::pair<int,int>(80,1));
	myHashTable->insert(std::pair<int, int>(40, 2));
	myHashTable->insert(std::pair<int, int>(65, 3));

	myHashTable->output(std::cout);
	std::cout << std::endl;

	myHashTable->insert(std::pair<int, int>(58, 4));
	myHashTable->insert(std::pair<int, int>(24, 5));

	myHashTable->output(std::cout);
	std::cout << std::endl;

	myHashTable->insert(std::pair<int, int>(35, 5));
	
	myHashTable->output(std::cout);
	std::cout << std::endl;

	return 0;
}
  • :前为索引号,:后为关键字值

性能分析

 

  • 0
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

董哥的黑板报

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值