【C++】哈希表

哈希(散列)方法:通过某种函数hashFunc使元素的储存位置与它的关键码之间能够建立一一映射的关系,从而快速查找元素。这种方法构造出来的转换函数称为哈希函数,散列函数,构造出来的结构称为哈希表

一.哈希函数

引入:

  1. 我们知道数组的优势在于快速查找某个元素,拿到数组的下标(索引)就直接可以定位到元素的位置
  2. 如果想知道一组元素出现的个数,可以创建一个范围足够大的数组,数组内每一个元素值代表相应元素出现的次数,如果插入元素i,就执行A[i]++,删除元素执行A[i]--,搜索元素时检查A[i]是否为0,这种做法用空间换时间
  3. 缺点:浪费空间,当遇到字符串时的编码问题
  4. 于是需要一种映射函数将大数映射成小数(散列函数),例如:hash[key] = key % size
  5. 问题:不同的元素被映射到相同的位置,产生哈希冲突
  6. 解决:线性探测,二次探测,开链...

常见哈希函数

  • 直接定址法:hash[key] = A*key + B,需要事先知道关键字的分布
  • 除留余数法:hash[key] = key % p,将关键码转换成哈希地址
  • 平方取中法:对key平方取中间三位,例如:1234平方,取1522756中间3位,227
  • 数学分析法:取一组数重复次数最少,得到几位数字,将其反转,右环位移,左环位移。例如电话号码取后四位,得到2345,反转后5432,右换位移5234

二.哈希冲突的解决

闭散列:

闭散列(开放定址法),当出现哈希冲突时,如果哈希表未填满,可以把key存放到冲突位置的下一个空位置


寻找方法

  • 线性探测:从冲突位置开始,依次向后探测,直到寻到下一个空位置
    • 删除:采用闭散列处理哈希冲突,不能随便删除哈希表已有元素,若直接删除元素会影响其他元素的搜索(索引改变),所以线性探测采用标记的伪删除来删除元素
    • 一旦发生哈希冲突,所有冲突连在一起,容易产生产生数据堆积
    • 标记:EXIST,EMPTY,DELETE
  • 二次探测:H = H+ i^2
  • 开散列:利用哈希桶(数组里面存的链表的地址)

三.哈希表的实现

线性探测:
cpp 复制代码
enum STATE
{
	EXIST,
	EMPTY,
	DELETE
};

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

template<class K>
struct DefaultFunc	
{
	size_t operator()(const K& key)
	{
		return (size_t)key;		//转成无符号整型
	}
};

template<>    //类模板的全特化
struct DefaultFunc<string>
{
	size_t operator()(const string& str)
	{
		size_t hash = 0;
		for (auto ch : str)
		{
			hash *= 131;
			hash += ch;
		}
		return hash;
	}
};

template<class K, class V, class HashFunc = DefaultFunc<K>>
class HashTable
{
public:
	//构造函数
	HashTable()
	{
		_table.resize(10);
	}

	bool Insert(const pair<K, V>& kv)
	{
		static HashFunc hf;
		//最低扩容标准
		if (_n * 10 / _table.size() >= 7)
		{
			size_t nsize = _table.size() * 2;
			//遍历旧表,重新映射新表
			HashTable<K, V> newtable;
			newtable._table.resize(nsize);

			for (size_t i = 0; i < _table.size(); i++)
			{
				if (_table[i]._state == EXIST)
				{
					newtable.Insert(_table[i]._kv);
				}
			}
			_table.swap(newtable._table);
		}

		//线性探测
		size_t hashi = hf(kv.first) % _table.size();
		while (_table[hashi]._state == EXIST)
		{
			hashi++;
			hashi %= _table.size();
		}

		_table[hashi]._kv = kv;
		_table[hashi]._state = EXIST;
		_n++;
		return true;
	}

	HashData<const K, V>* Find(const K& key)
	{
		size_t hashi = key % _table.size();
		while (_table[hashi]._state != EMPTY)
		{
			if (_table[hashi]._state == EXIST && _table[hashi]._kv.first == key)
			{
				return (HashData<const K, V>) & _table[hashi];
			}
			hashi++;
			return nullptr;
		}
	}

	bool Erase(const K& key)
	{
		HashData<const K, V>* ret = Find(key);
		if (ret)
		{
			ret->_state = DELETE;
			_n--;
			return true;
		}
		return false;
	}
	void Print()
	{
		for (int i = 0; i < _table.size(); i++)
		{
			if (_table[i]._state == EXIST)
			{
				cout << "键" << ":" << _table[i]._kv.first << "值:" << _table[i]._kv.second << endl;
			}
		}
		cout << endl;
	}

private:
	vector<HashData<K, V>> _table;    //编译器对内置类型不做处理,自定义类型调用析构
	size_t _n = 0;
	
};

开散列:

cpp 复制代码
namespace hash_bucket
{
	template<class K>
	struct DefaultFunc
	{
		size_t operator()(const K& key)
		{
			return key;
		}
	};

	template<>
	struct DefaultFunc<string>
	{
		size_t operator()(const string& kv)
		{
			size_t hash = 0;
			for (auto ch : kv)
			{
				hash *= 131;	//哈希算法,处理字符串
				hash += ch;
			}
			return hash;
		}
	};

	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 HashFunc = DefaultFunc<K>>
	class HashTable
	{
		typedef HashNode<K, V> Node;
	public:
		HashTable()
		{
			_table.resize(10, nullptr);

		}
		~HashTable()
		{
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_table[i] = nullptr;
			}
		}
		bool Insert(const pair<K,V>& kv)
		{
			static HashFunc hf;
			if (_n == _table.size())
			{
				size_t newSize = _table.size() * 2;
				vector<Node*> newTable(newSize, nullptr);
				//遍历新链表
				for (size_t i = 0; i < _table.size(); i++)
				{
					Node* cur = _table[i];
					while (cur)
					{
						Node* next = cur->_next;

						size_t hashi = hf(cur->_kv.first) % newSize;
						cur->_next = newTable[hashi];
						newTable[hashi] = cur;

						cur = next;
					}
					_table[i] = nullptr;
				}
				_table.swap(newTable);
			}

			size_t hashi = hf(kv.first) % _table.size();
			Node* newnode = new Node(kv);
			newnode->_next = _table[hashi];		//头插
			_table[hashi] = newnode;
			_n++;
			return true;
		}

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

		bool Erase(const K& key)
		{
			size_t hashi = hf(key) % _table.size();

			Node* prev = nullptr;
			Node* cur = _table[hashi];

			while (cur)
			{
				if (cur->_kv.first == key)
				{
					if (prev == nullptr)
					{
						_table[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					_n--;
					return true;
				}
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}
			return false;
		}

		void Print()
		{
			for (size_t i = 0; i < _table.size(); i++)
			{
				printf("[%d]->", i);
				Node* cur = _table[i];
				while (cur)
				{
					cout << cur->_kv.first << ":" << cur->_kv.second << "->";
					cur = cur->_next;
				}
				printf("NULL\n");
			}
			cout << endl;
		}
	private:
		vector<Node*> _table;    //指针数组,vector被delete后,Node*指向的空间还未删除,需要析构
		size_t _n = 0;
	};
}
相关推荐
ShineWinsu1 天前
对于C++:继承的解析—上
开发语言·数据结构·c++·算法·面试·笔试·继承
小付同学呀1 天前
C语言学习(五)——输入/输出
c语言·开发语言·学习
梦幻精灵_cq1 天前
学C之路:不可或缺的main()主函数框架(Learn-C 1st)
c语言·开发语言
消失的旧时光-19431 天前
C++ 多线程与并发系统取向(二)—— 资源保护:std::mutex 与 RAII(类比 Java synchronized)
java·开发语言·c++·并发
福大大架构师每日一题1 天前
go-zero v1.10.0发布!全面支持Go 1.23、MCP SDK迁移、性能与稳定性双提升
开发语言·后端·golang
抓饼先生1 天前
iceoryx编译和验证
linux·c++·零拷贝·iceoryx
王老师青少年编程1 天前
2020年信奥赛C++提高组csp-s初赛真题及答案解析(阅读程序第2题)
c++·题解·真题·初赛·信奥赛·csp-s·提高组
五阿哥永琪1 天前
1. 为什么java不能用is开头来做布尔值的参数名,会出现反序列化异常。
java·开发语言
逻极1 天前
pytest 入门指南:Python 测试框架从零到一(2025 实战版)
开发语言·python·pytest
你的冰西瓜1 天前
C++ STL算法——排序和相关操作
开发语言·c++·算法·stl