红黑树封装 map/set 及其迭代器(C++)

news2025/7/14 15:38:30

目录

一、map/set 的封装

1.1 封装思路

1.2 红黑树节点调整

1.3 map 和 set 的定义

1.4 仿函数 KeyOfValue

1.5 map/set 的插入

二、map/set 迭代器实现

2.1 迭代器的定义

2.2 解引用运算符重载

2.3 成员访问运算符重载

2.4 (不)等于运算符重载

2.5 begin() 与 end()

2.6 ++ 运算符重载

2.7 -- 运算符重载

2.8 [ ]下标访问运算符重载

三、源代码+测试用例

3.1 map/set 

3.2 迭代器

3.3 测试用例

3.4 红黑树


一、map/set 的封装

在实现了红黑树的部分功能后,我们可以便可以将红黑树作为底层结构来封装map 和 set ,其中map是 K-Value 模型 ,而 set 是 Key 模型。

我们接下来将使用模板、仿函数用一棵红黑树实现 map和set。

1.1 封装思路

因为 map 存储的是 pair ,而 set 存储的是 Key ,所以其解决的根本方向就是:

如果是 map,红黑树中就按照 pair 的 K 进行比较,从而插入;

如果是 set,红黑树中就按照 Key 值进行比较,进而插入。

让 map / set 主动传出待比较的数据,红黑树只用根据数据间关系进行插入即可,不用在乎待比较的数据是何种结构。

1.2 红黑树节点调整

上文我们实现的红黑树是按照键值对的方式进行存储的,而接下来我们要同时封装 map/set,故不能直接定死存储的结构,所以我们在此进行修改。

将原来的 kv 模型改为 data 模型,data 即是比较的数据内容。

注意,将 Kv模型改为 data后,插入与查找中比较的代码都要进行更新,稍后会讲解。

1.3 map 和 set 的定义

map 和 set 底层都使用的红黑树,所以我们 map/set的功能就是调用红黑树的成员函数即可。

template<class K, class V>
class Map
{
private:
	RBTree<K, pair<K, V>> _t;
};

template<class K>
class Set
{
private:
	RBTree<K,K> _t;
};

因为 Map 有两个模板参数,而 Set 只有一个模板参数。所以当我们使用的一个红黑树实现时,要进行匹配处理。即使 Set 是一个模板参数,在调用红黑树时也要传入两个模板参数。因为第一个模板参数是匹配 Map 满足红黑树的两个模板参数,而第二个模板参数是为了让底层红黑树拿到比较的数据。

为什么 Map 除了传入 pair 外,第一个参数直接传入 K,为什么不能省略?

因为 Find 的存在,map中 Find 函数是直接按 pair 中的 K 进行查找的,所以要额外设置该参数。

1.4 仿函数 KeyOfValue

接下来我们就要将数据取出供红黑树比较了,如果是 map,就按 pair 中的 K去比较,如果是 set,就按 Key 比较。

为此我们可以在 map 和 set 内部定义一个仿函数将其数据取出。

template<class K, class V>
class Map
{
    //Map-keyofvalue 仿函数
	struct MapKeyOfvalue
	{
		const K& operator()(const std::pair<K, V>& kv)
		{
			return kv.first;
		}
	};
private:
	RBTree<K, pair<K, V>> _t;
};

template<class K>
class Set
{
    //Set-keyofvalue 仿函数
    struct SetKeyOfvalue
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
private:
	RBTree<K,K> _t;
};

然后我们将其仿函数也作为模板,传入红黑树中,对应的,红黑树要添加一个模板参数来接收该仿函数。

改动代码如下:

 改动这些之后,我们便要将红黑树中比较数据大小的地方进行修改

用仿函数将数据取出,然后进行比较:

//根据模板参数创建仿函数
KeyOfvalue kovalue;
if (!_root)
{
	_root = new Node(data);
	_root->_col = BLACK;
	return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
    //比较处————进行改动
	if (kovalue(cur->_data) > kovalue(data))
	{
		parent = cur;
		cur = cur->_left;
	}
    //比较处————进行改动
	else if (kovalue(cur->_data) < kovalue(data))
	{
		parent = cur;
		cur = cur->_right;
	}
	else
	{
		return false;
	}
}
//创建新节点,使用data进行构造
cur = new Node(data);
//比较处————进行改动
if (kovalue(parent->_data) > kovalue(data))
{
	parent->_left = cur;
}
else
{
	parent->_right = cur;
}
cur->_parent = parent;

这样,红黑树便可以适配 map/set 的插入了。

1.5 map/set 的插入

接下来 map/set 的插入直接套用红黑树的即可。

代码如下:

//map的插入,插入pair
bool insert(const pair<K, V>& kv)
{
	return _t.Insert(kv);
}

//set的插入,插入key
bool insert(const K& key)
{
	return _t.Insert(key);
}

接下来进行测试,看我们map/set能否正常的插入数据。

二、map/set 迭代器实现

2.1 迭代器的定义

//			 节点数据	引用/const引用  指针/const指针
template <class T,class Ref,class Ptr>
struct __RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __RBTreeIterator<T, Ref, Ptr> self;
	Node* _node;
public:
	__RBTreeIterator(Node* node)
		:_node(node)
	{}
}

首先,我们要明确,其实 map/set 只是一层套壳,其中的功能都是由红黑树实现后,再封装到map/set中供我们使用,迭代器也不例外。

2.2 解引用运算符重载

解引用即返回该节点的存储的数据,主要用于 set 中,返回该数据的引用。

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

2.3 成员访问运算符重载

成员访问操作符即返回该节点的地址,主要用于 map 中,方便访问 pair 中的first以及second。

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

2.4 (不)等于运算符重载

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


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

2.5 begin() 与 end()

迭代器常用成员函数begin()与end(),其中begin()对应红黑树的最左节点,end()对应最后一个节点的下一个节点,即nullptr(为了简化,并未设置哨兵节点实现将其完美实现)


iterator begin()
{
	Node* left = _root;
	while (left && left->_left)
	{
		left = left->_left;
	}
	return iterator(left);
}

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

如果 map/set 中想使用红黑树中的迭代器,我们需要在 map/set 中进行声明。

声明如下:

如果想取一个类模板中的一个类型,要使用 typedname 进行声明。
告诉编译器这是一个类型,并不是一个静态变量

//如果想取一个类模板中的一个类型,要使用 typedname 进行声明。
//告诉编译器这是一个类型,并不是一个静态变量
typedef typename RBTree<K, pair<K, V>, MapKeyOfvalue>::iterator iterator;

注意:typename受限定符限制,尽量放在public下

2.6 ++ 运算符重载

首先我们需要明确,迭代器++是让当前迭代器指向红黑树中序遍历的下一个节点。

以下图的35节点为例。

  • 当迭代器指向 35 时,进行 ++,指向右子树最左节点,即 40。
  • 当迭代器指向 40 时,进行 ++,右子树为空,指向父节点,即 45。
  • 当迭代器指向 45 时,进行 ++,指向右子树最左节点,即 48。
  • 当迭代器指向 48 时,进行 ++,指向未遍历的父节点,即 50。

分析上面的情况,发现迭代器 ++ 始终围绕着右子树是否存在进行。

现在我们将其抽象化,分析其规律。

  1. 右子树不为空,进行 ++ 则是指向右子树中序的第一个(最左节点)。
  2. 右子树为空,++ 找孩子不是父亲右节点的祖先。

代码实现:


self& operator++()
{
	//如果右子树存在
	if (_node->_right)
	{
		Node* left = _node->_right;
		//则寻找右子树的最左节点
		while (left->_left)
		{
			left = left->_left;
		}
		_node = left;
	}
	//如果右子树不存在
	else
	{
        //找孩子不是父亲右节点的节点
		Node* parent = _node->_parent;
		Node* cur = _node;=
		while (cur == parent->_right)
		{
			cur = cur->_parent;
			parent = parent->_parent;
			//防止最后一个节点寻找祖先导致程序崩溃
			if (parent == nullptr)
			{
				break;
			}
		}
		_node = parent;
	}
	return *this;
}

需要注意,当 ++ 到最后一个节点的时候。有可能在寻找非父亲右节点的祖先时,父节点一路走到 nullptr 的情况,如图:

 

所以在每次 parent 更新时都进行一次判断,即可。

测试:

这里顺序把后置 ++ 的代码实现一下,直接套用前置 ++ 即可。

//迭代器后置++
self operator++(int)
{
	self it_temp(_node);
	++(*this);
	return it_temp;
}

2.7 -- 运算符重载

有了前面++的模拟实现,实现 --就是反着遍历即可。

  1. 左子树不为空,进行 -- 则是指向左子树中序的最后一个(最右节点)。
  2. 左子树为空,-- 找孩子不是父亲左节点的祖先。

代码如下:

self& operator--()
{
	//如果左子树存在
	if (_node->left)
	{
		//找左子树的最右节点
		Node* right = _node->_left;
		while (right->_right)
		{
			right = right->_right;
		}
		_node = rihgt;
	}
	//如果左子树不存在
	else
	{
		//找孩子不是父亲左节点的节点
		Node* parent = _node->parent;
		Node* cur = _node;
		while (parent->_left == cur)
		{
			cur = cur->_parent;
			parent = parent->_parent;
			if (parent == nullptr)
			{
				break;
			}
		}
		_node = parent;
	}
	return *this;
}

2.8 [ ]下标访问运算符重载

我们来看 map 的 [ ] 下标访问操作符,其中 [ ]返回的是mapped_type(pair) 类型。

我们便要对 map 中 insert 的返回值做出修改:

 注意,set 中的 insert 也是返回 pair,虽然很反常,但是官方库中确实是这样书写的。

因为只有 set 没有 [ ] 运算符重载,所以我们 set 中不必提供该函数,只用在 map 中提供即可。

首先,我们向 map 中 insert 数据 pair;pair的第一个参数为用户传入的 key 值,第二个参数则是用户声明的第二个模板参数的默认构造函数(如果是 int,则调用 int的构造函数,如果是 string ,则默认构造 string)。

pair<iterator, bool> result = insert(make_pair(key, V()));

然后我们返回迭代器指向的 pair 数据中的second。

//result.first取出迭代器,使用->运算符重载取出data地址,访问second并返回
return result.first->second;

完整的函数书写如下:

V& operator[](const K& key)
{
	pair<iterator, bool> result = insert(make_pair(key, V()));
	//如果存在,则插入失败
	//如果不存在,则插入数据
	//无论是否存在,都返回 second;
	return result.first->second;
}

接下来我们要对红黑树的 Insert 的返回值处进行改动,进而契合 map 的 pair 数据类型。改动有三处,这里贴图大家观察即可。

测试:


三、源代码+测试用例

3.1 map/set 

namespace brant
{
	template<class K,class V>
	class Map
	{
	public:
		struct MapKeyOfvalue
		{
			const K& operator()(const std::pair<K, V>& kv)
			{
				return kv.first;
			}
		};
		//外层要想使用红黑树的iterator,
		typedef typename RBTree<K, pair<K, V>, MapKeyOfvalue>::iterator iterator;
		iterator begin()
		{
			return _t.begin();
		}

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

		pair<iterator, bool> insert(const pair<K, V>& kv)
		{
			return _t.Insert(kv);
		}
		void InOrder()
		{
			_t.Inorder();
		}
		V& operator[](const K& key)
		{
			pair<iterator, bool> result = insert(make_pair(key, V()));
			return result.first->second;
		}
	private:
		RBTree<K, pair<K,V>, MapKeyOfvalue> _t;
	};

    template<class K>
	class Set
	{
		struct SetKeyOfvalue
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename RBTree<K, K, SetKeyOfvalue>::iterator iterator;
		iterator begin()
		{
			return _t.begin();
		}

		iterator end()
		{
			return _t.end();
		}
		
		pair<iterator, bool> insert(const K& key)
		{
			return _t.Insert(key);
		}

		void InOrder()
		{
			_t.Inorder();
		}
	private:
		RBTree<K, K, SetKeyOfvalue> _t;
	};
}

3.2 迭代器

enum Color { RED, BLACK };
template <class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;	
	RBTreeNode<T>* _right;	
	RBTreeNode<T>* _parent;	
	T _data;
	Color _col;					

	RBTreeNode(const T& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _col(RED)              
	{}
};

//			 节点数据	引用/const引用  指针/const指针
template <class T,class Ref,class Ptr>
struct __RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __RBTreeIterator<T, Ref, Ptr> self;
	typedef __RBTreeIterator<T, Ref, Ptr> iterator;
	Node* _node;
	__RBTreeIterator(Node* node)
		:_node(node)
	{}

	Ref operator*()
	{
		return _node->_data;
	}
	//map常使用operator ->  返回地址,然后通过——> 访问
	Ptr operator->()
	{
		return &(_node->_data);
	}

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

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

	iterator begin()
	{
		Node* left = _node;
		while (left && left->_left)
		{
			left = left->_left;
		}
		return iterator(left);
	}

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

	self& operator++()
	{
		//如果右子树存在
		if (_node->_right)
		{
			Node* left = _node->_right;
			//则寻找右子树的最左节点
			while (left->_left)
			{
				left = left->_left;
			}
			_node = left;
		}
		//如果右子树不存在
		else
		{
			//找孩子不是父亲右的节点
			Node* parent = _node->_parent;
			Node* cur = _node;
			while (cur == parent->_right)
			{
				cur = cur->_parent;
				parent = parent->_parent;
				//防止最后一个节点寻找祖先导致程序崩溃
				if (parent == nullptr)
				{
					break;
				}
			}
			_node = parent;
		}
		return *this;
	}
	self operator++(int)
	{
		self it_temp(_node);
		++(*this);
		return it_temp;
	}

	self& operator--()
	{
		if (_node->left)
		{
			Node* right = _node->_left;
			while (right->_right)
			{
				right = right->_right;
			}
			_node = right;
		}
		else
		{
			Node* parent = _node->parent;
			Node* cur = _node;
			while (parent->_left == cur)
			{
				cur = cur->_parent;
				parent = parent->_parent;
				if (parent == nullptr)
				{
					break;
				}
			}
			_node = parent;
		}
		return *this;
	}
};

3.3 测试用例

void test_iterator()
{
	brant::Set<int> s;
	s.insert(1);
	s.insert(2);
	s.insert(3);
	s.insert(4);
	s.insert(5);
	brant::Set<int>::iterator it_set = s.begin();
	cout << "set:" << endl;
	while (it_set != s.end())
	{
		cout << *it_set << " ";
		it_set++;
	}
	cout << endl;

	brant::Map<int, int> m;
	m.insert(make_pair(1, 100));
	m.insert(make_pair(2, 200));
	m.insert(make_pair(3, 300));
	m.insert(make_pair(4, 400));
	m.insert(make_pair(5, 500));
	brant::Map<int, int>::iterator it_map = m.begin();
	cout << "map:" << endl;

	while (it_map != m.end())
	{
		cout << (*it_map).first 
			<< ":" << (*it_map).second << endl;
		++it_map;
	}
}

void test_map_()
{
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜",
		"苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };

	brant::Map<string, int> countMap;
	for (auto& str : arr)
	{
		countMap[str]++;
	}

	brant::Map<string, int>::iterator it = countMap.begin();
	while (it != countMap.end())
	{
		cout << it->first << ":" << it->second << endl;
		++it;
	}
	cout << endl; 
	for (auto& kv : countMap)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
}

3.4 红黑树

只截取了改动和增添的部分。原来的红黑树在这.


template <class K, class T,class KeyOfvalue>
class RBTree
{
	typedef RBTreeNode<T> Node;

public:
	typedef __RBTreeIterator<T, T&, T*> iterator;
	typedef __RBTreeIterator<T, const T&, const T*> const_iteraotr;
	
	iterator begin()
	{
		//找最左节点
		Node* left = _root;
		while (left && left->_left)
		{
			left = left->_left;
		}
		//用一个节点构造迭代器
		return iterator(left);
	}

	iterator end()
	{
		//因为没有哨兵节点,直接使用空进行返回
		return iterator(nullptr);
	}
pair<iterator,bool>  Insert(const T& data)
{
	KeyOfvalue kovalue;
	if (!_root)
	{
		_root = new Node(data);
		_root->_col = BLACK;
		return make_pair(iterator(_root),true);
	}
	Node* parent = nullptr;
	Node* cur = _root;
	//找插入的位置
	while (cur)
	{
		if (kovalue(cur->_data) > kovalue(data))
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (kovalue(cur->_data) < kovalue(data))
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			return make_pair(iterator(cur),false);
		}
	}
	cur = new Node(data);
	//插入成功,返回新增节点 
	//注意:cur 可能会改变(情况1变色处理,cur指向可能会改变)
	//使用newnode记录新创建节点的地址
	Node* newnode = cur;

	if (kovalue(parent->_data) > kovalue(data))
	{
		parent->_left = cur;
	}
	else
	{
		parent->_right = cur;
	}
	cur->_parent = parent;
	while (parent && parent->_col == RED)
	{
		Node* grandfater = parent->_parent;
		assert(grandfater);
		assert(grandfater->_col == BLACK);
		if (grandfater->_left == parent)
		{
			Node* uncle = grandfater->_right;
			if (uncle && uncle->_col == RED)
			{
				parent->_col = uncle->_col = BLACK;
				grandfater->_col = RED;
				cur = grandfater;
				parent = cur->_parent;
			}
			else
			{
				if (cur == parent->_left)
				{
					RotateR(grandfater);
					parent->_col = BLACK;
					grandfater->_col = RED;
				}
				else
				{
					RotateL(parent);
					RotateR(grandfater);
					cur->_col = BLACK;
					grandfater->_col = RED;
				}
				break;
			}
		}
		else
		{
			Node* uncle = grandfater->_left;
			if (uncle && uncle->_col == RED)
			{
				parent->_col = uncle->_col = BLACK;
				grandfater->_col = RED;
				cur = grandfater;
				parent = cur->_parent;
			}
			else
			{
				if (cur == parent->_right)
				{
					RotateL(grandfater);
					parent->_col = BLACK;
					grandfater->_col = RED;
				}
				else
				{
					RotateR(parent);
					RotateL(grandfater);
					cur->_col = BLACK;
					grandfater->_col = RED;
				}
				break;
			}
		}
	}
	_root->_col = BLACK;
	return make_pair(iterator(newnode), true);
}
};

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[附源码]java毕业设计智慧农业销售平台

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

认知电子战 | 电子战简介

1 电子战的基本概念 电子战(Electronic Warfare,EW)也叫做电子对抗(Electronic Counter Measures,ECM) 简洁定义: 电子战是为确保我方使用电磁频谱,同时阻止敌方使用电磁频谱所采取的战术与技术 完善定义: 利用电磁能、定向能、水声能等的技术手段,确定、扰乱、削弱…

Linux kprobe原理

文章目录前言一、Kprobes and Return Probes二、How Does a Kprobe Work三、Changing Execution Path四、Return Probes4.1 How Does a Return Probe Work4.2 Kretprobe entry-handler五、How Does Jump Optimization Work5.1 Init a Kprobe5.2 Safety Check5.3 Preparing Deto…

Ubuntu配置全局系统代理(常用工具配置)

Ubuntu配置全局系统代理&#xff08;常用工具&#xff09;问题描述解决方法配置系统代理终端部分配置配置apt代理配置curl,wget,pip代理git相关代理的设置配置docker代理问题描述 公司电脑网络规则做了限制&#xff0c;主机没办法通外网&#xff0c;只能通过代理连接外网。主机…

普惠联接,让人类诗意地栖居在大地上

弗里德里希荷尔德林为世间留下了一句精彩绝伦的诗句&#xff1a;“人生在世&#xff0c;成绩斐然&#xff0c;却还依然诗意地栖居在大地上。”“人&#xff0c;诗意地栖居”&#xff0c;这一命题启发了此后众多思想家、社会学家的缪斯。人之为人&#xff0c;应该如何成为存在的…

Web APIs——BOM

下面从以下7个方面介绍BOM&#xff1a; BOM概述window对象的常见事件定时器JS执行机制location对象navigator对象history对象 1. 什么是BOM&#xff1f; BOM&#xff08;Browser Object Model&#xff09;即浏览器对象模型&#xff0c;它提供了独立于内容而与浏览器窗口进行交…

谷歌FLAN-T5作者亲讲:5400亿参数,1800个任务,如何实现大语言模型“自我改进”...

2021年&#xff0c;谷歌的研究者们提出了FLAN大模型&#xff0c;其基于Instruction Tuning的方式&#xff0c;极大地提升了大语言模型的理解能力。同时&#xff0c;各种Prompting方法的涌现预示着针对大模型的下游微调将成为研究领域关注的重点。近日&#xff0c;谷歌研究者们再…

电影主题HTM5网页设计作业成品——爱影评在线电影(10页面)使用dreamweaver制作采用DIV+CSS进行布局

HTML实例网页代码, 本实例适合于初学HTML的同学。该实例里面有设置了css的样式设置&#xff0c;有div的样式格局&#xff0c;这个实例比较全面&#xff0c;有助于同学的学习,本文将介绍如何通过从头开始设计个人网站并将其转换为代码的过程来实践设计。 文章目录一、网页介绍一…

企业知识库有什么价值?

图为简道云企业知识库让雇员对知识管理深感激动的最差课堂教学 简道云知识库&#xff1a;http://s.fanruan.com/rgdrd 我们可以借助创建知识程序库应用领域知识管理方法论&#xff0c;来同时实现重新分配知识。 最终目标是让每一人都有参与 KM 的积极主动性&#xff0c; 以期…

DOAW咖啡品牌是如何生意增长的?

上海人爱喝咖啡&#xff0c;早已不是什么秘密&#xff0c;上海是中国咖啡市场的战略要地&#xff0c;以行业而论&#xff0c;咖啡赛道有星巴克、瑞幸等大品牌&#xff0c;同时也有不少新品牌不断冲击线下线上渠道&#xff0c;使这个行业内卷加速。 DOAW虽然成立时间短&#xff…

【深度学习】yolov5 tag7.0 实例分割 从0到1的体会,从模型训练,到量化完成

这里记录下yolov5 tag7.0的实例分割&#xff0c;因为也用过paddle家族的实例分割&#xff0c;能够训练出来&#xff0c;但是开放restiful api时遇到点小问题&#xff0c;还是yolov爽啊&#xff01;&#xff01;通过这篇博文&#xff0c;您可以一步步的搭建自己的分割网络。 文章…

python+SQL sever+thinter学生宿舍管理系统

pythonSQL severthinter学生宿舍管理系统一、系统介绍二、功能展示1.主页2.用户登陆3.学生信息三、数据库四、其它1.其他系统实现一、系统介绍 系统主要功能学生信息管理、管理员信息管理、核酸信息管理等。 二、功能展示 1.主页 2.用户登陆 3.学生信息 三、数据库 /*Navic…

汉罗塔汉洛塔c++,看不懂ni打我

汉罗塔汉洛塔c,看不懂ni打我 大哥大姐手下留情 别打我&#xff01;&#xff01;&#xff01; [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GC9f81Hm-1669391569678)(C:\Users\ASUS\Desktop\汉罗塔 . Input第一行为一个正整数nOutput若干行&#…

vue3新一代状态管理器 — pinia的学习与使用

目录一、vuex与pinia特性二、使用pinia2.1 安装pinia2.2 项目引入2.3 创建store2.4 使用storegetteraction2.5 修改state1、直接修改2、使用$patch3、使用actions2.6 storeToRefs一、vuex与pinia Pinia 最初是为了探索 Vuex 的下一次迭代会是什么样子&#xff0c;结合了 Vuex 5…

4. 云计算中的存储基础知识

4.1 物理磁盘类型和存储设备类型 虚拟化中的存储架构 物理磁盘类型 - SATA盘 SATA的全称Serial Advanced Technology Attachment&#xff0c;SATA&#xff08;Serial ATA&#xff09;口的硬盘又叫串口硬盘。SATA采用串行连接方式&#xff0c;串行ATA总线使用嵌入式时钟信号&a…

kubernetes组件 Controller manager深刻认知

kubernetes组件 Controller manager深刻认知Controller manager常见的controllerController manager的工作流程informer的内部机制控制器的协同工作原理ReplicaSet controller 是如何被管理的&#xff1f;statefuleset 和deployment controller是如何控制滚动升级的statefulese…

学习基于html和JavaScript的滑动图片拼图验证源码

之前的文章《使用C#中的GDI功能实现图片填充验证方式》中介绍过使用GDI在Winform中实现图片拼图验证的思路&#xff0c;学了html中的canvas使用方式后&#xff0c;也可以照搬思路实现&#xff0c;只不过没有在Winform中方便。但是在网上看了不少基于html和JavaScript的滑动图片…

【数据结构】栈的模拟和使用理解

学习目录栈(Stack)栈的概念栈的使用栈相关的应用场景栈的模拟实现中缀表达式 转 后缀表达式栈(Stack) 栈的概念 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作. 进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底&a…

关于vba代码运行时错误1004 应用程序定义或对象定义错误问题

一、错误描述 将Excel所有工作表&#xff0c;汇总到一个工作表中&#xff1a; 在thisworkbook中运行如下&#xff1a; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 Su…

大数据面试题(五):Hadoop优化核心高频面试题

文章目录 Hadoop优化核心高频面试题 一、Mapreduce 跑的慢的原因&#xff1f; 1、计算机性能 2、I/O 操作优化 二、Mapreduce优化方法 1、数据输入 2、map阶段 3、reduce阶段 4、IO传输 5、数据倾斜问题 6、常用的调优参数 三、HDFS小文件优化方法 1、HDFS小文件…