c++编程(25)——unordered_map模拟实现

欢迎来到博主的专栏:c++编程

博主ID:代码小豪

文章目录

unordered_map是STL中的关联式容器之一,与常规的map有两点不同
(1)unordered是无序的意思,之所以取这名字,是因为与常规map迭代器前进的方向是有序的,而unordered_map的迭代器前进方向是无序的。

cpp 复制代码
void test1()
{
	int a[] = { 11,25,21,1,6,9,16 };
	unordered_map<int, int> op;
	map<int, int> m;
	for (auto e : a)
	{
		op.insert(make_pair(e, e));
		m.insert(make_pair(e, e));
	}
	for (auto e : m)//遍历map容器
	{
		cout << e.first << ' ';//1 6 9 11 16 21 25
	}
	cout << endl;
	for (auto e : op)//遍历unordered_map
	{
		cout << e.first << ' ';//11 9 1 25 21 6 16
	}
}

(2)之所以造成这种差异,是因为unordered_map的底层用的是哈希表,而map的底层是红黑树。这是底层而造成的差异。

unorder_map的底层

关于哈希表的介绍,博主放在了数据结构专栏中,因此不再赘述。

由于STL标准库中的set和map共用一个hashtable底层(SGI版stl),因此博主将hashtable做了些许修改:

cpp 复制代码
template<class key,class T,class keyofT>
class hash_tab
{
public:
	typedef T value_type;
	typedef hash_Node<value_type> Node;
	hash_tab()
	{
		_tab.resize(10);
	}

	~hash_tab()
	{
		for (size_t i=0;i<_tab.size();i++)
		{
			Node* cur = _tab[i];
			while (cur != nullptr)
			{
				Node* next = cur->_next;
				delete cur;
				cur = next;
			}
			_tab[i] = nullptr;
			_n = 0;
		}
	}

private:
	vector<Node*> _tab;
	size_t _n;
	keyofT kot;
};

由于map和set的数据不一样,set的数据是key类型的,而map的数据是pair<key,value>型的,因此需要一个key值提取器------keyofT。关于keyofT怎么用,博主在会在后续文章提到。

insert

unordered_map类被博主设计成这样,其成员为底层哈希表.

cpp 复制代码
template<class key,class value>
class unordered_map
{
public:
	typedef pair<key, value> value_type;

	template<class key,class value>
	class unordered_mapkeyofT
	{
		const key& operator()(const value_type& data)const
		{
			return data.first;
		}
	};

private:
	myhash::hash_tab<key, value_type, unordered_mapkeyofT<key, value>> _hashtab;//底层哈希表
	};

如果使用了底层哈希表,那么insert可以直接复用_hashtab的insert函数。

cpp 复制代码
pair<iterator, bool> insert(const value_type& data)
{
	return _hashtab.insert(data);
}

而unordered_map的erase,find操作都可以这样做,因此模拟实现unordered_map最重要的一步是设计出hashtab使用的迭代器。

迭代器

关于哈希的迭代器,博主是这样构思的,迭代器有两个成员,分别是指向当前哈希表的指针,以及一个指向该表的有效数据的指针。

cpp 复制代码
template<class key,class T,class ref,class ptr,class keyofT>
struct hash_iterator
{
	typedef hash_Node<T> Node;
	typedef hash_tab<key, T, keyofT> hash;
	typedef hash_iterator self;

	hash_iterfator(Node* node,hash* tab)
	{
		_node = node;
		_tab = tab;
	}


	keyofT kot;
	Node* _node;//指向节点
	hash* _tab;//指向当前哈希表
};

begin()函数返回指向哈希表第一个有效数据的节点

cpp 复制代码
Iterator begin()
{
	Node* cur = nullptr;
	for (int i = 0; i < _tab.size(); i++)
	{
		cur = _tab[i];
		if (cur != nullptr)
			return Iterator(cur, this);
	}
	return Iterator(cur, this);
}

end函数则返回nullptr作为结束标志。

cpp 复制代码
Iterator end()
{
	return Iterator(nullptr, this);
}

最后加上const版本的begin和end。

cpp 复制代码
const_Iterator begin() const
{
	Node* cur = nullptr;
	for (int i = 0; i < _tab.size(); i++)
	{
		cur = _tab[i];
		if (cur != nullptr)
			return Iterator(cur, this);
	}
	return Iterator(cur, this);
}

const_Iterator end()const
{
	return Iterator(nullptr, this);
}

成员访问函数

迭代器的成员访问函数也就是operator*和operator->.这很简单。

cpp 复制代码
ref operator*()
{
	return _node->_data;
}

ptr operator->()
{
	return &operator*();
}

operator++

operator++让迭代器前进到下一个有效数据。做法如下:

(1)有限前进到同一个桶中的下一个有效数据的节点

(2)如果桶中不存在有效节点,就去找下一个有数据的哈希桶。

代码如下:

cpp 复制代码
self operator++()
{
	if (_node->_next != nullptr)
	{
		_node = _node->_next;
	}
	else
	{
		size_t hashnum = kot(_node->_data) % _tab->_tab.size();
		hashnum++;
		while (hashnum < _tab->_tab.size())
		{
			Node* cur = _tab->_tab[hashnum];
			if (cur != nullptr)
			{
				_node = cur;
				return *this;
			}
			hashnum++;
		}
		_node = nullptr;//如果没找到,就返回end()标志
		return *this;
	}
}

operator--

operator--的操作如下:

(1)首先找到当前节点的映射地址

(2)判断该节点是否是该桶的第一个节点

若为否,则迭代器指向该节点的上一个节点

若为真,则迭代器指向上一个桶的最后一个节点

代码如下:

cpp 复制代码
	self operator--()
	{
		int hashnum = kot(_node) % _tab->_tab.size();
		Node* cur = _tab->_tab[hashnum];

		if (cur != _node)
		{
			while (cur->_next != _node)
			{
				cur = cur->_next;
			}
			_node = cur;
			return *this;
		}
		else//cur==_node
		{
			for (int i = hashnum-1; i >= 0; i--)
			{
				cur = _tab->_tab[i];
				if (cur != nullptr)
				{
					while (cur->_next != nullptr)
					{
						cur = cur->_next;
					}
					_node = cur;
					return *this;
				}
			}
		}
	}
相关推荐
黄卷青灯771 分钟前
c++ vector类 和 eigen库 处理向量的区别 列出代码举例
开发语言·c++·vector·eigen
深夜吞食5 分钟前
项目实现:云备份③(配置文件加载模块、数据管理模块的实现)
linux·c语言·c++·json
cwywsx6 分钟前
C++:二叉搜索树
开发语言·c++·算法
小米里的大麦20 分钟前
【C++】深入理解作用域和命名空间:从基础到进阶详解
c++·笔记·作用域·命名空间
Lenyiin22 分钟前
3286、穿越网格图的安全路径
c++·算法·leetcode
小丑西瓜66626 分钟前
c++智能指针
开发语言·c++·学习·基础语法·智能指针
螺蛳粉只吃炸蛋的走风29 分钟前
面试题总结(三) -- 内存管理篇
c语言·c++·面试·内存·堆栈·raii
Daking-1 小时前
「数组」十大排序:精讲与分析(C++)
c++·算法·排序算法
轩源源1 小时前
C/C++内存管理
c语言·开发语言·数据结构·c++·算法·缓存·内存空间的开创
程序员黄老师1 小时前
Windows与Linux下 SDL2的第一个窗口程序
c++·windows·ffmpeg·sdl