C++ map的简单使用实现

map和set的底层都是通过红黑树来实现的,但并不是原生态的红黑树,而是经过改造后的红黑树。且容器都会在各自的类中添加一些独特的函数来解决各自适配的问题

map和set底层是改造后的红黑树,我们先来看看改造后的红黑树

和普通的红黑树不同的是,在根节点上再加了一个头结点,该结点不是真实的结点,只是一个辅助结点,是为了后面实现红黑树的迭代器而出现的。该header结点的父节点就是真实的根节点,其左孩子是这棵树的最左结点,其右孩子是这棵树的最右节点。

我们现在通过STL源码来简单剖析一下map和set中如何利用红黑树来实现各自不同的功能的

在map中有两个泛型参数,一个是Key,一个是T,也就是value。其中Key的别名是key_type,然后再将Key和T作为pair对象的一个泛型参数,将其的别名改为value_type。成员只有一棵树,该树就是红黑树,红黑树有两个泛型参数,一个是Key,一个是Value。Key就是红黑树中结点的值的类型,Value就是结点Key对应的Value值。结点中继承了一个结点类,其就相当有5个成员,颜色、父类指针、左孩子指针、右孩子指针、结点的值。

在set中只有一个泛型参数Key。在该容器中,由于使用红黑树底层必须提供两个泛型参数,set就将vkey当做value。此时传过去的红黑树的泛型参数就是相同的,都是Key。

所以两个容器的不同,起最关键的就是value类型不同,map容器底层中的红黑树的value是一个pair对象;而set容器中的的红黑树的value就是set中本身的key。在红黑树中做特殊处理,就可以获得他们的value。

以下代码就是对红黑树的一种改进,适配于map和set关联容器

#include <iostream>
#include <utility>
#include <algorithm>
using namespace std;

enum COLOR
{
	BLACK,
	RED
};

template <class V>
struct RBTreeNode
{
	RBTreeNode<V>* _parent; //父节点
	RBTreeNode<V>* _left; //左孩子
	RBTreeNode<V>* _right; //右孩子
	V _val;
	COLOR _color; //颜色

	RBTreeNode(const V& val = V())
		:_parent(nullptr)
		, _left(nullptr)
		, _right(nullptr)
		, _val(val)
		, _color(RED)
	{}
};

template <class K, class V, class KeyOfValue>
class RBTree
{
public:
	typedef RBTreeNode<V> Node;

	RBTree()
		:_header(new Node)
	{
		//创建空树
		_header->_left = _header->_right = _header;
	}

	bool insert(const V& val)
	{
		if (_header->_parent == nullptr)
		{
			Node* root = new Node(val);

			_header->_parent = root;
			root->_parent = _header;
			_header->_left = _header->_right = root;

			//根节点为黑色
			root->_color = BLACK;
			return true;
		}

		Node* cur = _header->_parent;
		Node* parent = nullptr;

		KeyOfValue kov;
		//1.寻找到要插入的结点的位置
		while (cur)
		{
			parent = cur;
			if (kov(cur->_val) == kov(val))
				return false;
			else if (kov(cur->_val) > kov(val))
				cur = cur->_left;
			else
				cur = cur->_right;
		}
		//2.创建节点
		cur = new Node(val);
		if (kov(parent->_val) > kov(cur->_val))
			parent->_left = cur;
		else
			parent->_right = cur;
		cur->_parent = parent;

		//3.颜色的修改或者结构的调整
		while (cur != _header->_parent && cur->_parent->_color == RED)//不为根且存在连续红色,则需要调整
		{
			parent = cur->_parent;
			Node* gfather = parent->_parent;

			if (gfather->_left == parent)
			{
				Node* uncle = gfather->_right;
				//情况1.uncle存在且为红
				if (uncle && uncle->_color == RED)
				{
					parent->_color = uncle->_color = BLACK;
					gfather->_color = RED;
					//向上追溯
					cur = gfather;
				}
				else
				{
					if (parent->_right == cur)//情况3
					{
						RotateL(parent);
						swap(cur, parent);
					}
					//情况2.uncle不存在或者uncle为黑
					RotateR(gfather);
					parent->_color = BLACK;
					gfather->_color = RED;
					break;
				}
			}
			else
			{
				Node* uncle = gfather->_left;
				if (uncle && uncle->_color == RED)
				{
					parent->_color = uncle->_color = BLACK;
					gfather->_color = RED;
					//向上追溯
					cur = gfather;
				}
				else
				{
					if (parent->_left == cur)
					{
						RotateR(parent);
						swap(cur, parent);
					}

					RotateL(gfather);
					parent->_color = BLACK;
					gfather->_color = RED;
					break;
				}
			}
		}

		//根节点为黑色
		_header->_parent->_color = BLACK;
		//更新头结点的左右指向
		_header->_left = leftMost();
		_header->_right = rightMost();
		return true;
	}

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;
		if (parent == _header->_parent)
		{
			_header->_parent = subR;
			parent->_parent = _header;
		}
		else
		{
			Node* gfather = parent->_parent;
			if (gfather->_left == parent)
				gfather->_left = subR;
			else
				gfather->_right = subR;
			subR->_parent = gfather;
		}
		subR->_left = parent;
		parent->_parent = subR;
	}

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		if (parent == _header->_parent)
		{
			_header->_parent = subL;
			subL->_parent = _header;
		}
		else
		{
			Node* gfather = parent->_parent;
			if (gfather->_left == parent)
				gfather->_left = subL;
			else
				gfather->_right = subL;
			subL->_parent = gfather;
		}
		subL->_right = parent;
		parent->_parent = subL;
	}

	Node* leftMost()
	{
		Node* cur = _header->_parent;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return cur;
	}

	Node* rightMost()
	{
		Node* cur = _header->_parent;
		while (cur && cur->_right)
		{
			cur = cur->_right;
		}
		return cur;
	}
private:
	Node* _header;
};

template<class K, class T>
class Map
{
	struct MapKeyOfValue
	{
		const K& operator()(const pair<K, T>& val)
		{
			return val.first;
		}
	};
public:
	bool insert(const pair<K, T>& kv)
	{
		return _rbt.insert(kv);
	}

	T& operator[](const K& key)
	{
		bool ret = _rbt.insert(make_pair(key, T()));
	}

private:
	typedef RBTree<K, pair<K, T>, MapKeyOfValue> rbt;
	rbt _rbt;
};

template <class K>
class Set
{
	struct SetKeyOfValue
	{
		const K& operator()(const K& val)
		{
			return val;
		}
	};

public:
	bool insert(const K& val)
	{
		return _rbt.insert(val);
	}

private:
	typedef RBTree<K, K, SetKeyOfValue> rbt;
	rbt _rbt;
};

迭代器

我们原生的Node结点迭代器是无法实现迭代器的普通操作的,所以我们必须要对结点进行另一层的封装,重载对应的操作运算符。在该类中只有一个成员变量,就是Node结点。

对迭代器的解引用,就是获得迭代器的val值

	V& operator*()
	{
		return _node->_val;
	}

对迭代器的箭头操作,就是获得迭代器值的地址

V* operator->()
	{
		return &_node->_val;
	}

两个迭代器的判等操作就是结点的地址是否相同

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

迭代器的++和- -是要符合中序遍历的有序遍历,下面我们来一起分析

迭代器的begin()位置应该是树中最小的结点,而树中最小的结点就是树的最左结点;迭代器的end()位置应该是树中最大的结点,而树中最大的结点就是树的最右结点

迭代器的++操作分为两种情况,第一种是存在右子树的情况,第二种是不存在右子树的情况

当存在右子树时,我们需要遍历到右子树的最左结点,然后访问该结点。例如我们现在的迭代器在8的位置,根据中序遍历的条件,我们应该访问的是10号结点,所以我们先走到8结点的右子树11号结点,然后遍历11的最左结点,该结点为10,也正是我们要访问的结点

		if (_node->_right)
		{
			//右子树的最左结点
			_node = _node->_right;
			while (_node->_left)
			{
				_node = _node->_left;
			}
		}

第二种情况是不存在右子树。当不存在右子树,我们需要向上回溯,中序遍历的遍历规则就是左孩子、根、右孩子。所以我们要判断当前节点是父节点的左孩子还是右孩子,如果是左孩子则表示父节点还未访问,此时需要访问父节点;如果是右孩子则表示父节点也访问过了,需要向上回溯,直至该结点不为父节点的右孩子。例如我们节点在5号结点的位置,需要判断该结点父节点6号结点的左边还是右边,此时5号结点再6号结点的左边,可以表示6号结点也未访问,此时将迭代器更新的父节点的位置即可。

当我们迭代器在7号结点时,7号结点时在6号结点的右边,此时需要向上回溯,让结点更新置父节点,然后父节再向上回溯至其父节点的位置,再判断当前节点的位置是否为父节点的右边,此时6号结点是8号结点的左边,表示8号结点还未访问,此时访问8号结点即可

但是我们需要考虑到一种特殊的情况,就是该树的根节点没有右孩子时

正常来说,我们对迭代器的++操作应该会移动到空的头结点的位置,但是我们再回溯的过程中会出现问题。此时因为it没有右结点,需要判断该结点是在父节点的左边还是右边,此时是在右边,就会向上回溯

这样子对迭代器的++是一个死操作,永远不会走到空的位置。所以我们需要再跟结点处做特殊的处理。当node结点的右孩子为自己的父亲时,就不用更新结点了,此时已经走到end()的了,如果还更新置parent结点,则该++操作就没有发生改变

我们现在进行测试

迭代器部分代码

template <class  V>
struct RBTreeIterator
{
	typedef RBTreeNode<V> Node;
	typedef RBTreeIterator<V> Self;
	Node* _node;

	RBTreeIterator(Node* node)
		:_node(node)
	{}

	//解引用
	V& operator*()
	{
		return _node->_val;
	}

	V* operator->()
	{
		return &_node->_val;
	}

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

	Self& operator++()
	{
		if (_node->_right) //存在右节点
		{
			//右子树的最左结点
			_node = _node->_right;
			while (_node->_left)
			{
				_node = _node->_left;
			}
		}
		else //不存在右节点
		{
			Node* parent = _node->_parent;
			while (_node == parent->_right)//回溯
			{
				_node = parent;
				parent = parent->_parent;
			}
			//特殊情况:根节点没有右孩子,则不需要更新结点
			if (_node->_right != parent)
				_node = parent;
		}
		return *this;
	}
};

红黑树添加迭代器代码

	typedef RBTreeIterator<V> iterator;

	iterator begin()
	{
		return iterator(_header->_left);
	}
	iterator end()
	{
		return iterator(_header);
	}

map中添加红黑树迭代器代码

	typedef typename RBTree<K, pair<K, T>, MapKeyOfValue>::iterator iterator;

	iterator begin()
	{
		return _rbt.begin();
	}
	iterator end()
	{
		return _rbt.end();
	}

测试结果:

这里直接给出迭代器- -的代码,原理和++类似

在迭代器类中

	Self& operator--()
	{
		if (_node->_left)
		{
			//右子树的最左结点
			_node = _node->_left;
			while (_node->_right)
			{
				_node = _node->_right;
			}
		}
		else
		{
			Node* parent = _node->_parent;
			while (_node == parent->_left)
			{
				_node = parent;
				parent = parent->_parent;
			}
			if (_node->_left != parent)
				_node = parent;
		}
		return *this;
	}

在红黑树类中添加反向迭代器用于测试

iterator rbegin()
	{
		return iterator(_header->_right);
	}

map中也添加反向迭代器

	iterator rbegin()
	{
		return _rbt.rbegin();
	}

测试:

方括号[]

插入的返回值是返回一个pair对象,所以我们在插入时的返回值都需要修改成一个pair对象,且pair对象的first为插入后的结点的迭代器,second为插入是否成功

	pair<iterator, bool> insert(const V& val)
	{
		if (_header->_parent == nullptr)
		{
			Node* root = new Node(val);

			_header->_parent = root;
			root->_parent = _header;
			_header->_left = _header->_right = root;

			//根节点为黑色
			root->_color = BLACK;
			return make_pair(iterator(root), true);
		}

		Node* cur = _header->_parent;
		Node* parent = nullptr;

		KeyOfValue kov;
		//1.寻找到要插入的结点的位置
		while (cur)
		{
			parent = cur;
			if (kov(cur->_val) == kov(val))
				return make_pair(iterator(cur), false);
			else if (kov(cur->_val) > kov(val))
				cur = cur->_left;
			else
				cur = cur->_right;
		}
		//2.创建节点
		cur = new Node(val);
		Node* node = cur;
		if (kov(parent->_val) > kov(cur->_val))
			parent->_left = cur;
		else
			parent->_right = cur;
		cur->_parent = parent;

		//3.颜色的修改或者结构的调整
		while (cur != _header->_parent && cur->_parent->_color == RED)//不为根且存在连续红色,则需要调整
		{
			parent = cur->_parent;
			Node* gfather = parent->_parent;

			if (gfather->_left == parent)
			{
				Node* uncle = gfather->_right;
				//情况1.uncle存在且为红
				if (uncle && uncle->_color == RED)
				{
					parent->_color = uncle->_color = BLACK;
					gfather->_color = RED;
					//向上追溯
					cur = gfather;
				}
				else
				{
					if (parent->_right == cur)//情况3
					{
						RotateL(parent);
						swap(cur, parent);
					}
					//情况2.uncle不存在或者uncle为黑
					RotateR(gfather);
					parent->_color = BLACK;
					gfather->_color = RED;
					break;
				}
			}
			else
			{
				Node* uncle = gfather->_left;
				if (uncle && uncle->_color == RED)
				{
					parent->_color = uncle->_color = BLACK;
					gfather->_color = RED;
					//向上追溯
					cur = gfather;
				}
				else
				{
					if (parent->_left == cur)
					{
						RotateR(parent);
						swap(cur, parent);
					}

					RotateL(gfather);
					parent->_color = BLACK;
					gfather->_color = RED;
					break;
				}
			}
		}

		//根节点为黑色
		_header->_parent->_color = BLACK;
		//更新头结点的左右指向
		_header->_left = leftMost();
		_header->_right = rightMost();
		//return true;
		return make_pair(iterator(node), true);
	}

在map中也要修改

	pair<iterator, bool> insert(const pair<K, T>& kv)
	{
		return _rbt.insert(kv);
	}

	T& operator[](const K& key)
	{
		pair<iterator, bool> ret = _rbt.insert(make_pair(key, T()));
		//ret.first 迭代器
		//迭代器-> pair<k,v>对象
		//pair<k,v>->second,获得v
		return ret.first->second;
	}

测试:

到此这篇关于C++ map的简单使用实现的文章就介绍到这了,更多相关C++ map 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++中vector和map的删除方法(推荐)

    1.连续内存序列容器(vector,string,deque) 序列容器的erase方法返回值是指向紧接在被删除元素之后的元素的有效迭代器,可以根据这个返回值来安全删除元素. vector<int> c; for(vector<int>::iterator it = c.begin(); it != c.end();) { if(need_delete()) it = c.erase(it); else ++it; } 2.关联容器(set,multiset,map,multima

  • 深入了解C++中map用法

    /************************************************************************ * * Map的特点: 1.存储Key-value对 * 2.支持快速查找,查找的复杂度基本是Log(N) * 3.快速插入,快速删除,快速修改记 * /************************************************************************/ #include <iostream> #inc

  • C++将CBitmap类中的图像保存到文件的方法

    本文实例讲述了C++将CBitmap类中的图像保存到文件的方法.分享给大家供大家参考.具体实现方法如下: 使用下面的代码,可以把CBitmap类中的图像保存到图像文件中.支持格式:BMP.JPG.GIF和PNG. void SaveBitmap(CString strFilePath, CBitmap Bitmap) { if ( Bitmap.m_hObject ) { CImage imgTemp; // CImage是MFC中的类. imgTemp.Attach(Bitmap.operat

  • c++ map,mutimap删除问题分析

    这样删除会导致程序内存覆盖等一系列不可预知的bug 复制代码 代码如下: map<string, string>::iterator iter; for ( iter = mm.begin();iter != mm.end(); iter ++ ) { if ( iter->second == something ) { mm.erase( iter ); } } 原因:当erase掉iter后,继续执行iter++,这个时候就乱套了 正确方法: 复制代码 代码如下: for (iter

  • C++ map 根据value找key的实现

    flyfish 测试所需头文件 #include <algorithm> #include <vector> #include <map> #include <string> 初始 std::map<int, std::string> t; t.insert(std::make_pair(1, "a")); t.insert(std::make_pair(2, "b")); t.insert(std::ma

  • C++中 map的基本操作

    1.map简介 map是一类关联式容器.它的特点是增加和删除节点对迭代器的影响很小,除了那个操作节点,对其他的节点都没有什么影响.对于迭代器来说,可以修改实值,而不能修改key. 2.map的功能 自动建立Key - value的对应.key 和 value可以是任意你需要的类型. 根据key值快速查找记录,查找的复杂度基本是Log(N),如果有1000个记录,最多查找10次,1,000,000个记录,最多查找20次. 快速插入Key - Value 记录. 快速删除记录 根据Key 修改val

  • js遍历map javaScript遍历map的简单实现

    js遍历map javaScript遍历map的简单实现 var map = { "name" : "华仔", "realname":"刘德华" }; for (var key in map) { console.log("map["+key+"]"+map[key]); } 这样会把map给遍历掉,显示在浏览器上的控制器里. 以上这篇js遍历map javaScript遍历map的简单

  • C++ map的简单使用实现

    map和set的底层都是通过红黑树来实现的,但并不是原生态的红黑树,而是经过改造后的红黑树.且容器都会在各自的类中添加一些独特的函数来解决各自适配的问题 map和set底层是改造后的红黑树,我们先来看看改造后的红黑树 和普通的红黑树不同的是,在根节点上再加了一个头结点,该结点不是真实的结点,只是一个辅助结点,是为了后面实现红黑树的迭代器而出现的.该header结点的父节点就是真实的根节点,其左孩子是这棵树的最左结点,其右孩子是这棵树的最右节点. 我们现在通过STL源码来简单剖析一下map和set

  • Java中的Map集合简单汇总解析

    Map接口简介 Map接口是一种双列集合,它的每个元素都包含一个键对象Key和值对象Value,键和值对象之间存在一种对应关系,称为映射.从Map集合中访问元素时,只要指定了Key,就能找到对应的Value, Map中的键必须是唯一的,不能重复,如果存储了相同的键,后存储的值会覆盖原有的值,简而言之就是键相同,值覆盖. Map常用方法 put(K key, V value) 添加数据,如果先前包含该键的映射,则替换旧值 get(Object key) 返回指定键所映射的值 Set<Map.Ent

  • ECMAScript6中Map/WeakMap详解

    JS的对象本身就是个键值结构,ES6为什么还需要加Map呢,它与普通的JS对象有何区别? 一.Map 1. Map构造器 先看Map的简单用法 // 字符串作为key, 和JS对象类似 var map = new Map() // set map.set('name', 'John') map.set('age', 29) // get map.get('name') // John map.get('age') // 29 这么对代码,看起来确实没有JS对象简洁 但Map的强大之处在于它的ke

  • 详解JavaScript中Hash Map映射结构的实现

    Hash Map通常在JavaScript中作为一个简单的来存储键值对的地方.然而,Object并不是一个真正的哈希映射,如果使用不当可能会带来潜在的问题.而且JavaScript可能不提供本地哈希映射(至少不是跨浏览器兼容的),有一个更好的声明对象属性的方法. Hash Map的简单实现: var hashMap = { Set : function(key,value){this[key] = value}, Get : function(key){return this[key]}, Co

  • jquery.map()方法的使用详解

    原型方法map跟each类似调用的是同名静态方法,只不过返回来的数据必须经过另一个原型方法pushStack方法处理之后才返回,源码如下: map: function( callback ) { return this.pushStack( jQuery.map(this, function( elem, i ) { return callback.call( elem, i, elem ); })); }, 本文主要就是分析静态map方法至于pushStack在下一篇随笔里面分析: 首先了解下

  • 手把手教你用Java实现一套简单的鉴权服务

    前言 时遇JavaEE作业,题目要求写个简单web登录程序,按照老师的意思是用servlet.jsp和jdbc完成.本着要么不做,要做就要做好的原则,我开始着手完成此次作业(其实也是写实训作业的用户鉴权部分),而之前写项目的时候也有相关经验,这次正好能派上用场. 一.何为鉴权服务 引用百度百科的话说 鉴权(authentication)是指验证用户是否拥有访问系统的权利. 鉴权包括两个方面: 用户鉴权,网络对用户进行鉴权,防止非法用户占用网络资源. 网络鉴权,用户对网络进行鉴权,防止用户接入了非

  • Mybatis的resultMap返回map问题

    目录 resultMap返回map问题 简单封装resultMap返回对象为map resultMap返回map问题 <resultMap type="Map" id="bankMaintainMap"> <result column="bank_name" property="bankName"/> <result column="maintain_time_interval"

  • 基于SpringBoot与Mybatis实现SpringMVC Web项目

    一.热身 一个现实的场景是:当我们开发一个Web工程时,架构师和开发工程师可能更关心项目技术结构上的设计.而几乎所有结构良好的软件(项目)都使用了分层设计.分层设计是将项目按技术职能分为几个内聚的部分,从而将技术或接口的实现细节隐藏起来. 从另一个角度上来看,结构上的分层往往也能促进了技术人员的分工,可以使开发人员更专注于某一层业务与功能的实现,比如前端工程师只关心页面的展示与交互效果(例如专注于HTML,JS等),而后端工程师只关心数据和业务逻辑的处理(专注于Java,Mysql等).两者之间

  • MyBatis输入映射和输出映射实例详解

    什么是 MyBatis ? MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录. 我们知道,mapper.xml是我们配置操作数据库的sql语句的地方.其中每个sql语句对应着一个方法,每个方法都有自己的

随机推荐