哈希冲突的处理【闭散列方法-线性探测和二次探测】

散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表

给定表M,存在函数Hash(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数Hash(key)为哈希(Hash) 函数。

构造哈希表的两种方法

1、直接定址法--取关键字的某个线性函数为散列地址,Hash(Key)=Key 或 Hash(Key)= A*Key+B。

A、B为常数。

2、除留余数法--取关键值被某个不大于散列表长m的数p除后的所得的余数为散列地址。Hash(Key)= Key % P。

直接定址法:

利用数组下标可以很好的将对应的数据存入哈希表对应的位置。例如:在一个字符串中找出第一次只出现一次的字符,字符串为abcdabcdefg,需要找到e,利用下标统计可以很好地解决这个问题,对于这个问题,你必须开辟对应的256个空间。如果需要查找的数中出现了一个特别大的数(1000000),你必须要开辟1000000个空间,会造成大量空间的浪费。

除留余数法:

由于“直接定址法”的缺陷,于是下面引入“除留余数法”,该方法提高的空间的利用率,但不同的Key值经过哈希函数Hash(Key)处理以后可能产生相同的值哈希地址,我们称这种情况为哈希冲突。任意的散列函数都不能避免产生冲突。

下面介绍处理哈希冲突的闭散列方法

1、线性探测

2、二次探测(二次方探测)

下面进行线性探测和二次探测来处理哈希冲突

要使程序可以处理基本类型数据,也可以进行非基本类型的处理,可通过仿函数实现

enum Status//设置状态的数组
{
	EXIST,
	DELETE,
	EMPTY,
};
template<class K, class V>
struct KeyValue//字典
{
	K _key;
	V _value;
	KeyValue(const K& key = K(), const V& value = V())//设置K()和V()为了无参初始化
		:_key(key)
		, _value(value)
	{}
};
//针对string类型,仿函数实现
template<class K>
struct DefaultHashFuncer//基本类型
{
	size_t operator()(const K& key)
	{
		return key;
	}
};
static size_t BKDRHash(const char * str)//字符串哈希算法
{
	unsigned int seed = 131; // 31 131 1313 13131 131313
	unsigned int hash = 0;
	while (*str)
	{
		hash = hash * seed + (unsigned int)(*str++);
	}
	return (hash & 0x7FFFFFFF);
}
template<>
struct DefaultHashFuncer<string>//string类型--模板的特化
{
	size_t operator()(const string& str)
	{
		return BKDRHash(str.c_str());
	}
};
template<class K, class V, class HashFuncer = DefaultHashFuncer<K>>
class HashTable
{
	typedef KeyValue<K, V> KV;
public://进行各函数的实现--进行增删查改
private:
	KV* _table;//存放哈希数
	Status* _status;//存放状态的数组
	size_t _size;//哈希表中哈希数的个数
	size_t _capacity;//哈希表的大小
};

具体实现代码如下:

template<class K, class V, class HashFuncer = DefaultHashFuncer<K>>
HashTable<K, V, HashFuncer>::HashTable()
:_table(NULL)
, _status(NULL)
, _size(0)
, _capacity(0)
{}
template<class K, class V, class HashFuncer = DefaultHashFuncer<K>>
HashTable<K, V, HashFuncer>::HashTable(size_t size)
:_table(new KV[size])
, _status(new Status[size])
, _size(0)
, _capacity(size)
{//不能用memset进行初始化(枚举类型不能用memset)
	for (size_t i = 0; i < _capacity; i++)
	{
		_status[i] = EMPTY;//状态数据初始化为EMPTY
	}
}
template<class K, class V, class HashFuncer = DefaultHashFuncer<K>>
HashTable<K, V, HashFuncer>::~HashTable()
{
	if (_table)
	{
		delete[] _table;
		delete[] _status;
		_size = 0;
		_capacity = 0;
	}
}
template<class K, class V, class HashFuncer = DefaultHashFuncer<K>>
bool HashTable<K, V, HashFuncer>::Insert(const K& key, const V& value)//防止冗余用bool
{
	CheckCapacity(_size + 1);//检查容量,不足增容
	////线性探测
	//size_t index = HashFunc(key);
	//while (_status[index] == EXIST)//如果不为EMPTY或DELETE就不能在此位置处存入key,存在哈希冲突
	//{
	//	if (_table[index]._key == key && _table[index]._value == value)//如果key已存在,就插入失败
	//	{
	//		return false;
	//	}
	//	++index;
	//	if (index == _capacity)//如果哈希到了数组最后一位,就返回到第一位进行哈希
	//	{
	//		index = 0;
	//	}
	//}
	//++_size;
	//_table[index]._key = key;
	//_table[index]._value = value;
	//_status[index] = EXIST;
	//return true;
	//二次探测
	size_t i = 0;
	size_t index = HashFunc0(key);
	while (_status[index] == EXIST)//如果不为EMPTY或DELETE就不能在此位置处存入key,存在哈希冲突
	{
		if (_table[index]._key == key && _table[index]._value == value)//如果key已存在,就插入失败
		{
			return false;
		}
		index = HashFunci(index, ++i);
		if (index >= _capacity)//如果哈希到的位置超过了数组最后一位,就从首位开始求出对应位置
		{
			index = index - _capacity;
		}
    }
	_table[index]._key = key;
	_table[index]._value = value;
	_status[index] = EXIST;
	_size++;
	return true;;
}
template<class K, class V, class HashFuncer = DefaultHashFuncer<K>>
size_t HashTable<K, V, HashFuncer>::HashFunc(const K& key)//求出key在哈希表中的位置
{
	HashFuncer hp;
	return hp(key)%_capacity;//hp(key)调用仿函数
}
template<class K, class V, class HashFuncer = DefaultHashFuncer<K>>
size_t HashTable<K, V, HashFuncer>::HashFunc0(const K& key)
{
	return HashFunc(key);//调用HashFunc函数,找到二次探测最初的位置
}
template<class K, class V, class HashFuncer = DefaultHashFuncer<K>>
size_t HashTable<K, V, HashFuncer>::HashFunci(size_t index, size_t i)
{
	return index + (2 * i - 1);//优化后的算法
}
template<class K, class V, class HashFuncer = DefaultHashFuncer<K>>
void HashTable<K, V, HashFuncer>::CheckCapacity(size_t size)//注意载荷因子a控制在0.7到0.8之间
{
	if (size*10 > _capacity*7)//不用*0.7,由于定义的为size_t类型
	{
		HashTable<K, V> tmp(2 * _capacity);
		for (size_t index = 0; index < _capacity; index++)
		{
			if (_status[index] == EXIST)
			{
				tmp.Insert(_table[index]._key, _table[index]._value);//复用Insert插入
			}
		}
		this->Swap(tmp);//交换this和tmp
	}
}
template<class K, class V, class HashFuncer = DefaultHashFuncer<K>>
void HashTable<K, V, HashFuncer>::Swap(HashTable<K,V,HashFuncer>& ht)//交换this和ht
{
	swap(_table, ht._table);
	swap(_status, ht._status);
	_size = ht._size;
	_capacity = ht._capacity;
}
template<class K, class V, class HashFuncer = DefaultHashFuncer<K>>
void HashTable<K, V, HashFuncer>::PrintTable()
{
	for (size_t i = 0; i < _capacity; i++)
	{
		KeyValue<K, V>* tmp = _table;
		if (_status[i] == EXIST)
		{
			printf("第%d个位置E: %s %s\n", i, _table[i]._key.c_str(), _table[i]._value.c_str());
		}
		if (_status[i] == EMPTY)
		{
			printf("第%d个位置N\n", i);
		}
		if (_status[i] == DELETE)
		{
			printf("第%d个位置E: %s %s\n", i, _table[i]._key.c_str(), _table[i]._value.c_str());
		}
	}
}

template<class K, class V, class HashFuncer = DefaultHashFuncer<K>>
int HashTable<K, V, HashFuncer>::Find(const K& key, const V& value)
{
	for (size_t i = 0; i < _capacity; i++)
	{
		if (_status[i] == EXIST && 
			_table[i]._key == key && _table[i]._value == value)
		{
			return i;
		}
	}
	return -1;
}
template<class K, class V, class HashFuncer = DefaultHashFuncer<K>>
bool HashTable<K, V, HashFuncer>::Remove(const K& key, const V& value)//删除
{
	int pos = Find(key, value);
	if (pos == -1)//返回-1表示查找失败
	{
		return false;
	}
	_status[pos] = DELETE;
	--_size;
	return true;
}
template<class K, class V, class HashFuncer = DefaultHashFuncer<K>>
bool HashTable<K, V, HashFuncer>::Alter(const K& key, const V& value, 
	const K& NewKey, const V& NewValue)//改变
{
	int pos = Find(key, value);
	if (pos == -1)//返回-1表示查找失败
	{
		return false;
	}
	_table[pos]._key = NewKey;
	_table[pos]._value = NewValue;
	return true;
}

测试用例如下:

void TestKV()
{
	HashTable<string, string> ht2(5);
	ht2.Insert("scenluo", "萝瑟");
	ht2.Insert("Peter", "张sir");
	ht2.Insert("jack", "杰克");
	ht2.Insert("manager", "经理");
	ht2.Insert("Lafite", "拉菲");
	ht2.PrintTable();
	cout << "Find: " << ht2.Find("manager", "经理") << endl;
	cout << "Remove: " << ht2.Remove("manager", "经理") << endl;
	cout << "Alter: " << ht2.Alter("Lafite", "拉菲", "assistant", "助手") << endl;
	ht2.PrintTable();
}
时间: 2024-08-09 06:17:37

哈希冲突的处理【闭散列方法-线性探测和二次探测】的相关文章

处理哈希冲突的闭散列方法-线性探测

说到哈希冲突,就必须谈到哈希函数了. 什么时候哈希函数 哈希冲突函数hv(i),用于在元素i发生哈希冲突时,将其映射至另一个内存位置. 什么是哈希冲突 哈希冲突即关键字不同的元素被映射到了同一个内存位置,包括由同义词冲突和非同义词冲突. 处理哈希冲突的方法很多,这里浅谈一下处理哈希冲突的闭散列方法: 线性探测 如下图所示 在上图中,这里key取8. 实现线性探测代码: #pragma once #include<string> enum Status { EXIST, EMPTY, DELET

哈希算法(一次探测,二次探测,哈希桶法)支持字典查询

HashTable-散列表/哈希表,是根据关键字(key)而直接访问在内存存储位置的数据结构.它通过一个关键值的函数将所需的数据映射到表中的位置来访问数据,这个映射函数叫做散列函数,存放记录的数组叫做散列表. 构造哈希表的几种方法 直接定址法--取关键字的某个线性函数为散列地址,Hash(Key)= Key 或 Hash(Key)= A*Key + B,A.B为常数. 除留余数法--取关键值被某个不大于散列表长m的数p除后的所得的余数为散列地址.Hash(Key)= Key % P. 平方取中法

Java学习笔记(2)----散列集/线性表/队列/集合/图(Set,List,Queue,Collection,Map)

1. Java集合框架中的所有实例类都实现了Cloneable和Seriablizable接口.所以,它们的实例都是可复制和可序列化的. 2. 规则集存储的是不重复的元素.若要在集合中存储重复的元素,就需要使用线性表.线性表不仅可以存储重复的元素,而且允许用户指定存储的位置.用户可以通过下标来访问线性表中的元素. 3. Java集合支持三种类型的规则集:散列集HashSet.链式散列集LinkedHashSet和树形集TreeSet.HashSet以一个不可预知的顺序存储元素:LinkedHas

数据结构哈希表的闭散列基本实现

#pragma once #include<string> using namespace std; enum Status//表示当前位置的状态 { EXITS, DELETE, EMPTY, }; template<class K,class V> struct KeyValueNode//KV键值对 { K _key; V _value; KeyValueNode(const K& key=K(), const V& value=V()) :_key(key)

转载:散列冲突的解决策略

冲突解决的策略 尽管散列函数的目标是使得冲突最少,但实际上冲突是无法避免的.因此,我们必须研究冲突解决策略.冲突解决技术可以分为两类:开散列方法( open hashing,也称为拉链法,separate chaining )和闭散列方法( closed hashing,也称为开地址方法,open addressing ).这两种方法的不同之处在于:开散列法把发生冲突的关键码存储在散列表主表之外,而闭散列法把发生冲突的关键码存储在表中另一个槽内. 开散列方法: 1.拉链法 开散列方法的一种简单形

数据结构与算法分析java——散列

1. 散列的概念 散列方法的主要思想是根据结点的关键码值来确定其存储地址:以关键码值K为自变量,通过一定的函数关系h(K)(称为散列函数),计算出对应的函数值来,把这个值解释为结点的存储地址,将结点存入到此存储单元中.检索时,用同样的方法计算地址,然后到相应的单元里去取要找的结点.通过散列方法可以对结点进行快速检索.散列(hash,也称“哈希”)是一种重要的存储方式,也是一种常见的检索方法. 按散列存储方式构造的存储结构称为散列表(hash table).散列表中的一个位置称为槽(slot).散

数据结构-散列(1)

一.散列表(Hash table) 1.散列表用来表示集合和字典,通过散列函数建立从元素关键码集合到散列表地址集合的一个映射,搜索时可以直接到达或者逼近具有对应关键码的元素的实际存放地址: 2.散列函数是压缩映像函数,关键码集合比散列表地址集合大得多,所以经过散列函数的计算会把不同的关键码映射到同一个散列地址上,散列地址相同的不同关键码互为同义词: 3.构造散列函数的要求: (1).定义域必须包括需要存储的全部关键码,若散列表允许有m个地址则其值域必须在0到m-1之间: (2).散列函数计算出来

数据结构--散列(分离链接法解决冲突)

散列方法的主要思想是根据结点的关键码值来确定其存储地址:以关键码值K为自变量,通过一定的函数关系h(K)(称为散列函数),计算出对应的函数值来,把这个值解释为结点的存储地址,将结点存入到此存储单元中.检索时,用同样的方法计算地址,然后到相应的 单元里去取要找的结点.通过散列方法可以对结点进行快速检索.散列(hash,也称"哈希")是一种重要的存储方式,也是一种常见的检索方法. 因此,散列函数更像是一种映射,散列函数的选择有很多种,下面以散列函数为关键值对10取余为例,说明散列的插入关键

查找之散列查找(哈希表)

本学习笔记部分内容来自网易云课堂浙江大学数据结构课程,谢谢! 1.散列表(哈希表) 已知的几种查找方法: 顺序查找  O(N) 二分查找(静态查找)  O(logN) 二叉搜索树      O(h)  h为二叉树高度   (动态查找:有插入有删除有查找) 平衡二叉树      O(logN) 查找的本质:已知对象找位置 1.有序安排对象:全序或半序: 2.直接算出对象位置:散列. 散列查找法的两项基本工作: 1.计算位置:构造散列函数确定关键词存储位置: 2.解决冲突:应用某种策略解决多个关键词