哈希桶处理哈希冲突

哈希桶:哈希桶就是盛放不同key链表的容器(即是哈希表),我们可以把每个key的位置看作是一个指针,该指针所指向的位置里放了一个链表,可以认为是指针数组,故该方法也叫开链式。

相比闭散列,哈希桶提高了空间利用率:在实现哈希表时,常见的方法是线性探测、二次探测,这两个算法的具体实现可以查看我的博客。但是这两个算法有一个共同点就是:空间利用率低。为什么这么说呢?线性探测、二次探测的高效性很大程度上要取决于它的载荷因子,载荷因子即:存放关键字个数 / 空间大小。

通过查阅资料,我发现,使用素数做除数可以减少哈希冲突。见下:

素数表:使用素数做除数可以减少哈希冲突

// 使用素数表对齐做哈希表的容量,降低哈希冲突

const int _PrimeSize = 28;

static const unsigned long _PrimeList [_PrimeSize] =

{

53ul,         97ul,         193ul,       389ul,       769ul,

1543ul,       3079ul,       6151ul,      12289ul,     24593ul,

49157ul,      98317ul,      196613ul,    393241ul,    786433ul,

1572869ul,    3145739ul,    6291469ul,   12582917ul,  25165843ul,

50331653ul,   100663319ul,  201326611ul, 402653189ul, 805306457ul,

1610612741ul, 3221225473ul, 4294967291ul

};

下图进行哈希桶处理哈希冲突的展示

下面通过库中的vactor进行存放指向链表的指针,每个结点里包含_key,_value和_next。

#pragma
template<class K>
struct DefaultHashFunc
{
	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 DefaultHashFunc<string>
{
	size_t operator()(const string& str)
	{
		return BKDRHash(str.c_str());
	}
};
template<class K, class V>
struct HashTableNode//结点
{
	K _key;
	V _value;
	HashTableNode* _next;
	HashTableNode(const K& key, const V& value)
		:_key(key)
		, _value(value)
		, _next(NULL)
	{}
};
template<class K, class V, class HashFunc = DefaultHashFunc<K>>
class HashTableBucket
{
	typedef HashTableNode<K, V> Node;
public:
	HashTableBucket();
	HashTableBucket(const HashTableBucket<K, V, HashFunc>& htb);
	HashTableBucket<K, V, HashFunc>& operator=(HashTableBucket<K, V, HashFunc> htb);
	void PrintTables();
	bool Insert(const K& key,const V& value);//防冗余,在删除和查找时只需要key
	Node* Find(const K& key);
	bool Remove(const K& key);
protected:
	size_t _HashFunc(const K& key);
	size_t _GetNextPrime(size_t size);//获取下一个素数(利用素数表,使用素数做除数可以减少哈希冲突)
	void _CheckExpand();
private:
	vector<Node*> _tables;//开链式为指针数组,指针指向链表
	size_t _size;//有效数据数,vector中的size()为有效空间数
};

实现_HashFunc(const K& key),通过伪函数来判断不同类型的key所在链表的位置。

template<class K, class V, class HashFunc = DefaultHashFunc<K>>
size_t HashTableBucket<K, V, HashFunc>::_HashFunc(const K& key)
{
	HashFunc htb;
	return htb(key) % (_tables.size());//htb(key)伪函数
}

1. 插入函数的实现(Insert)

(1)检查容量。调用_CheckExpand()函数检查负载因子a,考虑是否扩张,当a为1时进行扩容。

(2)检查插入的key是否已经存在,不存在返回false,存在进行(3)操作。

(3)进行头插。

template<class K, class V, class HashFunc = DefaultHashFunc<K>>
bool HashTableBucket<K, V, HashFunc>::Insert(const K& key, const V& value)
{//防冗余,在删除和查找时只需要key
	_CheckExpand();//检查是否扩容
	for (size_t i = 0; i < _tables.size(); ++i)
	{
		Node* cur = _tables[i];
		while (cur)
		{//如果插入的元素存在就返回false
			if (cur->_key == key)
			{
				return false;
			}
			cur = cur->_next;
		}
	}
	//头插
	size_t index = _HashFunc(key);
	Node* tmp = new Node(key, value);
	tmp->_next = _tables[index];
	_tables[index] = tmp;
	++_size;
	return true;

2. 查找函数的实现(Find)

(1)调用_HashFunc()函数找到要寻找的Key所在的链表位置。

(2)通过遍历链表查找key。

template<class K, class V, class HashFunc = DefaultHashFunc<K>>
HashTableNode<K, V>* HashTableBucket<K, V, HashFunc>::Find(const K& key)//查找
{
	size_t index = _HashFunc(key);//链表结点位置
	Node* cur = _tables[index];
	while (cur)
	{
		if (cur->_key == key)
		{
			return cur;
		}
		cur = cur->_next;
	}
	return NULL;
}

3. 删除函数的实现(Remove)

(1)调用Find()函数,判断需要删除的key是否存在,不存在就返回false,存在就进行(2)操作。

(2)调用_HashFunc()函数找到key所在链表的位置,先通过遍历链表找到del结点的上一个结点prev,然后使prev的下一个结点指向del的下一个结点。

template<class K, class V, class HashFunc = DefaultHashFunc<K>>
bool HashTableBucket<K, V, HashFunc>::Remove(const K& key)//删除
{
	if (Find(key) == NULL)
	{
		return false;
	}
	size_t index = _HashFunc(key);
	//需要找到删除结点的前后结点
	Node* del = Find(key);
	Node* next = del->_next;
	Node* prev = _tables[index];
	while (prev)
	{
		if (prev->_next == del)
		{
			break;
		}
		prev = prev->_next;
	}
	if (next)//如果next存在时,进行链接
	{
		prev->_next = next;
	}
	del = NULL;
	return true;
}

检查是否需要扩容_CheckExpand()的实现。

template<class K, class V, class HashFunc = DefaultHashFunc<K>>
void HashTableBucket<K, V, HashFunc>::_CheckExpand()//检查负载因子,考虑是否扩容
{
	if (_size >= _tables.size())//负载因子达到了1,进行扩容
	{
		size_t NewSize = _GetNextPrime(_size);
		//进行结点复制
		vector<Node*> NewTables;
		NewTables.resize(NewSize);
		for (size_t i = 0; i < _tables.size(); ++i)
		{
			Node* cur = _tables[i];
			while (cur)//头插
			{
				Node* tmp = cur;
				cur = cur->_next;
				size_t index = _HashFunc(tmp->_key);//重新确定元素在表中位置
				tmp->_next = NewTables[index];
				NewTables[index] = tmp;
			}
		}
		_tables.swap(NewTables);//调用vector中的swap接口进行交换
	}
}
template<class K, class V, class HashFunc = DefaultHashFunc<K>>
size_t HashTableBucket<K, V, HashFunc>::_GetNextPrime(size_t size)
{//获取下一个素数(利用素数表,使用素数做除数可以减少哈希冲突)
	//使用素数表对齐做哈希表的容量,降低哈希冲突
	const int _PrimeSize = 28;
	static const unsigned long _PrimeList[_PrimeSize] =
	{
		53ul, 97ul, 193ul, 389ul, 769ul,
		1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
		49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
		1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
		50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
		1610612741ul, 3221225473ul, 4294967291ul
	};
	for (size_t i = 0; i < _PrimeSize; ++i)
	{
		if (_PrimeList[i] > size)
		{
			return _PrimeList[i];
		}
		return _PrimeList[i - 1];
	}
	return _PrimeList[_PrimeSize];//如果size大于或等于素数表中数据,就返回表中最大数
}

测试用例如下,实现字典(可以一对多)查询。

HashTableBucket<string, vector<string>> dict;
	vector<string> v;
	v.push_back("manager");
	dict.Insert("经理", v);

	v.clear();
	v.push_back("移动");
	v.push_back("距离");
	dict.Insert("remove",v);
	HashTableNode<string, vector<string>>* ret = dict.Find("remove");
	ret->_value.push_back("搬家");

	vector<string>& words = ret->_value;
	for (size_t i = 0; i < words.size(); ++i)//打印对应的多个字符串
	{
		cout << words[i].c_str() << endl;
	}
时间: 2024-10-12 11:58:29

哈希桶处理哈希冲突的相关文章

哈希桶

哈希桶:哈希桶就是盛放不同key链表的容器(即是哈希表),在这里我们可以把每个        key的位置看作是一个孔,孔里放了一个链表.或是使用一个顺序表来存放具        有相同哈希值的key的链表的头节点,利用这个头节点可以找到其它key值.

【数据结构】处理哈希冲突的开链法(哈希桶)算法实现

实现哈希表时,我们常见的方法是线性探测.二次探测,这两个算法也很简单.若有兴趣,可以查看我的博客.但是,这两个算法有一个共同点就是:空间利用率低.为什么这么说呢?线性探测.二次探测的高效性很大程度上要取决于它的载荷因子,载荷因子即:存放关键字个数/空间大小. 通过查阅资料,我发现,使用素数做除数可以减少哈希冲突(具体原因不详,大师专研的,发现很好用,就在这里分享给大家).见下: ----素数表 // 使用素数表对齐做哈希表的容量,降低哈希冲突 const int _PrimeSize = 28;

【干货】C++哈希桶(开链法解决哈希冲突)类的实现

开链法(哈希桶)是解决哈希冲突的常用手法,结构如下: 数据结构的设计思路是这样的,定义一个K-V的链式节点(Node),以数组方式存储节点指针 实现代码如下: #include<vector> #include"HashTable.h" size_t GetSize() { static size_t index = 0; const int _PrimeSize = 28; static const unsigned long _PrimeList[_PrimeSize]

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

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

openVswitch(OVS)源码分析之工作流程(哈希桶结构体的解释)

这篇blog是专门解决前篇openVswitch(OVS)源码分析之工作流程(哈希桶结构体的疑惑)中提到的哈希桶结构flex_array结构体成员变量含义的问题. 引用下前篇blog中分析讨论得到的flex_array结构体成员变量的含义结论: struct { int element_size; // 这是flex_array_part结构体存放的哈希头指针的大小 int total_nr_elements; // 这是全部flex_array_part结构体中的哈希头指针的总个数 int e

C++ 哈希表 线性探测 二次探测 哈希桶

#pragma once #include<iostream>//含一次探测  二次探测 #include<vector> #include<math.h> using namespace std; enum Status { EXIST, EMPTY, DELET, }; template <class K,class V> //key/value形式结构体 struct KV { K _key; V _value; KV(const K& key

哈希桶的实现

#pragma once#include<vector> template<class K>struct __HashFunc{    size_t operator()(const K&key)    {        return key;    }}; template<>struct __HashFunc<string>   //特化{    static size_t BKDRHash(const char*str)    //BKDRHa

哈希函数和哈希表综述 (转)

哈希表及哈希函数研究综述 摘要 随着信息化水平的不断提高,数据已经取代计算成为了信息计算的中心,对存储的需求不断提高信息量呈现爆炸式增长趋势,存储已经成为急需提高的瓶颈.哈希表作为海量信息存储的有效方式,本文详细介绍了哈希表的设计.冲突解决方案以及动态哈希表.另外针对哈希函数在相似性匹配.图片检索.分布式缓存和密码学等领域的应用做了简短得介绍 哈希经过这么多年的发展,出现了大量高性能的哈希函数和哈希表.本文通过介绍各种不同的哈希函数的设计原理以及不同的哈希表实现,旨在帮助读者在实际应用中,根据问

哈希表和哈希函数

一.哈希表 通过记录的存储位置和它的关键字之间建立一个确定的对应关系 f  以及处理冲突的方法,使得每个关键字和结构中一个唯一的存储位置相对应.这样对于关键字 K  根据对应关系 f  ,就可以找到存储在 f(K) .称这种对应关系f 为 哈希函数,按照这种方法建立的表称为哈希表. 1.1 原理: 通过记录的存储位置和它的关键字之间建立一个确定的对应关系 f ,使得每个关键字和结构中一个唯一的存储位置相对应.这样对于关键字 K  根据对应关系 f  ,就可以找到存储在 f(K) .称这种对应关系