从C++开始的编程生活(23)——哈希表

前言

本系列文章承接C++基础的学习,需要**++有C语言的基础++** 才能学会哦~

第23篇主要讲的是有关于C++的**++哈希表++** 。
C++才起步,都很简单!

目录

前言

哈希的概念

直接定址法

哈希冲突

负载因子

哈希函数

除法散列法/除留余数法

乘法散列法(了解即可)

全域散列法(了解即可)

其他方法

处理哈希冲突

开放定址法

线性探测

二次(方)探测

双重散列(了解)

开放定址法代码实现

链地址法

链地址法代码实现

哈希封装


哈希的概念

又叫散列,是一种组织数据的方式。通过++哈希函数关键字key与存储位置建立一个映射,查找时通过哈希函数计算出Key存储的位置++。

直接定址法

关键字范围集中 时,用关键字直接计算出一个绝对或相对的存储位置

如a-z的26个字母,开26个数的数组,用其ascll码做下标即可。

哈希冲突

关键字范围不集中 时,++直接定址法需要创建大量的无用空间++。

例如只有在[0, 999999]的N个值,要给他映射到大小为M(M> N)的空间中,就要借助哈希函数来映射。

但可能会出现一个问题:**多个Key经过哈希函数计算后,映射到了同样一个位置,这种情况就叫做哈希冲突(哈希碰撞)。**冲突不可避免,但是要尽量减少冲突的次数。

负载因子

负载因子(load factor) = ,N为已存数据个数,M为哈希表大小。

负载因子越小,哈希冲突概率越小,空间利用率低;
负载因子越大,哈希冲突概率越大,空间利用率高。

哈希函数

好的哈希函数要尽可能让N个关键字均匀地分散到M个存储空间里,减少冲突。

除法散列法/除留余数法

即 h(key) = key % M。

要点:

①**尽量避免M为2、10的幂等。**这些与进制相同的数字的幂,区域之后,相当于保留后x位,这样一来,哈希冲突很容易就会发生,不够均摊。

尽量取M为不太接近2的整数次幂的质数。可以尽量让key的所有位都参与运算,更"随机",更均摊。

③使用这种方法,要尽可能让Key更多的位数都参与运算。如在32位下,Key右移16位变成Key',通过Key 异或 Key'的结果作为哈希值。

乘法散列法(了解即可)

让Key乘上常数A(0<A<1),取其小数部分,再用空间大小M*小数部分,向下取整作为哈希值。即h(Key)= M * ( ( A * Key ) % 1.0)

这种方法对M大小没有要求。

全域散列法(了解即可)

若哈希函数是公开且确定的,攻击者就可以刻意制造会发生严重冲突的数据集,例如全映射到同一个位置。解决办法就是用全域散列法,给哈希函数增加随机性

h(Key) = ( (a * Key + b) % P) % M,P为足够大的质数,a为[1,P-1]中的任意随机整数,b为[0,P-1]中任意随机整数。

因为随机性,攻击者无法预测Key会映射到哪一个位置,攻击难度增大。

其他方法

平方折中法、折叠法、随机数法、数学分析法等。

处理哈希冲突

冲突不可避免,遇到冲突需要解决。

开放定址法

当Key用哈希函数计算出的位置冲突了,则按照某种规则++找一个没有存储数据的位置++进行存储

探测无存储数据的位置有三种规则:线性探测二次探测双重探测

线性探测

从冲突位置开始,依次线性向后探测,直到寻找到下一个没有存储数据的位置为止,如果走到哈希表尾,就回到哈希表头。

线性探测公式:hc(key,i) = (h(Key) + i) % M, i = {1,2,3,4,···,M-1},最多探测M-1次就能找到可以存储的位置。

群集/堆积: 例如,哈希值0,1,2(即hash0、hash1、hash2)都存储了数据,那么hash0、hash1、hash2、hash3的Key都会++争夺++ hash3的位置。此时使用二次探测可以一定程度改善这种情况。

二次(方)探测

从冲突位置开始,一次左右按二次方跳跃式探测,直到寻找到下一个没有存储数据的位置为止,如果走到哈希表尾,就回到哈希表头。

二次探测公式:hc(key,i) = (h(Key) ± i * i ) % M, i = {1,2,3,4,···,M/2}。探测顺序就是哈希值+1,然后-1,接着+4,之后-4,随后+9,-9,+16-,16如此类推。

双重散列(了解)

双重散列公式:hc(Key, i) = ( + i * (Key) )% M, i = {1,2,3,···,M}。再使用一个不同的哈希函数,来获得不同的偏移量值,直到寻找到下一个没有存储数据的位置为止。

要求**(Key) < M** ,且两者互为质数。

开放定址法代码实现

cpp 复制代码
#pragma once
#include<vector>
#include<iostream>
#include<map>
#include<unordered_map>
using namespace std;
enum State
{
	Empty,
	Delete,
	Exist
};

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

template<class K>
class HashFunc
{
public:
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};
//特化
//要把string转为整型才可以被哈希函数
template<>
class HashFunc<string>
{
public:
	size_t operator()(const string& key)
	{
		size_t hash = 0;
		for (auto ch : key)
		{
			hash += ch;
			hash *= 131;
			//BKDR哈希、DJB哈希、AP哈希···
		}
		return hash;
	}
};

template<class K,class V,class Hash = HashFunc<K>>
class HashTable
{
public:
	inline unsigned long __stl_next_prime(unsigned long n)
	{
		static const int __stl_num_primes = 28;
		static const unsigned long __stl_prime_list[__stl_num_primes] =
		{
			53,97,193,389,768.1543,3079,6151,12289,24593,49157,98317,196613,393241,786433
			,1572869,3145739,6291469,12582917,25165843,50331653,100663319,201326611,402653189
			,805306457,1610612741,3221225473,4294967291
		};
		const unsigned long* first = __stl_prime_list;
		const unsigned long* last = __stl_prime_list + __stl_num_primes;
		const unsigned long* pos = lower_bound(first, last, n);
		return pos == last ? *(last - 1) : *pos;
	}

	HashTable()
	{
		_tables.resize(__stl_next_prime(1));
	}

	bool Insert(const pair<K, V>& kv)
	{
		//若不允许冗余,则需要查找一下
		if (Find(kv.first))
		{
			return false;
		}
		//扩容
		if ((double)_n / (double)_tables.size() >= 0.7)
		{
			//	//获取素数表内比当前表大的一个素数
			//	size_t newSize = __Stl_next_prime(_tables.size() + 1);
			//	//扩容之后要重新映射
			//	vector<HashData<K, V>> newTables(newSize);
			//	//遍历旧表,将数据重新遍历。
			//	for (size_t i = 0; i < _tables.size(); i++)
			//	{
			//		if (_tables[i]._state == Exist)
			//		{
			//			//...
			//		}
			//	}
			//	_tables.swap(newTables);
			//}

			size_t newSize = __stl_next_prime(_tables.size() + 1);
			HashTable<K, V, Hash> newHT;
			newHT._tables.resize(newSize);
			//遍历旧表,将数据重新遍历。
			for (size_t i = 0; i < _tables.size(); i++)
			{
				if (_tables[i]._state == Exist)
				{
					newHT.Insert(_tables[i]._kv);
				}
			}
			//交换之后,若函数完毕,旧表就刚好被析构销毁
			_tables.swap(newHT._tables);

		}

		Hash hs;
		//hash0为哈希值
		size_t hash0 = hs(kv.first) % _tables.size();
		size_t i = 1;
		//hashi为探测值
		size_t hashi = hash0;
		while (_tables[hashi]._state == Exist)
		{
			//冲突探测
			hashi = (hash0 + i) % _tables.size();
			++i;
		}
		_tables[hashi]._kv = kv;
		_tables[hashi]._state = Exist;


		return true;
	}

	HashData<K, V>* Find(const K& key)
	{
		Hash hs;
		size_t hash0 = hs(key) % _tables.size();
		size_t hashi = hash0;
		size_t i = 1;
		size_t flag = 1;
		while (_tables[hashi]._state != Empty)
		{
			if (_tables[hashi]._state == Exist
				&& _tables[hashi]._kv.first == key)
			{
				return &_tables[hashi];
			}
			//线性检测
			hashi = (hash0 + i * flag) % _tables.size();
			flag *= -1;
			++i;
		}
		return nullptr;
	}

	bool Erase(const K& key)
	{
		HashData<K, V>* ret = Find(key);
		if (ret == nullptr)
		{
			return false;
		}
		else
		{
			ret->_state = Delete;
            --_n;
			return true;
		}
	}
private:
	vector<HashData<K, V>> _tables;
	size_t _n = 0;
};


//void TestHT1()
//{
//	HashTable<int, int> ht1;
//	ht1.Insert({ 54,1 });
//	ht1.Insert({ 1,1 });
//
//	cout << ht1.Find(1) << endl;
//	cout << ht1.Erase(54) << endl;
//	cout << ht1.Find(1) << endl;
//	cout << ht1.Find(54) << endl;
//	//for (size_t i = 0; i < 53; i++)
//	//{
//	//	ht1.Insert({ rand(),i });
//	//}
//}
void TestHT2()
{
	HashTable<string, string,stringHashFunc> ht2;
	ht2.Insert({ "sort","排序" });
	ht2.Insert({ "string","字符串" });

	cout << stringHashFunc()("abcd") << endl;
	cout << stringHashFunc()("bcad") << endl;
	cout << stringHashFunc()("abbe") << endl;
	
	unordered_map<string, string> dictMap;
	dictMap.insert({ "sort","排序" });
	dictMap.insert({ "string","字符串" });
} 

链地址法

也就是哈希桶,我们把哈希冲突的数据以链表的方式链接在一起。

如图,这样一来,负载因子就可以大于1了。

链地址法代码实现

cpp 复制代码
template<class, 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 HashTable
{
	typedef HashNode<K, V> Node;
public:
	inline unsigned long __stl_next_prime(unsigned long n)
	{
		static const int __stl_num_primes = 28;
		static const unsigned long __stl_prime_list[__stl_num_primes] =
		{
			53,97,193,389,768.1543,3079,6151,12289,24593,49157,98317,196613,393241,786433
			,1572869,3145739,6291469,12582917,25165843,50331653,100663319,201326611,402653189
			,805306457,1610612741,3221225473,4294967291
		};
		const unsigned long* first = __stl_prime_list;
		const unsigned long* last = __stl_prime_list + __stl_num_primes;
		const unsigned long* pos = lower_bound(first, last, n);
		return pos == last ? *(last - 1) : *pos;
	}

	HashTable()
	{
		_tables.resize(__stl_next_prime(1), nullptr);
	}

	bool Insert(const pair<K, V>& kv)
	{
		if (Find(kv.first))
		{
			return false;
		}
		//负载因子到1,就扩容
		if (_n == _tables.size())
		{
			size_t newSize = __stl_next_prime(_tables.size() + 1);
			vector<Node*> newtables(newSize, nullptr);
			//遍历旧表,把旧表的结点挪动到新表
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				while (cur)
				{
					Node* next = cur->_next;
					//cur头插到新表
					size_t hashi = cur->_kv.first % newSize;
					//让下一个结点为对应哈希值的第一个结点
					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 = _table[hashi];
		_tables[hashi] = newnode;
		++_n;

		return true;
	}

	Node* Find(const K& key)
	{
		size_t hashi = key % _tables.size();
		Node* cur = _tables[hashi];
		while (cur)
		{
			if (cur->_kv.first == key)
			{
				return cur;
			}

			cur = cur->_next;
		}

		return nullptr;
	}

	bool Erase(const K& key)
	{
		size_t hashi = key % _tables.size();
		Node* prev = nullptr;
		Node* cur = _tables[hashi];
		while (cur)
		{
			if (cur->_kv.first == key)
			{
				// 删除
				if (prev == nullptr)
				{
					// 头删
					_tables[hashi] = cur->_next;
				}
				else
				{
					prev->_next = cur->_next;
				}

				delete cur;
				return true;
			}

			prev = cur;
			cur = cur->_next;
		}

		return false;
	}
private:
	vector<Node*>_tables;
	size_t _n = 0;
};

哈希封装

只展示代码示例,内含解析~

HashTable.h

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1  // 禁用VS的C运行时安全警告

#include<vector>  // 引入vector容器,用于存储哈希表数据
#include<iostream>
#include<unordered_map>
#include<unordered_set>
using namespace std;
// 通用哈希函数模板(默认适配int等数值类型)
template<class K>
class HashFunc
{
public:
    // 重载()运算符,数值类型直接强转为size_t作为哈希值
    size_t operator()(const K& key)
    {
        return (size_t)key;
    }
};

// 哈希函数特化:针对string类型(解决字符串无法直接强转的问题)
template<>
class HashFunc<string>
{
public:
    size_t operator()(const string& key)
    {
        size_t hash = 0;          // 初始化哈希值
        for (auto ch : key)       // 遍历字符串每个字符
        {
            hash += ch;           // 累加字符ASCII值
            hash *= 131;          // 乘以131(经典哈希系数,减少冲突)
        }

        return hash;              // 返回最终哈希值
    }
};

// 开放定址法哈希表命名空间
namespace open_address
{
    // 哈希表元素状态枚举(解决删除后查找链断裂问题)
    enum State
    {
        EXIST,   // 元素存在
        EMPTY,   // 位置为空
        DELETE   // 元素已删除(伪删除)
    };

    // 哈希表数据节点:存储键值对 + 状态
    template<class K, class V>
    struct HashData
    {
        pair<K, V> _kv;            // 键值对
        State _state = EMPTY;      // 初始状态为空
    };

    // 开放定址法哈希表类模板:K-键,V-值,Hash-哈希函数(默认HashFunc<K>)
    template<class K, class V, class Hash = HashFunc<K>>
    class HashTable
    {
    public:
        // 私有工具函数:获取大于等于n的下一个素数(STL经典素数表,减少哈希冲突)
        inline unsigned long __stl_next_prime(unsigned long n)
        {
            // Note: assumes long is at least 32 bits.
            static const int __stl_num_primes = 28;  // 素数表长度
            static const unsigned long __stl_prime_list[__stl_num_primes] =
            {
                53, 97, 193, 389, 769,
                1543, 3079, 6151, 12289, 24593,
                49157, 98317, 196613, 393241, 786433,
                1572869, 3145739, 6291469, 12582917, 25165843,
                50331653, 100663319, 201326611, 402653189, 805306457,
                1610612741, 3221225473, 4294967291
            };
            const unsigned long* first = __stl_prime_list;    // 素数表起始
            const unsigned long* last = __stl_prime_list + __stl_num_primes;  // 素数表结束
            const unsigned long* pos = lower_bound(first, last, n);  // 二分找第一个>=n的素数
            return pos == last ? *(last - 1) : *pos;  // 超出表则返回最后一个素数
        }

        // 构造函数:初始化哈希表容量为大于1的第一个素数(53)
        HashTable()
        {
            _tables.resize(__stl_next_prime(1));
        }

        // 插入键值对:返回是否插入成功(重复键返回false)
        bool Insert(const pair<K, V>& kv)
        {
            if (Find(kv.first))  // 先查找:键已存在则返回false
                return false;

            // 扩容条件:负载因子(已存元素/表容量)>=0.7(开放定址法负载因子不宜过高)
            // if (_n*10 / _tables.size >= 7)  // 注释的扩容写法(等价)
            if ((double)_n / (double)_tables.size() >= 0.7)
            {
                // 注释的扩容逻辑(替换为新建哈希表的方式)
                //size_t newSize = __stl_next_prime(_tables.size() + 1);
                //vetcor<HashData<K, V>> newTables(newSize); // 笔误:vector拼错
                //// 遍历旧表,将数据都映射到新表
                //for (size_t i = 0; i < _tables.size(); i++)
                //{
                //	if (_tables[i]._state == EXIST)
                //	{
                //		// ...
                //	}
                //}
                //_tables.swap(newTables);

                size_t newSize = __stl_next_prime(_tables.size() + 1);  // 新容量为下一个素数
                HashTable<K, V, Hash> newHT;                            // 新建空哈希表
                newHT._tables.resize(newSize);                          // 调整新表容量

                // 遍历旧表:将有效数据重新插入新表(重新计算哈希位置)
                for (size_t i = 0; i < _tables.size(); i++)
                {
                    if (_tables[i]._state == EXIST)
                    {
                        newHT.Insert(_tables[i]._kv);
                    }
                }

                _tables.swap(newHT._tables);  // 交换新旧表(完成扩容)
            }

            Hash hs;                              // 创建哈希函数对象
            size_t hash0 = hs(kv.first) % _tables.size();  // 计算初始哈希位置
            size_t i = 1;                         // 线性探测步长
            size_t hashi = hash0;                 // 当前探测位置
            while (_tables[hashi]._state == EXIST)
            {
                // 线性探测:往后找空位置
                hashi = (hash0 + i) % _tables.size();
                ++i;
            }

            // 插入数据并更新状态
            _tables[hashi]._kv = kv;
            _tables[hashi]._state = EXIST;
            _n++;  // 有效元素数+1

            return true;
        }

        // 查找键:返回数据节点指针(找不到返回nullptr)
        HashData<K, V>* Find(const K& key)
        {
            Hash hs;
            size_t hash0 = hs(key) % _tables.size();  // 初始哈希位置
            size_t hashi = hash0;
            size_t i = 1;
            while (_tables[hashi]._state != EMPTY)  // 非空位置都要探测(包括DELETE)
            {
                // 找到存在且键匹配的元素
                if (_tables[hashi]._state == EXIST
                    && _tables[hashi]._kv.first == key)
                {
                    return &_tables[hashi];
                }
                // 继续线性探测
                hashi = (hash0 + i) % _tables.size();
                ++i;
            }
            return nullptr;  // 未找到
        }

        // 删除键:返回是否删除成功(伪删除,仅改状态)
        bool Erase(const K& key)
        {
            HashData<K, V>* ret = Find(key);
            if (ret == nullptr)  // 未找到
            {
                return false;
            }
            else  // 伪删除:改状态为DELETE,不直接清空(避免查找链断裂)
            {
                --_n;
                ret->_state = DELETE;
                return true;
            }
        }

    private:
        vector<HashData<K, V>> _tables;  // 哈希表本体(数组)
        size_t _n = 0;                   // 有效元素个数
    };

    // 字符串哈希函数(冗余,仅测试用,实际用上面的特化版本)
    class stringHashFunc
    {
    public:
        size_t operator()(const string& s)
        {
            size_t hash = 0;
            for (auto ch : s)
            {
                hash += ch;
                hash *= 131;
            }

            return hash;
        }
    };

    // 开放定址法测试函数
    void TestHT1()
    {
        HashTable<int, int> ht1;
        ht1.Insert({ 54, 1 });
        ht1.Insert({ 1, 1 });

        cout << ht1.Find(1) << endl;    // 输出地址(非空)
        cout << ht1.Erase(54) << endl;  // 输出1(删除成功)
        cout << ht1.Find(1) << endl;    // 输出地址(仍存在)
        cout << ht1.Find(54) << endl;   // 输出0(已删除)

        /*for (int i = 0; i < 53; i++)
        {
            ht1.Insert({rand(), i});
        }*/
    }

    // 日期结构体(仅占位,未实际使用)
    struct Date
    {
        int _year;
        int _month;
        int _day;
    };

    // 字符串哈希测试函数
    void TestHT2()
    {
        //HashTable<string, string, stringHashFunc> ht2;  // 注释的写法(等价)
        HashTable<string, string> ht2;
        ht2.Insert({ "sort", "排序" });
        ht2.Insert({ "string", "字符串" });

        // 测试字符串哈希值(验证不同字符串哈希值不同)
        cout << stringHashFunc()("abcd") << endl;
        cout << stringHashFunc()("bcad") << endl;
        cout << stringHashFunc()("abbe") << endl;

        // 对比标准库unordered_map
        unordered_map<string, string> dictMap;
        dictMap.insert({ "sort", "排序" });
        dictMap.insert({ "string", "字符串" });
        cout << dictMap.load_factor() << endl;    // 输出负载因子
        cout << dictMap.max_load_factor() << endl;// 输出最大负载因子
    }
}

// 链式哈希表命名空间
namespace hash_bucket
{
    // 链表节点模板(存储任意类型数据)
    template<class T>
    struct HashNode
    {
        T _data;               // 存储数据(map存pair,set存K)
        HashNode<T>* _next;    // 指向下一个节点
        HashNode(const T& data)  // 构造函数:初始化数据,next置空
            :_data(data)
            , _next(nullptr)
        {
        }
    };

    // 前置声明:哈希表类(供迭代器使用)
    template<class K, class T, class KeyOfT, class Hash>
    class HashTable;

    // 链式哈希表迭代器模板
    // K-键类型,T-存储类型,Ref-引用类型,Ptr-指针类型,KeyOfT-提取键的仿函数,Hash-哈希函数
    template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
    struct __HTIterator
    {
        typedef HashNode<T> Node;                                  // 节点类型
        typedef HashTable<K, T, KeyOfT, Hash> HT;                  // 哈希表类型
        typedef __HTIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;   // 迭代器自身类型

        Node* _node;    // 当前指向的节点
        const HT* _ht;  // 指向哈希表(用于++时找下一个桶)

        // 构造函数:初始化节点和哈希表指针
        __HTIterator(Node* node, const HT* ht)
            :_node(node)
            , _ht(ht)
        {
        }

        // 解引用运算符:返回数据的引用
        Ref operator*()
        {
            return _node->_data;
        }

        // 箭头运算符:返回数据的指针(支持it->first/second)
        Ptr operator->()
        {
            return &_node->_data;
        }

        // 前置++:迭代器后移(核心逻辑)
        Self operator++()
        {
            if (_node->_next)  // 1. 当前桶内有下一个节点:直接走链表
            {
                _node = _node->_next;
            }
            else  // 2. 当前桶走完:找下一个非空桶
            {
                KeyOfT kot;    // 提取键的仿函数
                Hash hs;       // 哈希函数
                // 计算当前节点所在桶的下标
                size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();
                ++hashi;       // 从下一个桶开始找
                while (hashi < _ht->_tables.size())
                {
                    if (_ht->_tables[hashi])  // 找到非空桶
                    {
                        _node = _ht->_tables[hashi];
                        break;
                    }
                    else  // 桶为空,继续找下一个
                    {
                        ++hashi;
                    }
                }

                // 所有桶遍历完:置空(迭代器结束)
                if (hashi == _ht->_tables.size())
                {
                    _node = nullptr;
                }
            }

            return *this;
        }

        // 不等于运算符:比较节点指针
        bool operator!=(const Self& s)
        {
            return _node != s._node;
        }

        // 等于运算符:比较节点指针
        bool operator==(const Self& s)
        {
            return _node == s._node;
        }
    };

    // 链式哈希表类模板(核心)
    // K-键类型,T-存储类型(map=pair<const K,V>,set=const K),KeyOfT-提取键的仿函数,Hash-哈希函数
    template<class K, class T, class KeyOfT, class Hash>
    class HashTable
    {
        // 友元:迭代器需要访问哈希表私有成员
        template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
        friend struct __HTIterator;

        typedef HashNode<T> Node;  // 节点类型
    public:
        // 迭代器类型定义(普通/const)
        typedef __HTIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;
        typedef __HTIterator<K, T, const T&, const T*, KeyOfT, Hash> ConstIterator;

        // 普通begin:返回第一个有效元素的迭代器
        Iterator Begin()
        {
            for (size_t i = 0; i < _tables.size(); i++)
            {
                Node* cur = _tables[i];
                if (cur)  // 找到第一个非空桶
                {
                    return Iterator(cur, this);
                }
            }

            return End();  // 空表返回end
        }

        // 普通end:返回尾后迭代器(nullptr)
        Iterator End()
        {
            return Iterator(nullptr, this);
        }

        // const版本begin:只读遍历
        ConstIterator Begin() const
        {
            for (size_t i = 0; i < _tables.size(); i++)
            {
                Node* cur = _tables[i];
                if (cur)
                {
                    return ConstIterator(cur, this);
                }
            }

            return End();
        }

        // const版本end:只读遍历的尾后
        ConstIterator End() const
        {
            return ConstIterator(nullptr, this);
        }

        // 构造函数:初始化哈希表容量为53,桶指针置空
        HashTable()
        {
            _tables.resize(__stl_next_prime(1), nullptr);
        }

        // 析构函数:释放所有节点
        ~HashTable()
        {
            // 依次把每个桶释放
            for (size_t i = 0; i < _tables.size(); i++)
            {
                Node* cur = _tables[i];
                while (cur)  // 释放当前桶的所有节点
                {
                    Node* next = cur->_next;
                    delete cur;
                    cur = next;
                }
                _tables[i] = nullptr;  // 桶指针置空
            }
        }

        // 私有工具函数:获取下一个素数(同开放定址法)
        inline unsigned long __stl_next_prime(unsigned long n)
        {
            // Note: assumes long is at least 32 bits.
            static const int __stl_num_primes = 28;
            static const unsigned long __stl_prime_list[__stl_num_primes] =
            {
                53, 97, 193, 389, 769,
                1543, 3079, 6151, 12289, 24593,
                49157, 98317, 196613, 393241, 786433,
                1572869, 3145739, 6291469, 12582917, 25165843,
                50331653, 100663319, 201326611, 402653189, 805306457,
                1610612741, 3221225473, 4294967291
            };
            const unsigned long* first = __stl_prime_list;
            const unsigned long* last = __stl_prime_list + __stl_num_primes;
            const unsigned long* pos = lower_bound(first, last, n);
            return pos == last ? *(last - 1) : *pos;
        }

        // 插入数据:返回pair<迭代器, bool>(迭代器指向元素,bool表示是否插入成功)
        pair<Iterator, bool> Insert(const T& data)
        {
            KeyOfT kot;
            // 先查找:键已存在则返回{迭代器, false}
            Iterator it = Find(kot(data));
            if (it != End())
                return { it, false };

            Hash hs;
            // 负载因子到1就扩容(链式哈希表负载因子可更高)
            if (_n == _tables.size())
            {
                size_t newSize = __stl_next_prime(_tables.size() + 1);
                vector<Node*> newtables(newSize, nullptr);  // 新桶数组
                // 遍历旧表,把旧表的节点挪动到新表
                for (size_t i = 0; i < _tables.size(); i++)
                {
                    Node* cur = _tables[i];
                    while (cur)
                    {
                        Node* next = cur->_next;  // 保存下一个节点

                        // cur头插到新表
                        size_t hashi = hs(kot(cur->_data)) % newSize;
                        cur->_next = newtables[hashi];
                        newtables[hashi] = cur;

                        cur = next;
                    }

                    _tables[i] = nullptr;  // 旧桶置空
                }

                _tables.swap(newtables);  // 交换新旧桶数组
            }

            // 插入新节点(头插法)
            size_t hashi = hs(kot(data)) % _tables.size();  // 计算哈希位置
            Node* newnode = new Node(data);                 // 新建节点

            // 头插到桶里面
            newnode->_next = _tables[hashi];
            _tables[hashi] = newnode;
            ++_n;  // 有效元素数+1
            return { Iterator(newnode, this), true };  // 返回迭代器和成功标志
        }

        // 查找键:返回迭代器(找不到返回end)
        Iterator Find(const K& key)
        {
            Hash hs;
            size_t hashi = hs(key) % _tables.size();  // 计算哈希位置
            Node* cur = _tables[hashi];               // 指向桶的第一个节点
            KeyOfT kot;
            while (cur)  // 遍历桶内链表
            {
                if (kot(cur->_data) == key)  // 找到匹配键
                {
                    return Iterator(cur, this);
                }

                cur = cur->_next;
            }

            return End();  // 未找到
        }

        // 删除键:返回是否删除成功
        bool Erase(const K& key)
        {
            Hash hs;
            KeyOfT kot;

            size_t hashi = hs(key) % _tables.size();  // 哈希位置
            Node* prev = nullptr;
            Node* cur = _tables[hashi];
            while (cur)  // 遍历桶内链表找目标节点
            {
                if (kot(cur->_data) == key)
                {
                    // 删除
                    if (prev == nullptr)  // 头节点删除
                    {
                        _tables[hashi] = cur->_next;
                    }
                    else  // 中间/尾节点删除
                    {
                        prev->_next = cur->_next;
                    }

                    delete cur;  // 释放节点
                    _n--;        // 有效元素数-1
                    return true;
                }

                prev = cur;
                cur = cur->_next;
            }

            return false;  // 未找到
        }
    private:
        //vector<list<pair<K, V>>> _tables;  // 注释的写法(用list替代指针数组)
        vector<Node*> _tables;  // 桶数组(存储节点指针)
        size_t _n = 0;         // 表中存储数据个数
    };
}

unorderedset.h

cpp 复制代码
#pragma once  // 防止头文件重复包含

// 依赖HashTable.h(链式哈希表的核心实现)
#include "HashTable.h"

namespace bit
{
    // unordered_set类模板(单值存储,元素唯一)
    template<class K, class Hash = HashFunc<K>>
    class unordered_set
    {
        // 仿函数:从K中提取键(set的元素就是键)
        struct SetKeyOfT
        {
            const K& operator()(const K& key)
            {
                return key;  // 直接返回自身
            }
        };
    public:
        // 迭代器类型定义(存储const K,元素不可修改)
        typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::Iterator iterator;
        typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::ConstIterator const_iterator;

        // 普通begin:返回第一个有效元素的迭代器
        iterator begin()
        {
            return _ht.Begin();
        }

        // 普通end:返回尾后迭代器
        iterator end()
        {
            return _ht.End();
        }

        // const版本begin:只读遍历
        const_iterator begin() const
        {
            return _ht.Begin();
        }

        // const版本end:只读遍历的尾后
        const_iterator end() const
        {
            return _ht.End();
        }

        // 插入元素(单值):返回pair<迭代器, bool>(区分插入成功/元素已存在)
        pair<iterator, bool> insert(const K& key)
        {
            return _ht.Insert(key);
        }
    private:
        // 底层存储:链式哈希表(存储const K,元素不可修改)
        hash_bucket::HashTable<K, const K, SetKeyOfT, Hash> _ht;
    };

    // 打印const的unordered_set(只能用const迭代器)
    void Print(const unordered_set<int>& s)
    {
        unordered_set<int>::const_iterator it = s.begin();
        while (it != s.end())
        {
            // *it += 1;  // 错误:set元素不可修改
            cout << *it << " ";
            ++it;
        }
        cout << endl;
    }

    // unordered_set测试函数
    void test_set1()
    {
        unordered_set<int> s;
        s.insert(1);
        s.insert(12);
        s.insert(54);
        s.insert(107);

        // 普通迭代器遍历
        unordered_set<int>::iterator it = s.begin();
        while (it != s.end())
        {
            // *it += 1;  // 错误:set元素不可修改
            cout << *it << " ";
            ++it;
        }
        cout << endl;

        // 范围for遍历(底层调用begin/end)
        for (auto e : s)
        {
            cout << e << " ";
        }
        cout << endl;
    }
}

unorderedmap.h:

cpp 复制代码
#pragma once  // 防止头文件重复包含

// 依赖HashTable.h(链式哈希表的核心实现)
#include "HashTable.h"

namespace bit
{
    // unordered_map类模板(键值对存储,键唯一)
    template<class K, class V, class Hash = HashFunc<K>>
    class unordered_map
    {
        // 仿函数:从pair<K,V>中提取键(给底层哈希表用)
        struct MapKeyOfT
        {
            const K& operator()(const pair<K, V>& kv)
            {
                return kv.first;  // 返回pair的第一个元素(键)
            }
        };
    public:
        // 迭代器类型定义(重命名底层哈希表的迭代器)
        // 存储类型为pair<const K,V>:键不可修改,符合map特性
        typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::Iterator iterator;
        typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::ConstIterator const_iterator;

        // 普通begin:返回第一个有效元素的迭代器
        iterator begin()
        {
            return _ht.Begin();
        }

        // 普通end:返回尾后迭代器
        iterator end()
        {
            return _ht.End();
        }

        // const版本begin:只读遍历
        const_iterator begin() const
        {
            return _ht.Begin();
        }

        // const版本end:只读遍历的尾后
        const_iterator end() const
        {
            return _ht.End();
        }

        // 插入键值对:返回pair<迭代器, bool>(区分插入成功/键已存在)
        pair<iterator, bool> insert(const pair<K, V>& kv)
        {
            return _ht.Insert(kv);
        }

        // []运算符重载(核心接口):支持map[key] = value 或 map[key]取值
        V& operator[](const K& key)
        {
            // 插入{key, 默认值}:不存在则插入,存在则返回已存在元素
            pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
            // 返回值的引用:可修改(如map["a"] = "b")
            return ret.first->second;
        }

    private:
        // 底层存储:链式哈希表(存储pair<const K,V>,键不可修改)
        hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
    };

    // 打印const的unordered_map(只能用const迭代器)
    void Print(const unordered_map<string, string>& dict)
    {
        unordered_map<string, string>::const_iterator it = dict.begin();
        while (it != dict.end())
        {
            //it->first += 'x';  // 错误:const迭代器不能修改键
            //it->second += 'x'; // 错误:const迭代器不能修改值

            cout << it->first << " " << it->second;
            ++it;
        }
        cout << endl;
    }

    // unordered_map测试函数
    void test_map1()
    {
        unordered_map<string, string> dict;
        unordered_map<string, string>::iterator it = dict.begin();
        while (it != dict.end())
        {
            //it->first += 'x';  // 错误:map的键不可修改
            it->second += 'x';    // 正确:值可以修改

            cout << it->first << " " << it->second;
            ++it;
        }
        cout << endl;

        // 统计字符串出现次数(map的核心场景)
        string arr[] = { "ƻ", "", "ƻ", "", "ƻ", "ƻ", "",
        "ƻ", "㽶", "ƻ", "㽶" };

        unordered_map<string, int> countMap;
        for (const auto& str : arr)
        {
            countMap[str]++;  // 核心用法:不存在则插入{str,0},再++;存在则直接++
        }

        // 遍历输出统计结果
        for (const auto& e : countMap)
        {
            cout << e.first << ":" << e.second << endl;
        }
        cout << endl;
    }
}

❤~~本文完结!!感谢观看!!接下来更精彩!!欢迎来我博客做客~~❤

相关推荐
英英_1 小时前
优化 MATLAB MapReduce 程序性能:从基础调优到进阶提速
开发语言·matlab·mapreduce
nainaire2 小时前
仿muduo库的Tcp服务器以及其应用层Http协议支持
服务器·网络·c++·tcp/ip·http
LSL666_2 小时前
BaseMapper——新增和删除
java·开发语言·mybatis·mybatisplus
不想写代码的星星2 小时前
C++ RAII:从“人肉记账”到“自动保姆”的资源管理革命
c++
xiangpanf2 小时前
PHP vs C语言:30字解析两大编程语言差异
c语言·开发语言·php
wdfk_prog2 小时前
MAX14830 可移植 C 驱动实现分析:一个适合多串口扩展场景的开源基础版本
c语言·开发语言·开源
Elnaij2 小时前
从C++开始的编程生活(22)——红黑树
开发语言·c++
Trouvaille ~2 小时前
【项目篇】从零手写高并发服务器(六):EventLoop事件循环——Reactor的心脏
linux·运维·服务器·c++·高并发·epoll·reactor模式
学嵌入式的小杨同学2 小时前
STM32 进阶封神之路(十八):RTC 实战全攻略 —— 时间设置 + 秒中断 + 串口更新 + 闹钟功能(库函数 + 代码落地)
c++·stm32·单片机·嵌入式硬件·mcu·架构·硬件架构