返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++ map的简单使用实现
  • 888
分享到

C++ map的简单使用实现

2024-04-02 19:04:59 888人浏览 八月长安
摘要

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

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 内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: C++ map的简单使用实现

本文链接: https://lsjlt.com/news/126738.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • C++ map的简单使用实现
    map和set的底层都是通过红黑树来实现的,但并不是原生态的红黑树,而是经过改造后的红黑树。且容器都会在各自的类中添加一些独特的函数来解决各自适配的问题 map和set底层是改造后的...
    99+
    2024-04-02
  • C++list-map链表与映射表的简单使用
    目录list 链表map 映射表list 链表 链表是由节点之间通过指针连接而成的链式结构存储结构体,对于链表,C++标准库中已经提供了封装好的链表了。 require: #incl...
    99+
    2023-05-19
    C++ list 链表 C++ map映射表
  • C++ vector的简单实现
    目录向量成员函数cpp总结向量 向量是序列容器,表示可以更改大小的数组。 就像数组一样,向量对其元素使用连续的存储位置,这意味着也可以使用指向其元素的常规指针上的偏移量来访问其元素,...
    99+
    2024-04-02
  • 使用c++怎么实现简单随机数
    使用c++怎么实现简单随机数?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。c++简单随机数#include<iostream>#include<...
    99+
    2023-06-15
  • C#DataGridView使用BindingNavigator实现简单分页功能
    要使用BindingNavigator实现简单的分页功能,可以按照以下步骤进行操作:1. 在窗体上添加一个DataGridView控...
    99+
    2023-09-15
    C#
  • C++ EnterCriticalSection简单使用
    目录EnterCriticalSection作用一、首先是它的使用步骤:二、示例代码:EnterCriticalSection作用 用途主要是在多线程中,当开启多线程中,要控制函数...
    99+
    2024-04-02
  • 怎么使用C#实现简单的计算器功能
    这篇文章主要介绍怎么使用C#实现简单的计算器功能,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!环境:VS2010及以上版本建立个Window窗体应用在工具箱里拖出两个TextBox,第一个放上面,第二个放下面 。主要...
    99+
    2023-06-29
  • 使用c++编程实现简单的打字小游戏
    你是否对键盘熟悉? “qwertyuiopasdfghjklzxcvbnm”是否已经印在你的脑海里? NO?     &nb...
    99+
    2024-04-02
  • C++内存池的简单实现
    目录一、内存池基础知识1、什么是内存池1.1 池化技术1.2 内存池2、内存池的作用2.1 效率问题2.2 内存碎片3、内存池技术的演进二、简易内存池原理1、整体设计1.1 内存池结...
    99+
    2024-04-02
  • C++简单实现shared_ptr的代码
    一、一些说明 1.智能指针用于资源管理,为了保证资源的操作得到顺利的执行防止资源泄露,因此大多数实现都以noexcept在参数列表后声明为不抛出异常。 2.对于有些明确不需要更改调用...
    99+
    2024-04-02
  • C#队列的简单使用
    队列的特性很简答,就是先进先出,一般利用数组来实现。 实现队列自然要实现几个函数:入队,出队,判断队满,判断队空,获得队头,队尾。 实现队列的关键在于队头指针和队尾指针的设置: 假设...
    99+
    2024-04-02
  • JNI实现最简单的JAVA调用C/C++代码
    JNI,是Java Native Interface的简称,中文是“Java本地调用”。通过这种技术可以做到以下两点: Java程序中的函数可以调用Native语言写的函数,Native一般指的是C/C++编写的函数。 Native程序...
    99+
    2023-05-31
    java jni ava
  • Python使用socket实现简单的文
           因为工作需要,要在两台设备之间进行压力测试。即A设备不断往B设备发送文件,B设备接收文件后校验文件是否正确接收。       用Python的socket模块写了简单的Server和Client脚本。Server负责监听端口,...
    99+
    2023-01-31
    简单 Python socket
  • Java中 Map转List 、 List转Map 简单好用
    1. Map转List 1.1 将Map的key转换为List public void testMapToList(){ // 创建一个Map Map map = new HashMap(); ...
    99+
    2023-08-30
    java list
  • 用C语言实现简单的三子棋
    三子棋代码的实现需要一个简单的思路做指引,所以我们先来做一下思路的整理,代码的实现主要分为以下几个步骤: 1.初始化数组2.显示数组3.电脑走4.玩家走5.判断输赢 所以,先写出源文...
    99+
    2024-04-02
  • C++中map和set的简介及使用详解
    目录关联式容器键值对setset的介绍set的使用multisetmapmap的介绍map的使用map构造map的插入map的[ ]运算符重载multiset关联式容器 关联式容器包...
    99+
    2024-04-02
  • 【C/C++】ghost ddl脚本简单实现
    目的:本篇是自己用C++实现的ddl的简单脚本(改写自自己的shell,但是还有一部分没完成),用来锻炼自己写C++的能力头文件exec_ddl.h```#include <regex>#include <cstdlib&...
    99+
    2023-06-03
  • 使用jquery 简单实现下拉菜单
    可以通过以下方式使用 jQuery 实现简单的下拉菜单:首先,需要在 HTML 文件中引入 jQuery 库和一个 CSS 文件来定...
    99+
    2023-08-17
    jQuery
  • 如何使用rust实现简单的单链表
    目录前言1.链表节点的定义2.链表的定义3.实现从链表头部插入节点的prepend方法4.为链表实现Display trait定制链表的打印显示5.为链表实现翻转链表功能的rever...
    99+
    2024-04-02
  • C#实现简单的聊天窗体
    本文实例为大家分享了C#实现简单的聊天窗体的具体代码,供大家参考,具体内容如下 一、要使用(学习)到的知识点 1、textBox控件 (1)功能:允许用户输入文本,并提供多行编辑和密...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作