【C++进阶:map和set】

news2024/11/17 13:58:18

本节涉及到的所有代码见以下链接,欢迎参考指正!

​​​​​​​

practice: 课程代码练习 - Gitee.comhttps://gitee.com/ace-zhe/practice/tree/master/map%E5%92%8Cset

​​​​​​​

关联式容器

在C++初阶阶段,已经学习并总了STL中的部分容器,比如:vector、list、deque、forward_list等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。那什么是关联式容器?它与序列式容器有什么区别?关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。

键值对:

用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代 表键值,value表示与key对应的信息。比如:现在要建立一个英汉互译的字典,那该字典中必然有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该应该单词,在词典中就可以找到与其对应的中文含义。
SGI-STL中定义一个pair结构来定义键值对,如下:
template <class T1, class T2>
struct pair
{
 typedef T1 first_type;
 typedef T2 second_type;
 T1 first;
 T2 second;
  pair(): first(T1()), second(T2())
   {}
  pair(const T1& a, const T2& b): first(a), second(b)
   {}
};

树形结构的关联式容器:

根据应用场景的不同,STL总共实现了两种不同结构的管理式容器:树型结构与哈希结构。树型结
构的关联式容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列。下面一依次介绍每一 个器。

set

翻译:
1. set是按照一定次序存储元素的容器
2. 在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。
set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。
3. 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行排序。
4. set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对
子集进行直接迭代。
5. set在底层是用二叉搜索树(红黑树)实现的。
注意:
1. 与map/multimap不同,map/multimap中存储的是真正的键值对<key, value>,set中只放
value,但在底层实际存放的是由<value, value>构成的键值对。
2. set中插入元素时,只需要插入value即可,不需要构造键值对。
3. set中的元素不可以重复(因此可以使用set进行去重),其功能理解为排序+去重
4. 使用set的迭代器遍历set中的元素,可以得到有序序列
5. set中的元素默认按照小于来比较
6. set中查找某个元素,时间复杂度为:log_2 n
7. set中的元素不允许修改,修改之后将会影响原搜索二叉树的结构

set的用法:

有之前使用容器的基础,上手使用set其实并不困难,这里就不详细说明,有需要可以阅读set相关文档,链接如下:

https://cplusplus.com/reference/set/set/?kw=sethttps://cplusplus.com/reference/set/set/?kw=set这里给出几个较为重要的用法:

set迭代器的使用:

set的迭代器是双向迭代器,利用迭代器可实现元素的遍历,也可利用范围for(本质一样)如下:

void set_test1()
{
	std::set<int> s1;
	s1.insert(2);
	s1.insert(4);
	s1.insert(3);
	s1.insert(6);
	s1.insert(6);
	s1.insert(1);
	std::set<int>::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << endl;
		it++;
//搜索树key值不可变,即不能对*it进行修改
	}
	cout << endl;

	for (auto ch : s1)
	{
		cout << ch << endl;
	}
}

set实际上的功能可描述为:排序+去重 ,测试结果如下:

 特殊成员函数count的用法:

与之前容器相比,set有一个新增的特殊成员函数count(),set中可以利用其判断某个元素是否存在,相较于用迭代器更加方便,如下:

void set_test2()
{
	std::set<int> s1;
	s1.insert(2);
	s1.insert(4);
	s1.insert(3);
	s1.insert(6);
	s1.insert(6);
	s1.insert(1);
	std::set<int>::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	int x = 0;
	while (cin >> x)
	{
//使用迭代器+find的方法
		/*auto ret = s1.find(x);
		if (ret != s1.end())
		{
			cout << "在" << endl;
		}
		else
		{
			cout << "不在" << endl;
		}
		cout << endl;*/
//使用count的方法
		if (s1.count(x))
		{
			cout << "在" << endl;
		}
		else
		{
			cout << "不在" << endl;
		}
		cout << endl;
    }
}

使用方法是,如果存在某个元素,函数返回1,否则返回0,测试结果如下: 

 multiset

与set的用法几乎一样,唯一的区别在于,multiset允许键值冗余,即允许相同元素存在,相同元素怎么插入取决于底层的实现,后面讲到底层原理会详细说明:

multiset的用法:

multiset迭代器的使用:

multiset迭代器的使用与set一样

void multiset_test1()
{
	std::multiset<int> s1;
	s1.insert(2);
	s1.insert(4);
	s1.insert(3);
	s1.insert(6);
	s1.insert(6);
	s1.insert(1);
	std::multiset<int>::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << endl;
		it++;
	}
	cout << endl;
}

set实际上的功能可描述为:排序 ,测试结果如下:

特殊成员函数count的用法:

与set不同的是:multiset的count不仅可以用于判断某一元素是否存在,还可以得到相应元素的个数,如下:

void multiset_test2()
{
	std::multiset<int> s1;
	s1.insert(2);
	s1.insert(4);
	s1.insert(3);
	s1.insert(6);
	s1.insert(6);
	s1.insert(1);
	s1.insert(1);
	s1.insert(1);

	std::multiset<int>::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	int x = 0;
	while (cin >> x)
	{
		if (s1.count(x))
		{
			cout << "在" << endl;
		}
		else
		{
			cout << "不在" << endl;
		}
		cout << s1.count(x) << endl;

		cout << endl;
	}
}

测试结果如下: 

在查找的元素不止一个时,找到的是中序遍历结果中第一个该元素,验证如下:

void multiset_test3()
{
	std::multiset<int> s1;
	s1.insert(2);
	s1.insert(4);
	s1.insert(3);
	s1.insert(6);
	s1.insert(6);
	s1.insert(1);
	s1.insert(1);
	s1.insert(1);

	std::multiset<int>::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	int x = 0;
	while (cin >> x)
	{
		auto ret = s1.find(x);
		while (ret != s1.end() && *ret == x)
		{
			cout << *ret << " ";
			ret++;
		}
		cout << endl;
		cout << endl;
	}
}

 测试结果如下:

 map

翻译:
1. map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素。
2. 在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的
内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型 value_type绑定在一起,为其取别名称为pair: typedef pair<const key, T> value_type;
3. 在内部,map中的元素总是按照键值key进行比较排序的。
4. map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序
对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。
5. map支持下标访问符,即在[ ]中放入key,就可以找到与key对应的value。
6. map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))。

 map的用法:

map同样与其它容器的使用无异,这里只对重要特殊的用法进行说明,更详细的用法见以下文档链接:

https://cplusplus.com/reference/map/map/?kw=maphttps://cplusplus.com/reference/map/map/?kw=map

map的插入:

我们知道,map的键值对以结构pair的形式存在,因此插入的时候应该插入一个pair结构,可以插入一个pair的匿名对象,但写完整类型比较麻烦,这里直接用一个模板函数make_pair来替代,实际上,make_pair就是对构造匿名对象的封装,它可以自动识=根据传入的参数类型来实例化对应的函数对象,不用自己写清楚这里绝大多数情况会被编译器处理为内联函数,因此不用考虑效率的问题,具体如下:

void map_test1()
{
	std::map<string, string> m1;
    //m1.insert(pair<string,string>("world", "世界"));
	m1.insert(make_pair("world", "世界"));
	m1.insert(make_pair("char", "字符"));
	m1.insert(make_pair("string", "字符串"));
	m1.insert(make_pair("girl", "女孩"));
	auto dit = m1.begin();
	while (dit != m1.end())
	{
		//cout << (*dit).first << ":" << (*dit).second << endl;
		cout << dit->first << ":" << dit->second << endl;
		dit++;
	}

}

这里返回迭代器类型通过箭头可以指向一个pair结构类型的指针,访问结构体成员有两种方法,其一:解引用得到结构体通过.访问,其二: 直接用结构体指针通过箭头访问

 方括号[ ]的用法:

map的[ ]重载是其重要的用法之一,通过它能实现元素的插入、修改、插入并修改以及查找等功能,如下:

void map_test2()
{
	std::map<string, string> m1;
	m1.insert(make_pair("world", "世界"));
	m1.insert(make_pair("char", "字符"));
	m1.insert(make_pair("string", "字符串"));
	m1.insert(make_pair("girl", "女孩"));
	m1.insert(make_pair("string", "(字符串)"));//key已经存在,插入失败
	auto dit = m1.begin();
	while (dit != m1.end())
	{
		//cout << (*dit).first << ":" << (*dit).second << endl;
		cout << dit->first << ":" << dit->second << endl;
		dit++;
	}
	cout << endl;
	m1["boy"];//插入
	m1["cup"] = "杯子";//插入+修改
	m1["string"] = "(字符串)";//修改
	cout << endl;
	cout << m1["string"]<<endl;

}

测试结果如下:

掌握[ ]的用法,就要学习其底层是如何实现的,查阅文档,重载[ ]具体实现分析如下:

 

应用实例:统计水果次数,代码如下:

void map_test3()
{
	string arr[] = { "西瓜", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉", "梨" };
	map<string, int> countMap;

	//for (auto& e : arr)
	//{
	//	auto ret = countMap.find(e);
	//	if (ret == countMap.end())
	//	{
	//		countMap.insert(make_pair(e, 1));
	//	}
	//	else
	//	{
	//		ret->second++;
	//	}
	//}

	for (auto& e : arr)
	{
		countMap[e]++;//一句就等效于上述注释部分的代码功能
	}

	for (auto& kv : countMap)
	{
		cout << kv.first << ":" << kv.second << endl;
	}

}

 测试结果如下:

multimap

multimap中的接口可以参考map,功能都是类似的。
注意:
1. multimap中的key是可以重复的
2. multimap中的元素默认将key按照小于来比较
3. multimap中没有重载operator[]操作,因为multimap中的key值和value值不是一 一对应的
4. 使用时与map包含的头文件相同

multimap的用法:

除了方括号用法几乎与map用法一致,这里只做简单举例:

multimap的插入:

void multimap_test1()
{
	std::multimap<string, string> m1;
	m1.insert(make_pair("world", "世界"));
	m1.insert(make_pair("char", "字符"));
	m1.insert(make_pair("string", "字符串"));
	m1.insert(make_pair("girl", "女孩"));
	m1.insert(make_pair("string", "(字符串)"));//即使key已经存在,也能成功插入,允许键值冗余
	auto dit = m1.begin();
	while (dit != m1.end())
	{
		//cout << (*dit).first << ":" << (*dit).second << endl;
		cout << dit->first << ":" << dit->second << endl;
		dit++;
	}
	cout << endl;
}

运行测试结果如下:

相关编程题练习

1.前K个高频单词:

题目链接:

力扣

题目描述:

给定一个单词列表 words 和一个整数 k ,返回前 k 个出现次数最多的单词。返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序排序。

示例:

 

题目分析:大思路可以分为两步,第一步为给次数排序,找出次数最多的前k个,第二步为给字符串排序,以实现出现相等次数字符串的字典序排序,由此推测我们可能会用到和排序相关的算法和容器。

题目实现:

1.用map<string,int>定义一个map容器可以完成字符串出现次数的统计

2.用set<pair<string,int>,Compare>定义一个set容器通过自己实现Compare完成按出现次数从多到少排序(ps:Compare默认是按pair升序即字符串升序来排,但我们想要其按次数降序来排,因此要自己实现)

3.完成以上两步,我们就能得到次数最多的前k个字符串,而次数相等时按字符串字典序排序可以在Compare中控制

4.定义数组将multiset中的前k个pair的key值即字符串值尾插即可

class Solution {
public:
    vector<string> topKFrequent(vector<string>& words, int k) {
      //定义map<string,int>统计各字符串出现的次数
      map<string,int> countmap;
      for(auto& str:words)
      {
          countmap[str]++;
      }

      //定义multiset排序
      class Compare{
         public:
         bool operator()(const pair<string,int>& left,const pair<string,int>& right) const
         {
             return left.second>right.second||left.second==right.second&&left.first<right.first;
         }
      };
      multiset<pair<string,int>,Compare> sortset(countmap.begin(),countmap.end());
      //定义vector完成尾插
      vector<string> result;
      auto it=sortset.begin();
      while(k--)
      {
        result.push_back(it->first);
        it++;
      }
      return result;
    }
};

除了选用具有排序功能的set容器来进行排序,还可以用库里已有的排序算法,但需要注意排序的稳定性(相同数据的处理),这里补充一个稳定的排序算法为stable_sort,用法如下:


class Solution {
public:
struct Compare{
         bool operator()(const pair<string,int>& left,const pair<string,int>& right) const
         {
             return left.second>right.second;
         }
      };
    vector<string> topKFrequent(vector<string>& words, int k) {
      //定义map<string,int>统计各字符串出现的次数
      map<string,int> countmap;
      for(auto& str:words)
      {
          countmap[str]++;
      }
      vector<pair<string,int>> v(countmap.begin(),countmap.end());
      stable_sort(v.begin(),v.end(),Compare());
      //定义vector完成尾插
      vector<string> result;
      auto it=v.begin();
      while(k--)
      {
        result.push_back(it->first);
        it++;
      }
      return result;
    }
};

2.两数组交集问题

题目链接:

力扣

题目描述:

给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以不考虑输出结果的顺序 。

示例:

题目分析:

题目整体分为两步,第一步:排序去重,第二步:同时依次遍历判断

题目实现:

首先用具有排序及去重功能set来对两数组排序去重,得到两组从小到大排序的数据,定义一个结果数组,再同时逐次相比较,两数组都从第一个数据开始比较,比较过程中,若相等则为交集,该数字入结果数组,下标均++;若不相等,则较小数字所在数组下标++,直至其中一个数组走完,实现如下:

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
      set<int> s1(nums1.begin(),nums1.end());
      set<int> s2(nums2.begin(),nums2.end());
      auto it1=s1.begin();
      auto it2=s2.begin();
      vector<int> ret;
      while(it1!=s1.end()&&it2!=s2.end())
      {
        if(*it1==*it2)
        {
            ret.push_back(*it1);
            it1++;
            it2++;
        }
        else if(*it1<*it2)
        {
            it1++;
        }
        else{
            it2++;
        }
      }
      return ret;
    }
};

拓展:两数组差集问题

分析后发现找差集方法与交集正好相反,同样的,首先用具有排序及去重功能set来对两数组排序去重,得到两组从小到大排序的数据,定义一个结果数组,再同时逐次相比较,两数组都从第一个数据开始比较,比较过程中,若相等则一定不是,下标均++;若不相等,则较小数字一定为差集,入结果数组,所在数组下标++,直至其中一个数组走完,剩下一个数组中的剩余元素一定都是差集元素。

底层结构

前面对map/multimap/set/multiset进行了简单的介绍,在其文档介绍中发现,这几个容器有个 共同点是:其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此 map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。

AVL树:

内容较多且重要,单独整理了一篇文章,见以下链接:

http://t.csdn.cn/Evckrhttp://t.csdn.cn/Evckr

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即log_2 (N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合,因此引入红黑树。

红黑树:

内容较多且重要,单独整理了一篇文章,见以下链接:

http://t.csdn.cn/JyZk8http://t.csdn.cn/JyZk8

红黑树模拟实现STL中的mapset

STL库中是怎么用红黑树封装map和set的,我们参考STL源码中核心部分发现:封装map和set时用的是同一颗红黑树,这与我们之前练习时的做法不同,即没有设计出两种红黑树模板,而是通过增加模板参数在实际运用中只需传不同的模板参数就可用一份模板来实例化出相应的红黑树类型进而封装成不同类型的容器,实际上就是为了复用,如下图(不完整,仅是核心代码的截取):

  说明:给红黑树设置两个模板参数,key_type和value_type,key_type就是两种容器的key值,仅用于拿到key值,用于find()和erase()等以key为参数的函数,而value_type,set依然指key值,map指的是pair结构,用于决定树的结点中存储的数据类型。

我们进一步来看库里的红黑树究竟是怎么实现的,set和map内部又分别是怎么样得到相应的红黑树,见下图(不完整,仅为核心代码的截取):

 map和set的封装:

首先要做的的就是根据源码中的设计来修改我们自己实现的红黑树结构,首先修改红黑树结点和红黑树的插入部分,修改后代码如下:

enum Color
{
	RED,
	BLACK
};
template<class T>//T就表示结点存值的类型
//红黑树结点的定义
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;//红黑树也涉及到旋转,因此给出父节点
	T _data;//表示结点值
	Color _col;

	//给一个构造函数
	RBTreeNode(T& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _col(RED)//为什么结点颜色默认给红色?
	{}

};
//红黑树的定义
template<class K, class T>
class RBTree
{
	typedef RBTreeNode<T> Node;
private:
	Node* _root = nullptr;
public:
bool Insert(const T& data)
	{
		//按照搜索二叉树的规则插入结点

		//如果根节点为空,说明第一次插入,那么构造一个新结点让根节点指向它,将其颜色设为BLACK,返回true即插入成功
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return true;
		}

		//根节点不为空,按照搜索二叉树规则插入数据
		Node* cur = _root;//cur是为了向下找到插入的位置
		Node* parent = nullptr;//parent是为了找到插入位置后与原树链接
		while (cur)
		{
			if (cur->_data < data)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_data > data)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		//找到了插入的位置
		cur = new Node(data);
		if (cur->_data > parent->_data)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;
		//到此结点按照搜索二叉树的性质插入成功了,接下来就是调整为红黑树结构
//...下面的判断调整部分就先不再赘述...//
    }
};

这样改完,我们发现了一个问题:在比较_data、data等值时,由于红黑树不知道上一层的数据具体是什么,因此我们不能单纯的只是实现一种数据类型的比较,参考源码发现,它的解决方式是运用仿函数,红黑树不知道数据类型,但上一层的map和set知道,因此可以在上一层就取出需要参与比较的key值,定义相应红黑树的时候同时传参,红黑树再通过使用仿函数来实现相应的比较,具体修改如下:

//Set.h
namespace wz
{
	template<class K>
	class Set
	{
		struct SetKeyofT
		{
			const K& operator () (const K& key)
			{
				return key;
			}
	};
	public:
		bool Insert(const K& key)
		{
			return _t.Insert(key);
		}
	private:
		RBTree<K, K,SetKeyofT> _t;
	};

	void settest()
	{
		Set<int> s1;
		s1.Insert(2);
		s1.Insert(4);
		s1.Insert(3);
		s1.Insert(6);
		s1.Insert(6);
		s1.Insert(1);
		int x = 0;

	}
}

//Mpa.h
namespace wz
{
template<class K,class V>
class Map
{
		struct MapKeyofT
		{
			const K& operator()(const pair<const K, V>& kv)
			{
				return kv.first;
			}
		};
public:
	bool Insert(const pair<const K, V>& kv)
	{
		return _t.Insert(kv);
	}
private:
	RBTree<K, pair<const K, V>,MapKeyofT> _t;
};

void maptest()
{
	Map<int, int> m1;
	m1.Insert(make_pair(2,2));
	m1.Insert(make_pair(4,4));
	m1.Insert(make_pair(3,3));
	m1.Insert(make_pair(6,6));
	m1.Insert(make_pair(6,6));
	m1.Insert(make_pair(1,1));
	int x = 0;
}
}

//RBTree.h
template<class K, class T,class KeyofValue>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	//成员函数
	//析构
	~RBTree()
	{
		_Destory(_root);
		_root = nullptr;
	}
	//查找
	Node* Find(const K& key)
	{
		Node* cur = _root;
		KeyofValue kov;
		while (cur)
		{
			if (kov(cur->_data)< key)
			{
				cur = cur->_right;
			}
			else if (kov(cur->_data) > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}

		return nullptr;
	}
	//插入
	bool Insert(T data)
	{
		//按照搜索二叉树的规则插入结点

		//如果根节点为空,说明第一次插入,那么构造一个新结点让根节点指向它,将其颜色设为BLACK,返回true即插入成功
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return true;
		}

		//根节点不为空,按照搜索二叉树规则插入数据
		Node* cur = _root;//cur是为了向下找到插入的位置
		KeyofValue kov;
		Node* parent = nullptr;//parent是为了找到插入位置后与原树链接
		while (cur)
		{
			if (kov(cur->_data) < kov(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kov(cur->_data)>kov(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		//找到了插入的位置
		cur = new Node(data);
		if (kov(cur->_data) > kov(parent->_data))
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;
		//到此结点按照搜索二叉树的性质插入成功了,接下来就是调整为红黑树结构
//。。。此处调整部分省略。。。
};

测试插入的功能如下,说明调整到目前为止是成功地:

settest():

maptest():

 总结,我们发现C++中很喜欢把一些类型不确定的问题,用仿函数来解决,库里当然增加了一个仿函数Compare,与之前总结过的一样,可以自行用来控制比较方式,因此不作为重点再次总结,有需要的可以参考我之前的总结。

 map和set的迭代器如何实现

观察源码可以知道,map和set的迭代器其实就是对红黑树迭代器的封装,因此我们这里主要先实现一下红黑树的迭代器,实现方法其实和链表的迭代器类似,这里不再详细说明,直接提供代码,如有需要可以参考本人总结链表时相关的分析,红黑树迭代器实现的代码如下,重点部分都有注释标记:

红黑树迭代器的实现:

template<class T,class Ref,class Ptr>
struct __RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __RBTreeIterator<T,Ref,Ptr> Self;

	Node* _node;

	__RBTreeIterator(Node* node)
		:_node(node)
	{}
	//  支持普通迭代器构造const迭代器的构造函数
	__RBTreeIterator(const __RBTreeIterator<T, T&, T*>& it)
		:_node(it._node)
	{}

	Ref operator*()
	{
		return _node->_data;
	}
	Ptr operator->()
	{
		return &_node->_data;
	}
	bool operator!=(const Self& s)
	{
		return _node != s._node;
	}

	Self& operator++()
	{
//如果右树不为空,则说明下一个一定是右树的最左节点
		if (_node->_right)
		{
			Node* subleft = _node->_right;
			while (subleft->_left)
			{
				subleft = subleft->_left;
			}
			_node= subleft;
		}
//如果右树为空,则不断向上寻找孩子为左树的父节点
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent&&parent->_right == cur)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	 }
	Self& operator--()
	{
//如果左树为空,则不断向上寻找孩子为右树的父节点
		if (_node->_left)
		{
			Node* subright = _node->_right;
			while (subright->_left)
			{
				subright = subright->_right;
			}
			_node = subright;
		}
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && parent->_left == cur)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}
	

};

红黑树begin()、end()、const_begin()、const_end()的实现:

typedef __RBTreeIterator<T, T&, T*> iterator;
typedef __RBTreeIterator<T, const T&, const T*> const_iterator;

	iterator begin()
	{
		Node* cur = _root;
		while (cur&&cur->_left)
		{
			cur = cur->_left;
		}
		return iterator(cur);
	}
	iterator end()
	{
		return iterator(nullptr);
	}
	const_iterator begin()const
	{
		Node* cur = _root;
		while (cur && cur->left)
		{
			cur = cur->_left;
		}
		return const_iterator(cur);
	}
	const_iterator end()const
	{
		return const_iterator(nullptr);
	}

Map.h:

包括具体实现以及测试代码,都已经调试过,测试代码均能正常运行

namespace wz
{
template<class K,class V>
class Map
{
public:
		struct MapKeyofT
		{
			const K& operator()(const pair<const K, V>& kv)
			{
				return kv.first;
			}
		};
		typedef typename RBTree<K, pair<const K, V>, MapKeyofT>::iterator iterator;

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

	iterator end()
	{
		return _t.end();
	}
	pair<iterator,bool> Insert(const pair<const K, V>& kv)
	{
		return _t.Insert(kv);
	}
	V& operator[](const K& key)
	{
		pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));
		return ret.first->second;
	}
private:
	RBTree<K, pair<const K, V>,MapKeyofT> _t;
};

void maptest1()
{
	Map<int, int> m1;
	m1.Insert(make_pair(2,2));
	m1.Insert(make_pair(4,4));
	m1.Insert(make_pair(3,3));
	m1.Insert(make_pair(6,6));
	m1.Insert(make_pair(6,6));
	m1.Insert(make_pair(1,1));
	Map<int, int>::iterator it = m1.begin();
	while (it != m1.end())
	{
		cout << it->first << ":" << it->second << endl;
		/*it->first = "1111";
		it->second = "111";*/

		++it;
	}
	cout << endl;

	for (auto& kv : m1)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
	cout << endl;
}
void maptest2()
{
	string arr[] = { "西瓜", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉", "梨" };
	map<string, int> countMap;
	//for (auto& e : arr)
	//{
	//	auto ret = countMap.find(e);
	//	if (ret == countMap.end())
	//	{
	//		countMap.insert(make_pair(e, 1));
	//	}
	//	else
	//	{
	//		ret->second++;
	//	}
	//}

	for (auto& e : arr)
	{
		countMap[e]++;
	}

	for (auto& kv : countMap)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
}
}

Set.h:

包括具体实现以及测试代码,都已经调试过,测试代码均能正常运行

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

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

	void settest()
	{
		Set<int> s1;
		s1.Insert(2);
		s1.Insert(4);
		s1.Insert(3);
		s1.Insert(6);
		s1.Insert(6);
		s1.Insert(1);
		Set<int>::iterator it = s1.begin();
		while (it != s1.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;

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

RBTree.h:

#pragma once
#include<iostream>

using namespace std;
//红黑树基本结构--红黑树结点的定义
//因为相较于普通的搜索二叉树,红黑树每个节点增加了颜色的属性,且不是红色就是黑色,为此我们定义一个枚举结构来表示颜色
enum Color
{
	RED,
	BLACK
};
//类比之前的AVL树和搜索二叉树,我们仍然设置两个模板参数分别方便表示Key,和value的类型
template<class T>//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)//为什么结点颜色默认给红色?
	{}

};
template<class T,class Ref,class Ptr>
struct __RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __RBTreeIterator<T,Ref,Ptr> Self;

	Node* _node;

	__RBTreeIterator(Node* node)
		:_node(node)
	{}
	__RBTreeIterator(const __RBTreeIterator<T, T&, T*>& it)
		:_node(it._node)
	{}

	Ref operator*()
	{
		return _node->_data;
	}
	Ptr operator->()
	{
		return &_node->_data;
	}
	bool operator!=(const Self& s)
	{
		return _node != s._node;
	}

	Self& operator++()
	{
		if (_node->_right)
		{
			Node* subleft = _node->_right;
			while (subleft->_left)
			{
				subleft = subleft->_left;
			}
			_node= subleft;
		}
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent&&parent->_right == cur)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	 }
	Self& operator--()
	{
		if (_node->_left)
		{
			Node* subright = _node->_right;
			while (subright->_left)
			{
				subright = subright->_right;
			}
			_node = subright;
		}
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && parent->_left == cur)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}
	

};

template<class K, class T,class KeyofValue>
class RBTree
{
public:
	typedef RBTreeNode<T> Node;
	typedef __RBTreeIterator<T, T&, T*> iterator;
	typedef __RBTreeIterator<T, const T&, const T*> const_iterator;

	iterator begin()
	{
		Node* cur = _root;
		while (cur&&cur->_left)
		{
			cur = cur->_left;
		}
		return iterator(cur);
	}
	iterator end()
	{
		return iterator(nullptr);
	}
	const_iterator begin()const
	{
		Node* cur = _root;
		while (cur && cur->left)
		{
			cur = cur->_left;
		}
		return const_iterator(cur);
	}
	const_iterator end()const
	{
		return const_iterator(nullptr);
	}
	//成员函数
	//析构
	~RBTree()
	{
		_Destory(_root);
		_root = nullptr;
	}
	//查找
	Node* Find(const K& key)
	{
		Node* cur = _root;
		KeyofValue kov;
		while (cur)
		{
			if (kov(cur->_data)< key)
			{
				cur = cur->_right;
			}
			else if (kov(cur->_data) > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}

		return nullptr;
	}
	//插入
	pair<iterator,bool> Insert(const T& data)
	{
		//按照搜索二叉树的规则插入结点

		//如果根节点为空,说明第一次插入,那么构造一个新结点让根节点指向它,将其颜色设为BLACK,返回true即插入成功
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(iterator(_root),true);
		}

		//根节点不为空,按照搜索二叉树规则插入数据
		Node* cur = _root;//cur是为了向下找到插入的位置
		KeyofValue kov;
		Node* parent = nullptr;//parent是为了找到插入位置后与原树链接
		while (cur)
		{
			if (kov(cur->_data) < kov(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kov(cur->_data)>kov(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return make_pair(iterator(cur),false);
			}
		}
		//找到了插入的位置
		cur = new Node(data);
		Node* newnode = cur;
		if (kov(cur->_data) > kov(parent->_data))
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;
		//到此结点按照搜索二叉树的性质插入成功了,接下来就是调整为红黑树结构

		//parent一定存在,当parent不为空且是红色结点,说明它不是根节点,就需要向上更新,是parent是黑色则跳过循环跳过直接返回true即可
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			//分parent是grandfather的左孩子和右孩子两种情况来讨论
			if (grandfather->_left == parent)
			{
				//定义grandfather的右孩子为uncle
				Node* uncle = grandfather->_right;
				//情况一
				if (uncle && uncle->_col == RED)
				{
					//变色
					uncle->_col = BLACK;
					parent->_col = BLACK;
					grandfather->_col = RED;
					//更新
					cur = grandfather;
					parent = cur->_parent;
				}
				//情况二+三
				else//uncle不存在或uncle存在且为黑
				{
					//情况二
					if (cur == parent->_left)
					{
						//右单旋+变色
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					//情况3
					else
					{
						//针对p左单旋,针对g右单旋
						RotateL(parent);
						RotateR(grandfather);
						//更新结点颜色
						cur->_col = BLACK;
						parent->_col = RED;
						grandfather->_col = RED;
					}
					break;
				}
			}
			else//(grandfather->_right == parent)
			{
				//定义grandfather的左孩子为uncle
				Node* uncle = grandfather->_left;
				//情况一
				if (uncle && uncle->_col == RED)
				{
					//变色
					uncle->_col = BLACK;
					parent->_col = BLACK;
					grandfather->_col = RED;
					//更新
					cur = grandfather;
					parent = cur->_parent;
				}
				//情况二+三
				else//uncle不存在或uncle存在且为黑
				{
					//情况二
					if (cur == parent->_right)
					{
						//左单旋+变色
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					//情况3
					else
					{
						//针对p右单旋,针对g左单旋
						RotateR(parent);
						RotateL(grandfather);
						//更新结点颜色
						cur->_col = BLACK;
						parent->_col = RED;
						grandfather->_col = RED;
					}
					break;
				}
			}
		}
		//有可能更新到了根节点,如果到了根节点就要把根节点置黑
		_root->_col = BLACK;
		return make_pair(iterator(newnode),true);
	}
	//中序遍历
	void Inorder()
	{
		_Inorder(_root);
		cout << endl;
	}
	//判断是否是红黑树
	bool IsBalance()
	{
		//性质1:结点的颜色不是红色就是黑色,这点其实不用验证,因为我们定义的枚举类型就能约束这一点
		//性质2:根节点是黑色,又因为空树也可看做红黑树,故若根节点存在且为红色,则不满足,返回false
		if (_root && _root->_col == RED)
		{
			cout << "根节点颜色是红色的" << endl;
			return false;
		}
		//性质3:不能有连续的红结点;
		//检查是否有连续红结点只要遍历所有节点同时检查该节点是否和它的父节点同为红色即可
		// 【检查当前结点与孩子结点不太好,因为一个结点可能没有孩子,但一定有父亲,走到这里已经不需要考虑整棵树的根节点了,因为前面已经判断过了】
		// 
		//性质4:每个节点到该后代所有叶节点的所有简单路径上黑色结点数相等,		
		//设置一个参考值,后面每遍历完一条路径就把得出的黑色节点数和这个基准值比较,不相等就返回false
		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
			{
				benchmark++;
			}
			cur = cur->_left;
		}
		//递归判断是否有连续的红结点以及每条路径黑色结点是否相等
		return _check(_root, 0, benchmark);
	}
private:
	void _Destory(Node* _root)
	{
		if (_root == nullptr)
		{
			return;
		}
		_Destory(_root->_left);
		_Destory(_root->_right);
		delete _root;

	}
	bool _check(Node* root, int blacknum, int benchmark)
	{
		//到空节点,说明一条路径走完了,此时判断一次黑色结点数量和基准值是否相等,相等则直接返回true,不等返回false
		if (root == nullptr)
		{
			if (blacknum != benchmark)
			{
				cout << "某条路径黑色节点的数量不相等" << endl;
				return false;
			}
			return true;
		}
		//不是空节点且颜色是黑色,这条路径的黑色结点就加1
		if (root->_col == BLACK)
		{
			blacknum++;
		}
		//是红色结点,就判断与父节点颜色关系,都为红色直接返回false
		if (root->_parent && root->_col == RED && root->_parent->_col == RED)
		{
			cout << "出现连续的红色结点" << endl;
			return false;
		}
		//继续向左向右递归判断,当前的黑结点数量以及基准值都要往下传
		return _check(root->_left, blacknum, benchmark)
			&& _check(root->_right, blacknum, benchmark);
	}
	void _Inorder(Node*& root)
	{
		if (root == nullptr)
		{
			return;
		}
		_Inorder(root->_left);
		cout << root->_kv.first << " ";
		_Inorder(root->_right);
	}
	//右单旋
	void RotateR(Node* parent)
	{
		//定义两个变量分别标记将作为替补根的父节点的左,以及后面要改变父亲的替补结点的右
		Node* SubL = parent->_left;
		Node* SubLR = SubL->_right;//就是分析过程中的b树
		//为了和上层的树链接,还要先记录一下当前parent的父亲
		Node* pparent = parent->_parent;
		//按照分析改变链接关系
		SubL->_right = parent;
		parent->_parent = SubL;
		parent->_left = SubLR;
		//b树不是空树,则更新SubLR的父指针
		if (SubLR)
		{
			SubLR->_parent = parent;
		}
		//如果来之前parent就是根节点了,那直接将根节点更新为SubL,并让其父亲指向空
		if (parent == _root)
		{
			_root = SubL;
			_root->_parent = nullptr;
		}
		//否则就让pparent指向parent的指针指向SubL
		else
		{
			if (parent == pparent->_left)
			{
				pparent->_left = SubL;
			}
			else
			{
				pparent->_right = SubL;

			}
			SubL->_parent = pparent;
		}
		//红黑树没有平衡因子的概念,无需更新平衡因子!!!!
		//SubL->_bf = parent->_bf = 0;
	}
	//左单旋
	void RotateL(Node* parent)
	{
		Node* SubR = parent->_right;
		Node* SubRL = SubR->_left;
		Node* pparent = parent->_parent;
		//改链接关系
		SubR->_left = parent;
		parent->_parent = SubR;
		parent->_right = SubRL;

		if (SubRL)
		{
			SubRL->_parent = parent;
		}

		if (parent == _root)
		{
			_root = SubR;
			_root->_parent = nullptr;
		}
		else
		{
			if (pparent->_left == parent)
			{
				pparent->_left = SubR;
			}
			else
			{
				pparent->_right = SubR;
			}
			SubR->_parent = pparent;
		}
		//红黑树没有平衡因子的概念,无需更新平衡因子!!!!
		//SubL->_bf = parent->_bf = 0;
	}
	Node* _root = nullptr;
};

综上我们基于对红黑树的改造对map和set完成了模拟封装,基本功能和思想与库里是基本一样的,库里考虑到更全面的问题,肯定还会有很多细节问题,但是不需要我们去挖得太深,同时我们模拟实现的内容并不全面,向Find()、sor()等函数都没实现,但在这里不算是重点,把我总结到的内容都理解,对于map和set的学习就很深入了,足够日后的理解使用。

本节涉及到的所有代码见以下链接,欢迎参考指正!

practice: 课程代码练习 - Gitee.comhttps://gitee.com/ace-zhe/practice/tree/master/map%E5%92%8Cset

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

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

相关文章

麒麟信安携手先进数通发布“多云协同,加速推进服务器虚拟化国产平滑迁移”信创联合解决方案

金融行业是现代经济的核心&#xff0c;金融行业信息技术应用创新是关系“国家安全”和“科技强国”战略的重要工程。为满足银行等金融机构数字化转型和信创发展的双重需求&#xff0c;麒麟信安与北京先进数通信息技术股份公司携手推出“多云协同&#xff0c;加速推进服务器虚拟…

Elisp之message为内容增加颜色(二十六)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

驱动 CAN总线

Controller Area Network&#xff08;控制器局部网络&#xff09;是一种有效支持分布式控制或实时控制的串行异步半双工的现场总线&#xff0c;支持多主机多从机&#xff0c;需要 CAN控制器 和 CAN收发器 的硬件支持 标准内容ISO 11898-1:2015定义数据链路层&#xff08;包括逻…

「二叉树与递归的一些框架思维」

文章目录 0 二叉树1 思路2 刷题2.1 二叉树的最大深度题解Code结果 2.2 二叉树的直径题解Code结果 2.3 在每个树行中找最大值题解Code结果 2.4 翻转二叉树题解Code结果 2.5 二叉树展开为链表题解Code结果 2.6 每日一题&#xff1a;数组中两元素的最大乘积题解Code结果 0 二叉树 …

华为开源自研AI框架昇思MindSpore应用案例:Vision Transformer图像分类

目录 一、环境准备1.进入ModelArts官网2.使用CodeLab体验Notebook实例 二、环境准备与数据读取三、模型解析Transformer基本原理Attention模块 Transformer EncoderViT模型的输入整体构建ViT 四、模型训练与推理模型训练模型验证模型推理 近些年&#xff0c;随着基于自注意&…

Spring——Spring是什么?IoC容器是什么?

文章目录 前言一、Spring是什么1.IoC 容器 —— 容器2.IoC 容器 —— IoC传统程序开发控制反转式程序开发 3.Spring IoC 二、DI是什么总结 前言 本人是一个普通程序猿!分享一点自己的见解,如果有错误的地方欢迎各位大佬莅临指导,如果你也对编程感兴趣的话&#xff0c;互关一下…

Vue2基础四、生命周期

零、文章目录 Vue2基础四、生命周期 1、生命周期 Vue生命周期&#xff1a;一个Vue实例从 创建 到 销毁 的整个过程。生命周期四个阶段&#xff1a;① 创建 ② 挂载 ③ 更新 ④ 销毁 创建阶段&#xff1a;创建响应式数据挂载阶段&#xff1a;渲染模板更新阶段&#xff1a;修改…

基于K8s环境·使用ArgoCD部署Jenkins和静态Agent节点

今天是「DevOps云学堂」与你共同进步的第 47天 第⑦期DevOps实战训练营 7月15日已开营 实践环境升级基于K8s和ArgoCD 本文节选自第⑦期DevOps训练营 &#xff0c; 对于训练营的同学实践此文档依赖于基础环境配置文档&#xff0c; 运行K8s集群并配置NFS存储。实际上只要有个K8s集…

Spring Security内置过滤器详解

相关文章&#xff1a; OAuth2的定义和运行流程Spring Security OAuth实现Gitee快捷登录Spring Security OAuth实现GitHub快捷登录Spring Security的过滤器链机制Spring Security OAuth Client配置加载源码分析 文章目录 前言OAuth2AuthorizationRequestRedirectFilterOAuth2Log…

matlab多线程,parfor循环进度,matlab互斥锁

一. 内容简介 matlab多线程&#xff0c;parfor循环进度&#xff0c;matlab互斥锁 二. 软件环境 2.1 matlab 2022b 2.2代码链接 https://gitee.com/JJW_1601897441/csdn 三.主要流程 3.1 matlab多线程 有好几种&#xff0c;最简单的&#xff0c;最好理解的就是parfor&am…

cpolar内网穿透外网远程访问本地网站

文章目录 cpolar内网穿透外网远程访问本地网站 cpolar内网穿透外网远程访问本地网站 在现代人的生活中&#xff0c;电脑是离不开的重要设备&#xff0c;大家看到用到的各种物品都离不开电脑的支持。尽管移动电子设备发展十分迅速&#xff0c;由于其自身存在的短板&#xff0c;…

Leetcode-每日一题【剑指 Offer 66. 构建乘积数组】

题目 给定一个数组 A[0,1,…,n-1]&#xff0c;请构建一个数组 B[0,1,…,n-1]&#xff0c;其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]A[0]A[1]…A[i-1]A[i1]…A[n-1]。不能使用除法。 示例: 输入: [1,2,3,4,5]输出: [120,60,40,30,24] 提示&#xff1a; 所…

SSIS对SQL Server向Mysql数据转发表数据 (三)

1、在控制流界面&#xff0c;在左侧的组件里&#xff0c;添加一个“序列容器组件”和一个“数据流任务组件” 2、双击数据流任务&#xff0c;进入到数据流界面&#xff0c;然后再在左面添加一个OLE DB 源组件、目标源组件 3、右键源组件&#xff0c;编辑&#xff0c;选择好相关…

虎年现货黄金投资布局图

参与现货黄金交易的主要目的&#xff0c;是为了根据行情走势的变动&#xff0c;把握一些较佳的获利机会&#xff0c;在这样的一个过程中&#xff0c;如果投资者能够提前把布局的图表画好&#xff0c;那么就可能获得事半功倍的效果&#xff0c;而本文将为大家简单的介绍&#xf…

C++——STL容器之list链表的讲解

目录 一.list的介绍 二.list类成员函数的讲解 2.2迭代器 三.添加删除数据&#xff1a; 3.1添加&#xff1a; 3.2删除数据 四.排序及去重函数&#xff1a; 错误案例如下&#xff1a; 方法如下&#xff1a; 一.list的介绍 list列表是序列容器&#xff0c;允许在序列内的任何…

前端面试题 —— React (二)

目录 一、React 组件中怎么做事件代理&#xff1f;它的原理是什么&#xff1f; 二、React.Component 和 React.PureComponent 的区别 三、Component, Element, Instance 之间有什么区别和联系&#xff1f; 四、React声明组件有哪几种方法&#xff0c;有什么不同&#xff1f…

数学建模-MATLAB三维作图

导出图片用无压缩tif会更清晰 帮助文档&#xff1a;doc 函数名 matlab代码导出为PDF 新建实时脚本或右键文件转换为实时脚本实时编辑器-全部运行-内嵌显示保存为PDF

120个颠覆你认知的gpt使用案例汇总,办公效率提高500%

文章目录 介绍1.代码生成2.代码注释3.代码解释器4.充当 Linux 终端5.代码纠正6.英语口语练习7.专业的翻译8.面试官9.写任何考科目的作业10.快速解决学习中的任何问题11.网站推荐12.网络工具软件推荐13.快速学习新技能14.快速总结长文本的核心思想15.解决日常办公问题16.制作各种…

【CASA】生态系统NPP及碳源、碳汇模拟(土地利用变化、未来气候变化、空间动态模拟)

碳中和可以从碳排放&#xff08;碳源&#xff09;和碳固定&#xff08;碳汇&#xff09;这两个侧面来理解。陆地生态系统在全球碳循环过程中有着重要作用&#xff0c;准确地评估陆地生态系统碳汇及碳源变化对于研究碳循环过程、预测气候变化及制定合理政策具有重要意义。CASA(C…

【QT 网络云盘客户端】——实现文件属性窗口

目录 文件属性对话框 设置字体样式 获取文件的信息 显示文件属性对话框 当我们点击文件中的属性&#xff0c;则会弹出一个属性对话框&#xff1a; 实现过程&#xff1a; 0.设置 属性 菜单项的槽函数。 1.鼠获取鼠标选中的QListWidgetItem,它包含 图标和文件名 2.根据文件…