C++中哈希的开放定址法和哈希桶

目录

一、哈希表的简介

二、链地址法

开放定址法代码实现

链地址法代码实现


一、哈希表的简介


    HashMap    unordered_map      哈希表——遍历无序
    HashSet    unordered_set        单向迭代器
     增删查效率O(1)
    
     Map         红黑树——中序遍历有序
     Set        双向迭代器
    增删查效率O(logN)

 哈希又称散列(hash),是一种组织数据的方式,
 本质是通过哈希函数把关键字Key根存储位置建立一个映射关系
 查找时通过这个哈希函数计算出Key存储的位置,进行快速查找
 
 一、开放定址法
 字符串中的第一个唯一字符中使用的计数排序就是直接定值法
 本质就是用关键字计算出一个绝对或者相对位置
 局限:只适用于整形,数据范围还需要比较集中

二、链地址法

哈希桶——链地址法
冲突的时候不去占其他的位置,而是用对应的链表
 
 三、哈希冲突/哈希碰撞
 两个不同的key可能会映射到同一个位置里去
 解决方法:哈希函数
    1、除留余数法/除法散列法
 (1)取模h(key) = key % M;(M个空间)
 (2)尽量避免M为某些值,比如2的幂,10的幂
        ——因为可以将这一过程理解为保留相应进制的次数位,
        比如key%2^2就是保留0x 00 00 00 00的后两位
 (3)建议M取不太接近2的整数次幂的一个质数(素数)
 (4)Java采取的做法是专用2的幂,因为既然是等价于取指数个位,
        那就等价于直接进行位运算,在与原来的key与key'异或
        (位运算相比取模运算 效率要高很多)
  2、乘法散列法:
 (1)对哈希表大小M没有要求,用key乘上常数A,0 < A < 1,取小数部分
 (2)再用M乘k*A的小数部分,再向下取整(1.1 -> 1)
 (3)有人认为A取黄金分割点比较好
    —— h(key) = floor{M * [(key * A) % 1.0]}
  3、全域散列法
    防止敌意对手恶意构建数据库;常用于分布式集群等。
    构造出严重冲突的数据集让所有关键字全部落入同一个位置中。
  解决方法是给散列函数增加随机性,提供一个全域散列函数组
    ——h(key) = ((a * key + b) % P) % M
    这里P为一个足够大的质数,a取[1, P - 1],b取[0, P - 1],
    这样就形成了一个全域散列函数组,
    这里只要程序不启动,就没有办法确定使用哪个哈希函数,
    自然就难以构建恶意数据集
 
 四、负载因子
 假设哈希表中已经映射了N个值,哈希表的大小为M,
 那么负载因子 = N / M,负载因子越大,越容易有空间冲突,空间利用率越高
 对于开放定址法,一般让负载因子小于等于0.7,否则扩容;

而对于链地址法,因为每个节点往下都是一个链表,所以对负载因子没有限制,但是一般在他接近1的时候扩容

开放定址法代码实现

enum State
{
	EXIST,
	EMPTY,
	DELETE
};

template<class K, class V>
struct HashData
{
	pair<K, V> _kv;
	State _state = EMPTY;
};

template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

//特化——传string的时候,会优先走提供的特化的版本
template <>
struct HashFunc<string>
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		//尽可能减少冲突,这里通过让字符串的所有字符的ASCII码值相加
		for (auto ch : s)
		{
			hash += ch;
		}
		return hash;
	}
};

template <class K, class V, class Hash = HashFunc<K>>
class HashTable
{
public:
	HashTable()
		: _tables(11)
		, _n(0)
	{}

	size_t HashFunc(const K& key)
	{
		//只保留了后多少位
		//size_t hash = key % (_tables.size());
		size_t hash = key & (_tables.size() - 1);
		hash ^= (key >> (32 - _m));
		return hash;
		//更分散,而且采用位运算效率更高
	}

	bool Insert(const pair<K, V>& kv)
	{
		if (Find(kv.first))
		{
			return false;
		}
		//负载因子>=0.7扩容
		//通过这种方式处理就不用走类型转换了,精度要求不大的话
		if (_n*10 / _tables.size() > 7)
		{
			//直接建立一个新的哈希表
			HashTable<K, V, Hash> newht;
			//C++采取的是提供一个不太接近2的倍数的素数表
			newht._tables.resize(_tables.size() * 2);
			for (auto data : _tables)
			{
				//要保留位置与数据的映射关系
				if (data._state == EXIST)
					newht.Insert(data._kv);
			}
			//直接用新的具有映射关系的哈希表代替原来的哈希表
			_tables.swap(newht._tables);
		}

		Hash hash;
		size_t hash0 = hash(kv.first) % (_tables.size());
		size_t hashi = hash0;
		size_t i = 1;
		int flag = 1;
		//EMPTY和DELETE都可以作为插入的位置
		while (_tables[hashi]._state == EXIST)
		{
			//线性探测(类似于循环队列的由队尾走向队头)、二次探测
			hashi = (hash0 + (i * i * flag))%_tables.size();//不可以用++,因为常量不能被修改
			if (flag == 1)
			{
				flag = -1;//向后探测
			}
			else
			{
				flag = 1;
				++i;
			}
		}

		_tables[hashi]._kv = kv;
		_tables[hashi]._state = EXIST;
		_n++;

		return true;
	}

	HashData<K, V>* Find(const K& key)
	{
		Hash hash;
		size_t hash0 = hash(key) % (_tables.size());
		size_t hashi = hash0;
		size_t i = 1;
		int flag = 1;
		while (_tables[hashi]._state != EMPTY)
		{
			if (_tables[hashi]._kv.first == key && _tables[hashi]._state == EXIST)
			{
				return &_tables[hashi];
			}

			//线性探测
			hashi = (hash0 + (i * i * flag)) % _tables.size();
			if (flag == 1)
			{
				flag = -1;
			}
			else
			{
				i++;
				flag = 1;
			}
		}

		return nullptr;
	}

	bool Erase(const K& key)
	{
		HashData<K, V>* ret = Find(key);
		if (ret)
		{
			ret->_state = DELETE;
			_n--;
			return true;
		}
		else
		{
			return false;
		}
	}
private:
	//还需要标识一个位置是空
	vector<HashData<K, V>> _tables;
	size_t _n;//存储数据个数
	size_t _m = 16;
};

链地址法代码实现

namespace hash_bucket
{
	template <class K, class V>
	struct HashNode
	{
		pair<K, V> _kv;
		HashNode<K, V>* _next;
		HashNode(const pair<K, V>& kv)
			: _kv(kv)
			, _next(nullptr)
		{}
	};

	template <class K, class V, class Hash = HashFunc<K>>
	class HashTables
	{
		typedef HashNode<K, V> Node;
	public:
		HashTables()
			: _tables(53)
			, _n(0)
		{ }

		bool Insert(const pair<K, V>& kv)
		{
			if (_n == _tables.size())
			{
				//不能采用开放定址法的方式,因为会产生大量不必要的拷贝
				vector<Node*> newTables(_tables.size() * 2);
				for (int i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					Node* next = cur->_next;
					while (cur)
					{
						size_t hashi = cur->_kv.first % newTables.size();
						cur->_next = newTables[hashi];
						newTables[hashi] = cur;

						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newTables);
			}

			size_t hashi = kv.first % _tables.size();
			Node* newnode = new Node(kv);

			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			_n++;

			return true;
		}
	private:
		vector<Node*> _tables;//指针数组
		size_t _n = 0;
	};
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值