开放地址法:【C++进阶学习】第九弹——哈希的原理与实现——开放寻址法的讲解-CSDN博客

前言:

目录

一、链地址法的基本思想

二、链地址法的实现步骤

节点结构

构造和析构

插入操作

查找操作

删除操作

打印操作

三、测试代码

四、总结


一、链地址法的基本思想

前面所讲的开放地址法,我们是通过建立一种映射的关系来存储数据

【C++进阶学习】第十弹——哈希的原理与实现——链地址法的原理与讲解-LMLPHP

这种方法时常会遇到图中的这种情况,有利有弊

【C++进阶学习】第十弹——哈希的原理与实现——链地址法的原理与讲解-LMLPHP

二、链地址法的实现步骤

首先,我们先来看一下链地址法的重点:

节点结构

	template<class K>       
struct HashFunc         //仿函数,这里的功能是将其他类型转化为整形
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};
template<>     //特化
struct HashFunc<string>    //string类的不可以直接转化为整形,所以需要特殊处理
{
	size_t operator()(const string& key)
	{
		size_t hash = 0;
		for (auto e : key)
		{
			hash *= 31;
			hash += e;
		}
		return hash;
	}
};


    template<class K,class V>
	struct HashNode
	{
		HashNode* next;
		pair<K, V> _kv;
		HashNode(const pair<K,V>& kv)    //构造函数
			:next(nullptr)     //初始化列表
			,_kv(kv)
		{}
	};

	template<class K,class V, class Hash = HashFunc<K>>
	class HashTable
	{
		typedef HashNode<K, V> Node;
	public:
		
	private:
		//vector<list> _tables;    //这也是一种思路
		vector<Node*> _tables;
		size_t _n;
	};

构造和析构

因为在节点中我们使用了指针类型的数据,所以我们尽量将构造和析构函数自己定义,这里没啥难度,看代码即可:

        HashTable()
		{
			_tables.resize(10);     //初始化大小为19
		}
		~HashTable()
		{
			for (int i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];     //每个链表的头节点
				while (cur)     //遍历链表,清空链表中的所有元素
				{
					Node* next = cur->next;
					delete cur;
					cur = next;
				}
				_tables[i] = nullptr;
			}
		}

插入操作

【C++进阶学习】第十弹——哈希的原理与实现——链地址法的原理与讲解-LMLPHP

链地址法插入操作的基本思路就是:

		bool Insert(const pair<K, V>& kv)
		{
			Hash hf;
			if (Find(kv.first))
				return false;

			//负载因子最大到1,到1时进行扩容
			//我们提供这样一个思路:如果数据真的非常多的时候,用链表来存储,因为要
			//                    考虑负载因子的原因,其实是比较浪费空间的,我们
			//                    可以把节点结构进行更改,改成红黑树的结构
			if (_n == _tables.size())
			{
				扩容
				//size_t newSize = _tables.size() * 2;
				//HashTable<K, V> newHT;
				//newHT._tables.resize(newSize);
				遍历旧表
				//for (size_t i = 0; i < _tables.size(); i++)
				//{
				//	Node* cur = _tables[i];
				//	while (cur)
				//	{
				//		newHT.Insert(cur->_kv);
				//		cur = cur->next;
				//	}
				//}
				//_tables.swap(newHT._tables);

				//方法二
				vector<Node*> newTables;
				newTables.resize(_tables.size() * 2);
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->next;
						size_t hashi = hf(cur->_kv.first) % newTables.size();
						cur->next = newTables[hashi];
						newTables[hashi] = cur;
						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newTables);
			}
			size_t hashi = hf(kv.first) % _tables.size();     //哈希函数
			Node* newnode = new Node(kv);
			//头插
			newnode->next = _tables[hashi];
			_tables[hashi] = newnode;
			++_n;

			return true;
		}

查找操作

	    Node* Find(const K& key)
		{
			Hash hf;
			size_t hashi = hf(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)
		{
			Hash hf;
			size_t hashi = hf(key) % _tables.size();
			Node* cur = _tables[hashi];
			Node* parent = nullptr;
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					if (parent == nullptr)
					{
						_tables[hashi] = cur->next;
					}
					else
					{
						parent->next = cur->next;
					}
					delete cur;
					cur = nullptr;
					return true;
				}
				parent = cur;
				cur = cur->next;
			}
			return false;
		}

打印操作

		void Some()
		{
			size_t bucketSize = 0;        //桶的个数 
			size_t maxbucketLen = 0;      //最大桶长
			size_t sum=0;                 //总的元素个数
			double averagebucketLen = 0;  //平均桶长

			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				if (cur)
				{
					++bucketSize;
				}
				size_t bucketLen = 0;
				while (cur)
				{
					++bucketLen;
					cur = cur->next;
				}
				sum += bucketLen;
				if (bucketLen > maxbucketLen)
				{
					maxbucketLen = bucketLen;
				}
			}
			averagebucketLen = (double)sum / (double)bucketSize;
			cout << "桶的个数:" << bucketSize << endl;
			cout << "桶的最大长度:" << maxbucketLen << endl;
			cout << "平均桶的长度:" << averagebucketLen << endl;
		}

三、测试代码

我们给出几个测试用例检验一下上面的方法是否有误:

测试一:

	void TestHT1()   //测试插入,查找和删除操作是否有误
	{
		HashTable<int, int> ht;
		int a[] = { 4,14,24,34,5,7,1 };
		for (auto e : a)
		{
			ht.Insert(make_pair(e, e));
		}

		cout << ht.Find(4) << endl;     //如果成功插入,这里会返回一个地址
		ht.Erase(4);                    //删除节点
		cout << ht.Find(4) << endl;     //删除后会返回nullptr
	}

运行结果:

【C++进阶学习】第十弹——哈希的原理与实现——链地址法的原理与讲解-LMLPHP

测试二:

	void TestHT2()    //测试string
	{
		string arr[] = { "香蕉","甜瓜","苹果","香蕉","苹果","苹果" };
		HashTable<string, int> ht;
		for (auto e : arr)
		{
			auto ret = ht.Find(e);
			if (ret)
				ret->_kv.second++;
			else
			{
				ht.Insert(make_pair(e, 1));
			}
		}
		ht.Some();    //通过桶的相关信息可以推断出插入情况
	}

运行结果:

【C++进阶学习】第十弹——哈希的原理与实现——链地址法的原理与讲解-LMLPHP

四、总结

感谢各位大佬观看,创作不易,还请各位大佬点赞支持!!!

07-30 04:54