【C++】如何用一个哈希表同时封装出unordered_set与unordered_map

👀樊梓慕:个人主页

 🎥个人专栏:《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C++》《Linux》《算法》

🌝每一个不曾起舞的日子,都是对生命的辜负


目录

前言

1.哈希桶源码

 2.哈希表模板参数的控制

3.字符串作为键值如何映射哈希值

3.1BKDRHash算法

4.封装

4.1构造函数

4.拷贝构造 

4.2析构函数

4.3赋值运算符重载

4.4正向迭代器

4.4.1定义 

4.4.2构造 

4.4.3重载解引用*操作符

4.4.4重载箭头->操作符

4.4.5重载==与!=操作符

4.4.6重载前置++操作符

5.源码

哈希表与迭代器封装源码

MyUnordered_set源码

MyUnordered_map源码

测试源码


前言

上次我们模拟实现了闭散列的哈希表与开散列的哈希表,但很明显上次实现的很粗糙功能很简单,迭代器并没有实现,以及泛型编程思想也没有应用,那么对于本篇文章我们要用一个哈希表同时封装出unordered_set 与 unordered_map,对此我们之前已经成功用一颗红黑树同时封装出set与map,大致的思想是类似的,只不过这里会略微复杂一些。


欢迎大家📂收藏📂以便未来做题时可以快速找到思路,巧妙的方法可以事半功倍。 

=========================================================================

GITEE相关代码:🌟樊飞 (fanfei_c) - Gitee.com🌟

=========================================================================


1.哈希桶源码

//每个哈希桶中存储数据的结构
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 HashTable
{
public:
	typedef HashNode<K, V> Node;

	//插入函数
	bool Insert(const pair<K, V>& kv)
	{
		//1、查看哈希表中是否存在该键值的键值对
		if (Find(kv.first)) //哈希表中已经存在该键值的键值对(不允许数据冗余)
		{
			return false; //插入失败
		}

		//2、判断是否需要调整哈希表的大小
		if (_n == _tables.size()) //负载因子超过1
		{
			//增容
			//a、创建一个新的哈希表,新哈希表的大小设置为原哈希表的2倍
			vector<Node*> newTables(_tables.size() * 2, nullptr);

			//b、将原哈希表当中的结点插入到新哈希表
			for (size_t i = 0; i < _tables.size(); i++)
			{
				//取出旧表中的节点,重新计算挂到新表桶中
				Node* cur = _tables[i];
				while (cur)
				{
					Node* next = cur->_next;
					size_t hashi = cur->_kv.first % newTables.size();

					//将该结点头插到新哈希表中编号为index的哈希桶中
					cur->_next = newTables[hashi];
					newTables[hashi] = cur;

					cur = next;
				}
				_tables[i] = nullptr; //该桶取完后将该桶置空

			}
			//c、交换这两个哈希表
			_tables.swap(newTables);
		}

		//3、将键值对插入哈希表
		size_t hashi = kv.first % _tables.size(); //通过哈希函数计算出对应的哈希桶编号index(除数不能是capacity)
		Node* newnode = new Node(kv); //根据所给数据创建一个待插入结点

		//将该结点头插到新哈希表中编号为index的哈希桶中
		newnode->_next = _tables[hashi];
		_tables[hashi] = newnode;

		//4、哈希表中的有效元素个数加一
		_n++;
		return true;
	}

	//查找函数
	HashNode<K, V>* Find(const K& key)
	{
		if (_tables.size() == 0) //哈希表大小为0,查找失败
		{
			return nullptr;
		}

		size_t hashi = key % _tables.size();

		//遍历编号为index的哈希桶
		HashNode<K, V>* cur = _tables[hashi];
		while (cur) //直到将该桶遍历完为止
		{
			if (cur->_kv.first == key) //key值匹配,则查找成功
			{
				return cur;
			}
			cur = cur->_next;
		}
		return nullptr; //直到该桶全部遍历完毕还没有找到目标元素,查找失败
	}

	//删除函数
	bool Erase(const K& key)
	{
		//1、通过哈希函数计算出对应的哈希桶编号hashi(除数不能是capacity)
		size_t hashi = key % _tables.size();
		//2、在编号为index的哈希桶中寻找待删除结点
		Node* prev = nullptr;
		Node* cur = _tables[hashi];
		while (cur) //直到将该桶遍历完为止
		{
			if (cur->_kv.first == key) //key值匹配,则查找成功
			{
				//3、若找到了待删除结点,则删除该结点
				if (prev == nullptr) //待删除结点是哈希桶中的第一个结点
				{
					_tables[hashi] = cur->_next; //将第一个结点从该哈希桶中移除
				}
				else //待删除结点不是哈希桶的第一个结点
				{
					prev->_next = cur->_next; //将该结点从哈希桶中移除
				}
				delete cur; //释放该结点
				//4、删除结点后,将哈希表中的有效元素个数减一
				_n--;
				return true; //删除成功
			}
			prev = cur;
			cur = cur->_next;
		}
		return false; //直到该桶全部遍历完毕还没有找到待删除元素,删除失败
	}

private:
	vector<Node*> _tables; //哈希表
	size_t _n = 0; //哈希表中的有效元素个数
};

 2.哈希表模板参数的控制

template<class K, class T>
class HashTable

那么对于unordered_set:

template<class K>
class unordered_set
{
public:
	//...
private:
	HashTable<K, K> _ht; //传入底层哈希表的是K和K
};

 对于unordered_map:

template<class K, class V>
class unordered_map
{
public:
	//...
private:
	HashTable<K, pair<K, V>> _ht; //传入底层哈希表的是K以及K和V构成的键值对
};

即:


而哈希结点的模板参数也应该由原来的K、V变为T:

  • 上层容器是unordered_set时,传入的T是键值,哈希结点中存储的就是键值。
  • 上层容器是unordered_map时,传入的T是键值对,哈希结点中存储的就是键值对。

更改模板参数后,哈希结点的定义如下:

//哈希结点的定义
template<class T>
struct HashNode
{
	T _data;
	HashNode<T>* _next;

	//构造函数
	HashNode(const T& data)
		:_data(data)
		, _next(nullptr)
	{}
};

与红黑树类似,红黑树需要获取 T 中的key值用来比较大小,在哈希映射中,我们需要获得 T 的键值,然后通过哈希函数计算出对应的哈希地址进行映射。

现在由于我们在哈希结点当中存储的数据类型是T,这个T可能就是一个键值,也可能是一个键值对,对于底层的哈希表来说,它并不知道哈希结点当中存储的数据究竟是什么类型,因此需要由上层容器提供一个『 仿函数』,用于获取T类型数据当中的键值。

对于unordered_map :

template<class K, class V>
class unordered_map
{
	//仿函数
	struct MapKeyOfT
	{
		const K& operator()(const pair<K, V>& kv) //返回键值对当中的键值key
		{
			return kv.first;
		}
	};
public:
	//...
private:
	HashTable<K, pair<K, V>, MapKeyOfT> _ht;
};

而虽然unordered_set容器传入哈希表的T就是键值,但是底层哈希表并不知道上层容器的种类,底层哈希表在获取键值时会统一通过传入的仿函数进行获取,因此unordered_set容器也需要向底层哈希表提供一个仿函数。

对于unordered_set :

template<class K>
class unordered_set
{
	//仿函数
	struct SetKeyOfT
	{
		const K& operator()(const K& key) //返回键值key
		{
			return key;
		}
	};
public:
	//...
private:
	HashTable<K, K, SetKeyOfT> _ht;
};

因此,底层哈希表的模板参数现在需要增加一个,用于接收上层容器提供的仿函数。

template<class K, class T, class KeyOfT>
class HashTable

3.字符串作为键值如何映射哈希值

上篇文章我在结尾留下一个问题:

除留余数法是映射哈希值的有效方法,但是这里我们考虑的都是整型情况下,那如果是字符串呢?如果key值是字符串,字符串可没办法取余数,而我们最终是一定要实现泛型编程的,我们怎样才能构建出一个通用的映射关系呢?

3.1BKDRHash算法

经过实验,BKDRHash算法无论是在实际效果还是编码实现中,效果都是最突出的。该算法由于在Brian Kernighan与Dennis Ritchie的《The C Programing Language》一书被展示而得名,是一种简单快捷的hash算法,也是Java目前采用的字符串的hash算法。

因此,现在我们需要在哈希表的模板参数中再增加一个『 仿函数』,用于将键值key转换成对应的整型。

template<class K, class T, class KeyOfT, class HashFunc = Hash<K>>
class HashTable

若是上层没有传入该仿函数,我们则使用默认的仿函数,该默认仿函数直接返回键值key即可,但是用字符串作为键值key是比较常见的,因此我们可以针对string类型写一个类模板的『 特化』,此时当键值key为string类型时,该仿函数就会根据BKDRHash算法返回一个对应的整型。

template<class K>
struct Hash
{
	size_t operator()(const K& key) //返回键值key
	{
		return key;
	}
};
//string类型的特化
template<>
struct Hash<string>
{
	size_t operator()(const string& s) //BKDRHash算法
	{
		size_t value = 0;
		for (auto ch : s)
		{
			value = value * 131 + ch;
		}
		return value;
	}
};

4.封装

4.1构造函数

我们知道哈希表在插入之前一定size>0,因为如果size==0就无法计算哈希值,所以我们之前说在构造这里处理,让哈希表创建出来之后就有一定大小的空间。

HashTable()
{
	_tables.resize(10, nullptr); //构造10个大小的空间,默认存放空指针
	_n = 0; //实际存储个数为0
}

4.拷贝构造 

4.2析构函数

因为哈希表当中存储的结点都是new出来的,因此在哈希表被析构时必须进行结点的释放。在析构哈希表时我们只需要依次取出非空的哈希桶,遍历哈希桶当中的结点并进行释放即可。

~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;
	}
}

4.3赋值运算符重载

实现赋值运算符重载函数时,可以通过参数间接调用拷贝构造函数,之后将拷贝构造出来的哈希表和当前哈希表的两个成员变量分别进行交换即可,当赋值运算符重载函数调用结束后,拷贝构造出来的哈希表会因为出了作用域而被自动析构,此时原哈希表之前的数据也就顺势被释放了。

HashTable& operator=(HashTable ht)
{
	//交换哈希表中两个成员变量的数据
	_tables.swap(ht._table);
	swap(_n, ht._n);

	return *this; //支持连续赋值
}

4.4正向迭代器

哈希表的正向迭代器实际上就是对哈希结点指针进行了封装,但是由于在实现++运算符重载时,可能需要在哈希表中去寻找下一个非空哈希桶,因此每一个正向迭代器中都应该存储哈希表的地址。

4.4.1定义 

//正向迭代器
template<class K, class T, class KeyOfT, class Hash = Hash<K>>
struct __HTIterator
{
	typedef HashNode<T> Node; //哈希结点的类型
	typedef HashTable<K, T, KeyOfT, Hash> HT; //哈希表的类型
	typedef __HTIterator<K, T, KeyOfT, Hash> Self; //正向迭代器的类型

	Node* _node; //结点指针
	HT* _ht; //哈希表的地址
};

4.4.2构造 

因此在构造正向迭代器时,我们不仅需要对应哈希结点的指针,还需要该哈希结点所在哈希表的地址。

__HTIterator(Node* node, HT* ht)
	:_node(node)
	, _ht(ht)
{}

4.4.3重载解引用*操作符

T& operator*()
{
	return _node->_data; //返回哈希结点中数据的引用
}

4.4.4重载箭头->操作符

T* operator->()
{
	return &_node->_data; //返回哈希结点中数据的地址
}

4.4.5重载==与!=操作符

bool operator!=(const Self& s) const
{
	return _node != s._node; //判断两个结点的地址是否不同
}

bool operator==(const Self& s) const
{
	return _node == s._node; //判断两个结点的地址是否相同
}

4.4.6重载前置++操作符

前置++的逻辑,我们只需要额外考虑以下可能会换桶的情况即可。

  • 若当前结点不是当前哈希桶中的最后一个结点,则++后走到当前哈希桶的下一个结点。
  • 若当前结点是当前哈希桶的最后一个结点,则++后走到下一个非空哈希桶的第一个结点。
Self& operator++()
{
	if (_node->_next)
	{
		// 当前桶还是节点
		_node = _node->_next;
	}
	else
	{
		// 当前桶走完了,找下一个桶
		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;
			}

			hashi++;
		}

		// 后面没有桶了
		if (hashi == _ht->_tables.size())
		{
			_node = nullptr;
		}
	}

	return *this;
}

 注意: 哈希表的迭代器类型是单向迭代器,没有反向迭代器,所以没有实现–运算符的重载。


5.源码

在具体实现时,需要注意迭代器与哈希表的相互引用,由于迭代器实现在哈希表前面,而编译器只会向前寻找哈希表的定义,所以会发生迭代器不认识哈希表的情况,这是就需要一个前置声明(具体看代码中的体现),而迭代器要访问哈希表中的private成员,所以要在哈希表中声明迭代器为友元。

哈希表与迭代器封装源码

#pragma once

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

// 特化
template<>
struct HashFunc<string>
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto e : s)
		{
			hash += e;
			hash *= 131;
		}

		return hash;
	}
};

namespace Open_Hash
{
	template<class T>
	struct HashNode
	{
		HashNode<T>* _next;
		T _data;

		HashNode(const T& data)
			:_next(nullptr)
			, _data(data)
		{}
	};

	// 前置声明
	template<class K, class T, class KeyOfT, class Hash>
	class HashTable;

	template<class K, class T, class KeyOfT, class Hash>
	struct __HTIterator
	{
		typedef HashNode<T> Node;
		typedef HashTable<K, T, KeyOfT, Hash> HT;
		typedef __HTIterator<K, T, KeyOfT, Hash> Self;

		Node* _node;
		HT* _ht;

		__HTIterator(Node* node, HT* ht)
			:_node(node)
			, _ht(ht)
		{}

		T& operator*()
		{
			return _node->_data;
		}

		Self& operator++()
		{
			if (_node->_next)
			{
				// 当前桶还是节点
				_node = _node->_next;
			}
			else
			{
				// 当前桶走完了,找下一个桶
				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;
					}

					hashi++;
				}

				// 后面没有桶了
				if (hashi == _ht->_tables.size())
				{
					_node = nullptr;
				}
			}

			return *this;
		}

		bool operator!=(const Self& s)
		{
			return _node != s._node;
		}
	};

	template<class K, class T, class KeyOfT, class Hash>
	class HashTable
	{
		//迭代器要访问哈希表的私有成员,所以要声明友元
		template<class K, class T, class KeyOfT, class Hash>
		friend struct __HTIterator;

		typedef HashNode<T> Node;
	public:
		typedef __HTIterator<K, T, KeyOfT, Hash> iterator;

		iterator begin()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				// 找到第一个桶的第一个节点
				if (_tables[i])
				{
					return iterator(_tables[i], this);
				}
			}

			return end();
		}

		iterator end()
		{
			return iterator(nullptr, this);
		}

		HashTable()
		{
			_tables.resize(10, nullptr);
			_n = 0;
		}

		~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;
			}
		}

		bool Insert(const T& data)
		{
			KeyOfT kot;

			if (Find(kot(data)))
				return false;

			Hash hs;

			// 负载因子到1就扩容
			if (_n == _tables.size())
			{
				vector<Node*> newTables(_tables.size() * 2, nullptr);
				for (size_t i = 0; i < _tables.size(); i++)
				{
					// 取出旧表中节点,重新计算挂到新表桶中
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;

						// 头插到新表
						size_t hashi = hs(kot(cur->_data)) % newTables.size();
						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;
			return true;
		}

		Node* Find(const K& key)
		{
			KeyOfT kot;
			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return cur;
				}

				cur = cur->_next;
			}

			return nullptr;
		}

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

					delete cur;

					--_n;
					return true;
				}

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

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

}

MyUnordered_set源码

#pragma once

#include"Open_Hash.h"

namespace MyHash
{
	template<class K, class Hash = HashFunc<K>>
	class unordered_set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename Open_Hash::HashTable<K, const K, SetKeyOfT, Hash>::iterator iterator;

		iterator begin()
		{
			return _ht.begin();
		}

		iterator end()
		{
			return _ht.end();
		}

		bool insert(const K& key)
		{
			return _ht.Insert(key);
		}

	private:
		Open_Hash::HashTable<K, const K, SetKeyOfT, Hash> _ht;
	};

	void test_set1()
	{
		unordered_set<int> us;
		us.insert(3);
		us.insert(1);
		us.insert(5);
		us.insert(15);
		us.insert(45);
		us.insert(7);

		unordered_set<int>::iterator it = us.begin();
		while (it != us.end())
		{
			//*it += 100;
			cout << *it << " ";
			++it;
		}
		cout << endl;

		for (auto e : us)
		{
			cout << e << " ";
		}
		cout << endl;
	}
}

MyUnordered_map源码

#pragma once

#include"Open_Hash.h"

namespace MyHash
{
	template<class K, class V, class Hash = HashFunc<K>>
	class unordered_map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};

	public:
		typedef typename Open_Hash::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::iterator iterator;

		iterator begin()
		{
			return _ht.begin();
		}

		iterator end()
		{
			return _ht.end();
		}

		bool insert(const pair<K, V>& kv)
		{
			return _ht.Insert(kv);
		}
	private:
		Open_Hash::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
	};
	void test_map1()
	{
		unordered_map<string, string> dict;
		dict.insert(make_pair("sort", "排序"));
		dict.insert(make_pair("left", "左面"));
		dict.insert(make_pair("right", "右面"));

		for (auto& kv : dict)
		{
			//kv.first += 'x';
			kv.second += 'y';

			cout << kv.first << ":" << kv.second << endl;
		}
	}
}

测试源码

#include<iostream>
#include<string>
#include<vector>

using namespace std;

#include"Open_Hash.h"

#include"MyUnordered_map.h"
#include"MyUnordered_set.h"

int main()
{
	MyHash::test_set1();
	MyHash::test_map1();
	return 0;
}

=========================================================================

如果你对该系列文章有兴趣的话,欢迎持续关注博主动态,博主会持续输出优质内容

🍎博主很需要大家的支持,你的支持是我创作的不竭动力🍎

🌟~ 点赞收藏+关注 ~🌟

=========================================================================

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/484566.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

(三维重建学习)已有位姿放入colmap和3D Gaussian Splatting训练

这里写目录标题 一、colmap解算数据放入高斯1. 将稀疏重建的文件放入高斯2. 将稠密重建的文件放入高斯 二、vkitti数据放入高斯 一、colmap解算数据放入高斯 运行Colmap.bat文件之后&#xff0c;进行稀疏重建和稠密重建之后可以得到如下文件结构。 1. 将稀疏重建的文件放入高…

windows10 WSL启动Ubuntu虚拟机,安装DolphinScheduler

文章目录 1. 启动WSL与虚拟机2. 安装Docker与DolphinScheduler容器 1. 启动WSL与虚拟机 使用管理员权限运行命令&#xff1a; Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux重启后即可创建虚拟机 在Microsoft Store中搜索Ubuntu&…

Wear-Any-Way——可控虚拟试衣一键试穿,可自定义穿着方式

概述 Wear-Any-Way 是阿里巴巴最新推出的虚拟试衣技术&#xff0c;它不仅可以让用户在虚拟环境中试穿衣服&#xff0c;还可以根据需要自定义衣服的样式&#xff0c;比如卷起袖子、打开或拖动外套等。这种技术的引入旨在帮助消费者更好地了解衣服在不同穿着方式下的效果&#x…

一个python实现的kline-chart图表程序(二)

前面一中简单介绍了kline-chart的图表程序&#xff0c;实际上这个程序最主要的功能不是显示K线&#xff0c;因为显示K线的程序太多了&#xff0c;没必要专门重写&#xff0c;这个程序最主要的功能是根据需要显示包含K线在内的各种指标&#xff0c;自己算的指标&#xff0c;或是…

plSql 大批量数据导入到表中

主要2种思路&#xff0c;一为insert插入sql&#xff0c;二是借助plsql提供的工具 insert语句odbc importer/导入器 insert语句 把要插入的数据转为insert语句&#xff0c;直接复制到plsql的sql窗口&#xff0c;运行即可&#xff1b;或者在命令行窗口回车键&#xff0c;选择要执…

使用 RunwayML 对图像进行 Camera 操作

RunwayML 是一個功能強大的平台&#xff0c;可以讓您使用 AI 和机器学习来增强您的图像和视频。 它提供一系列预训练模型&#xff0c;可用于各种任务&#xff0c;包括图像编辑、风格化和特效。 在本文中&#xff0c;我们将介绍如何使用 RunwayML 对图像进行 Camera 操作。我们…

[AIGC] SQL中的数据添加和操作:数据类型介绍

SQL&#xff08;结构化查询语言&#xff09;作为一种强大的数据库查询和操作工具&#xff0c;它能够完成从简单查询到复杂数据操作的各种任务。在这篇文章中&#xff0c;我们主要讨论如何在SQL中添加&#xff08;插入&#xff09;数据&#xff0c;以及在数据操作过程中&#xf…

数据结构(五)——树森林

5.4 树和森林 5.4.1 树的存储结构 树的存储1&#xff1a;双亲表示法 用数组顺序存储各结点&#xff0c;每个结点中保存数据元素、指向双亲结点(父结点)的“指针” #define MAX_TREE_SIZE 100// 树的结点 typedef struct{ElemType data;int parent; }PTNode;// 树的类型 type…

学习或复习电路的game推荐:nandgame(NAND与非门游戏)、Turing_Complete(图灵完备)

https://www.nandgame.com/ 免费 https://store.steampowered.com/app/1444480/Turing_Complete/ 收费&#xff0c;70元。据说可以导出 Verilog &#xff01;

关于安卓调用文件浏览器(一)打开并复制

背景 最近在做一个硬件产品&#xff0c;安卓应用开发。PM抽风&#xff0c;要求从app打开文件浏览器&#xff0c;跳转到指定目录&#xff0c;然后可以实现文件复制粘贴操作。 思考 从应用开发的角度看&#xff0c;从app打开系统文件浏览器并且选择文件&#xff0c;这是很常见…

馆室一体化查档平台制度有哪些

馆室一体化查档平台制度是指图书馆或档案馆在数字化和信息化的背景下&#xff0c;建立起的集查阅、借阅、咨询、文献传递等多项功能于一体的平台制度。下面是一些常见的馆室一体化查档平台制度&#xff1a; 1. 馆藏管理制度&#xff1a;包括图书和档案的采购、编目、分类、整理…

那些王道书里的题目-----计算机网络篇

注&#xff1a;仅记录个人认为有启发的题目 p155 34.下列四个地址块中&#xff0c;与地址块 172.16.166.192/26 不重叠&#xff0c;且与172.16.166.192/26聚合后的地址块不会引入多余地址的是&#xff08;&#xff09; A.172.16.166.192/27 B.172.16.166.128/26 …

day06vue2学习

day06 路由的封装抽离 问题&#xff1a;所有的路由配置都堆在main.js中不太合适么&#xff1f;不好&#xff0c;会加大代码的复杂度 目标&#xff1a;将路由模块抽离出来。好处&#xff1a;差分模块&#xff0c;利于维护。 大致的做法就是&#xff0c;将路由相关的东西都提…

codeTop102:二叉树的层序遍历

前言 在已知BFS的方式后&#xff0c;知道每次从队列中取一个节点&#xff0c;就要将这个节点的所有子节点按照顺序放入队列。 难点在于怎么确定将同一层的节点放在一个数组里面的输出&#xff0c;也就是输出一个二维数组&#xff1f; 解决方法: 每次while循环将队列上轮放入的…

蓝桥集训之矩形牛棚

蓝桥集训之矩形牛棚 核心思想&#xff1a;单调队列 模板&#xff1a;Acwing.131.直方图矩形面积首先遍历所有下界 然后确定以该下界为底的直方图 求最大矩形 #include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N 30…

Java学习day2

命名规则 在JAVA中&#xff0c;公共类的明朝必须与包含该类的源文件的文件名向匹配&#xff0c;即 这两个名称要一致 变量类型 与c/c基本一致 需要注意的是&#xff0c;long类型的数据在后面需要加上l或L&#xff08;建议加L&#xff0c;l可能会被误判&#xff09;&#xff…

【Redis】优惠券秒杀

全局唯一ID 全局唯一ID生成策略&#xff1a; UUIDRedis自增snowflake算法数据库自增 Redis自增ID策略&#xff1a;每天一个key&#xff0c;方便统计订单量ID构造是 时间戳 计数器 Component public class RedisIdWorker {// 2024的第一时刻private static final long BEGIN…

【C语言】编译和链接----预处理详解【图文详解】

欢迎来CILMY23的博客喔&#xff0c;本篇为【C语言】文件操作揭秘&#xff1a;C语言中文件的顺序读写、随机读写、判断文件结束和文件缓冲区详细解析【图文详解】&#xff0c;感谢观看&#xff0c;支持的可以给个一键三连&#xff0c;点赞关注收藏。 前言 欢迎来到本篇博客&…

易语言学习第一天(安装破解和配置)

一、引言 易语言是一个自主开发&#xff0c;适合国情&#xff0c;不同层次不同专业的人员易学易用的汉语编程语言。易语言降低了广大电脑用户编程的门槛&#xff0c;尤其是根本不懂英文或者英文了解很少的用户&#xff0c;可以通过使用本语言极其快速地进入Windows程序编写的大…

全新的分布式锁,功能简单且强大

分布式锁是分布式系统中一个极为重要的工具。 目前有多种分布式锁的设计方案&#xff0c;比如借助 redis&#xff0c;mq&#xff0c;数据库&#xff0c;zookeeper 等第三方服务系统来设计分布式锁。 tldb 提供的分布式锁&#xff0c;主要是要简化这个设计的过程&#xff0c;提…