当前位置: 首页 > article >正文

set和map封装

目录

set和map区别

set和map的插入

set和map的实现

 修改红黑树的模板参数 

修改比较时使用的变量

迭代器的实现

迭代器的定义

*解引用重载

->成员访问重载

++自增重载

==重载

封装迭代器

RBTree迭代器封装

封装set迭代器

对set迭代器进行修改

封装map迭代器

修改对insert的返回值进行

map的[ ]解引用重载  

代码汇总


set和map区别

set和map是两种容器,与vector,string,list...不同的是,set和map属于关联性容器

关联性容器意味着数据的存放是有一些规则的,不是将数据随意存放的。

关联性容器规定了数据存放的规则,也就意味着其插入时需要对数据进行调整来满足规则要求,关联性容器严格的插入要求也促成了容器在某些特定功能上的效率增强。

set和map底层实现使用的是红黑树,这也使得set和map在对数据查找方面的效率极高。查找的时间复杂度可以达到O(logN)。100万的数据大约只需要查找30次。

set<int> s;		//此时set存储一个数据,类型是int
map<int, string> m;   //map存储两个数据,int和string  :查找数据的时候使用int类型

 set和map是同一种关联性容器,但是map可以存储两种数据。

set只存储了key(数据),进行查找的时候就只能用T类型进行查找;

map存储了key和T,虽然存储了两种数据,但是在进行查找的时候,map还是使用key,也就是第一种类型进行查找的。

map多储存的T使得其找查找到key后能够读取到其中存储的T数据。

关于set和map的使用场景区分:对于一次考试成绩要在大量数据中查找有没有60(或其他分数)分的,就可以将数据存储在set中,然后进行查找,key中存储的就是成绩;而如果要在找到60分成绩的同时能够输出对应成绩学生的姓名,此时使用map就更合适,此时key存储成绩,而T存储学生姓名。


set和map的插入

set的插入:因为set只存储一种数据,所以set插入与其他容器的插入并没区别,直接将数据插入即可。

set<int> s;		//此时set存储一个数据,类型是int
s.insert(1);
s.insert(2);
s.insert(3);
s.insert(4);

但是map中存放的数据有两种,其插入要求插入pair类型的对象。

详细可见一下文档。

map - C++ Referencehttps://legacy.cplusplus.com/reference/map/map/

pair包含两个成员变量:一个是first存储Key,另一个second存储T; 

 所以构造pair对象是对map进行插入时不可避免的。

pair的构造有两种:1)通过函数make_pair()来直接创建pair对象

2)在C++11支持多参数的构造,所以也可以直接通过{ }来实现构造。

map<int, string> m;   //map存储两个数据,int和string  :查找数据的时候使用int类型
pair<int, string> pa = make_pair(1, "hello");m.insert(pa);          //通过pair对象进行插入m.insert(make_pair(2, "world"));    //通过临时变量直接进行插入。m.insert({ 0,"best" });    //通过多参数的构造,实现对pair对象的构造

关于set和map的使用就就介绍到这里,详细可以看下方文档。

map - C++ Referencehttps://legacy.cplusplus.com/reference/map/map/set - C++ Referencehttps://legacy.cplusplus.com/reference/set/set/?kw=set下面将详细介绍set和map封装实现逻辑。


set和map的实现

set和map的底层是使用红黑树进行的实现,关于《红黑树》已经出过详细介绍了,此处将借助红黑树的底层逻辑来进一步封装出set和map容器。

红黑树剖析-CSDN博客文章浏览阅读476次,点赞28次,收藏13次。红黑树用于高效查找数据,及防止了普通搜索二叉树,也规避了AVL树的插入时多次旋转的代价,本文深度剖析了红黑树插入的逻辑,步骤以及插入后调整的方法,帮助读者能够理解和正确使用红黑树。 https://blog.csdn.net/2401_87944878/article/details/146522638此处贴一下红黑树的实现代码,如对红黑树的实现有详细理解可直接跳过。

#pragma once
#include<iostream>
using namespace std;namespace cfl
{enum Col{RED,BLACK};template<class K, class  V>struct RBTreeNode{//默认构造函数RBTreeNode(const pair<K, V>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED){}pair<K, V> _kv;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Col _col;};template<class K, class V>class RBTree{typedef RBTreeNode<K, V> Node;public://进行左旋void RotateL(Node* parent){//调整parent的父节点,右节点//调整cur的父节点,左节点//调整cur_left的父节点//调整parent的父节点的指向Node* pparent = parent->_parent;Node* cur = parent->_right;Node* cur_left = cur->_left;cur->_parent = pparent;if (parent == _root)   //注意:如果parent是根,在旋转后要对根进行更新{_root = cur;}else{if (pparent->_left == parent){pparent->_left = cur;}else{pparent->_right = cur;}}parent->_right = cur_left;parent->_parent = cur;if (cur_left)cur_left->_parent = parent;cur->_left = parent;}//进行右旋void RotateR(Node* parent){//调整parent的父节点,左节点//调整cur的父节点,右节点//调整cur_right的父节点//调整parent的父节点的指向Node* pparent = parent->_parent;Node* cur = parent->_left;Node* cur_right = cur->_right;cur->_parent = pparent;if (parent == _root){_root = cur;}else{if (pparent->_left == parent){pparent->_left = cur;}else{pparent->_right = cur;}}parent->_left = cur_right;parent->_parent = cur;cur->_right = parent;if (cur_right)cur_right->_parent = parent;}bool Insert(const pair<K, V> kv){Node* newnode = new Node(kv);if (_root == nullptr){_root = newnode;   //根节点为空,直接进行赋值_root->_col = BLACK;return true;}//根节点不为空,找节点插入位置Node* pcur = _root;Node* parent = nullptr;while (pcur){parent = pcur;if (pcur->_kv.first > kv.first){pcur = pcur->_left;}else if (pcur->_kv.first < kv.first){pcur = pcur->_right;}else{return false;   //相等不需要插入}}//找到节点的位置//进行插入if (parent->_kv.first > kv.first){parent->_left = newnode;}else{parent->_right = newnode;}newnode->_parent = parent;//检查节点是否满足要求//....//父节点是黑色,满足条件if (parent->_col == BLACK){_root->_col = BLACK;return true;}else    //父节点是红色,此时出现连续的红色,需要进行调整{pcur = newnode;while (parent && parent->_col == RED){Node* grandparent = parent->_parent;if (parent == grandparent->_left)  //分类确定uncle节点{Node* uncle = grandparent->_right;if (uncle && uncle->_col == RED){//对节点进行变色uncle->_col = parent->_col = BLACK;grandparent->_col = RED;pcur = grandparent;   //继续向上调整parent = pcur->_parent;}else   //uncle节点是空或uncle节点是黑色{//判断旋转方式//上述if条件中已经确定了parent==grandparent->_leftif (pcur == parent->_left){//以grandparent为中心,进行右旋RotateR(grandparent);//进行变色parent->_col = BLACK;grandparent->_col = RED;_root->_col = BLACK;//此处parent是当前子树的根且是黑色,不用继续向上调整了return true;}else   //cur==parent->_right{//需要进行双旋RotateL(parent);RotateR(grandparent);//调色pcur->_col = BLACK;grandparent->_col = parent->_col = RED;_root->_col = BLACK;return true;}}}else  //parent=grandparent->rigth{Node* uncle = grandparent->_left;if (uncle && uncle->_col == RED){//对节点进行变色uncle->_col = parent->_col = BLACK;grandparent->_col = RED;pcur = grandparent;   //继续向上调整parent = pcur->_parent;}else   //uncle节点是空或uncle节点是黑色{//判断旋转方式//上述if条件中已经确定了parent==grandparent->_rightif (pcur == parent->_right){//以grandparent为中心,进行右旋RotateL(grandparent);//进行变色parent->_col = BLACK;grandparent->_col = RED;//此处parent是当前子树的根且是黑色,不用继续向上调整了_root->_col = BLACK;return true;}else   //cur==parent->_left{//需要进行双旋RotateR(parent);RotateL(grandparent);//调色pcur->_col = BLACK;grandparent->_col = parent->_col = RED;_root->_col = BLACK;return true;}}}}}_root->_col = BLACK;return true;}bool Isbance(){int num = 0;  //记录根到叶子节点有多少个黑节点Node* pcur = _root;while (pcur){if (pcur->_col == BLACK){++num;}pcur = pcur->_left;}return Isbance(_root, num, 0);   //num是每条支路黑节点个数的参照}private:bool Isbance(Node* root, int num, int each)   //each记录当前之路黑节点个数{if (root == nullptr){if (each == num)  //每条路的黑色节点数相同return true;cout << "黑色节点个数不对" << endl;return false;}if (root->_col == BLACK){each++;}else{if (root->_parent->_col == RED)  //看父节点是不是红色{return false;cout << root->_kv.first << " 红节点连续" << endl;}}return Isbance(root->_left, num, each) && Isbance(root->_right, num, each);}Node* _root = nullptr;};
}

 修改红黑树的模板参数 

红黑树的模板参数就现在看来需要两个:1)确定查找数据的类型;2)用于存储数据的类型。

注意:关于红黑树的模板参数并不止这两种,在后面还需要添加。

对于set来说:查找数据的类型和存储数据的类型是一样的,都是T。

而对于map来说:查找数据的类型是key,而存储数据的类型是pair。

template<class T>
class set
{typedef RBTree<T, T> Tree;    //查找类型和存储类型相同private:Tree _tree;
};
template<class K,class V>
class map
{typedef RBTree<K, pair<K,V>> Tree;    //查找类型和存储类型相同private:Tree _tree;
};
template<class K, class T>
class RBTree
{//......
}

在上面贴出的红黑树实现代码中,使用的均是pair进行数据存储,此时需要将pair均改为T来实现泛型编程的效果。

template<class K, class  T>
struct RBTreeNode
{//默认构造函数RBTreeNode(const T& kv):_date(date), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED){}T _date;RBTreeNode<K, T>* _left;RBTreeNode<K, T>* _right;RBTreeNode<K, T>* _parent;Col _col;
};

关于RBTree中代码的修改在此处不再贴出,将会放在结尾总结处统一贴出。


修改比较时使用的变量

经过上面对红黑树模板参数的修改后,T中可能存储的是自定义类型pair也肯能是内置类型int...;

在插入函数Insert中,要对数据进行比较:内置类型可以直接进行比较,但是对于pair类型其比较方式并不是我们希望的方式。

可以看到pair的比较方式是:先比较first变量,再比较second变量。但是我们理想的比较方式是只对first中存储的Key类型进行比较,所以此处不能直接使用pair的比较方法。

如果能够拿到pair的first中存储的键值Key就好了,直接将Key进行比较就行了。为了实现拿到键值Key此处采用内部类的方式实现。

在set和map中分别实现内部类,来获取其比较是用的数据;对于set来说返回的就是T,而对于map来说返回的则是pair中存储的first。在RBTree添加一个模板参数用来保存内部类即可。

	struct SetofKey{//重载()运算符const T& operator()(const T& date){return date;}};
public:typedef RBTree<T, T,SetofKey> Tree;    //查找类型和存储类型相同
	struct MapofKey{//重载()运算符const K& operator()(const pair<K, V>& kv){return kv.first;}};
public:typedef RBTree<K, pair<K,V>,MapofKey> Tree;    //查找类型和存储类型相同
template<class K, class T,class TofKey>
class RBTree
{typedef RBTreeNode<K, T> Node;
public:// ......
}

当需要进行数据比较的时候,先用内部类取出需要比较的数据即可。

比如一下修改(以下仅是部分修改代码)。

TofKey getkey;
//根节点不为空,找节点插入位置
Node* pcur = _root;
Node* parent = nullptr;
while (pcur)
{parent = pcur;if (getkey(pcur->_date) > getkey(date)){pcur = pcur->_left;}else if (getkey(pcur->_date) < getkey(date)){pcur = pcur->_right;}else{return false;   //相等不需要插入}
}

迭代器的实现

set和map的底层是红黑树,所以其迭代器自然就是指针,通过结构体对指针进行封装重载++,==以及*解引用等。

迭代器的定义

//迭代器的实现
template<class T,class Ref ,class Ptr>
struct TreeIterator
{typedef RBTreeNode<T> Node;typedef TreeIterator<T, Ref, Ptr> Self;typedef //构造函数TreeIterator(Node* node):_node(node){ }Node* _node;
};

其中的Ref和Ptr用来区分非const和const迭代器。 

*解引用重载

//解引用重载
Self operator*()
{return _node->_date;
}

->成员访问重载

//重载->访问
Ptr operator->()
{return &(_node->_date);
}

++自增重载

对于指针的++,红黑树的遍历顺序是中序遍历;

所以在重载++的时候,下一个指针位置分为两种情况:1)当前节点的右节点不为空,找右子树的最小节点(即最左侧节点);2)当前节点右节点为空时,向上找,直到找到一个节点是其父节点的左节点时,其父节点就是下一个节点。

//重载++
Self operator++()
{if (_node->_right){//找右树的最小节点Node* pcur = _node->_right;while (pcur->_left){pcur = pcur->_left;}_node = pcur;return *this;}else{//向上找Node* parent = _node->_parent;Node* pcur = _node;while (parent){if (pcur == parent->_left){_node = parent;return *this;}else{pcur = parent;parent = pcur->_parent;}}_node = nullptr;return *this;}
}

==重载

//==重载
bool operator==(Self& it)
{return _node == it._node;
}bool operator!=(Self& it)
{return _node != it._node;
}

封装迭代器

RBTree迭代器封装

template<class K, class T,class TofKey>
class RBTree
{typedef RBTreeNode<T> Node;
public:typedef TreeIterator<T, T&, T*> iterator;typedef TreeIterator<T, const T&, const T*> const_iterator;//beginiterator begin(){Node* pcur = _root;//找最小节点,即最左侧while (pcur && pcur->_left){pcur = pcur->_left;}return pcur;   //隐式类型转化,通过pcur指针进行iterator的构造}//enditerator end(){return nullptr;}
}

const版本

//begin
const_iterator begin()const
{Node* pcur = _root;//找最小节点,即最左侧while (pcur && pcur->_left){pcur = pcur->_left;}return pcur;   //隐式类型转化,通过pcur指针进行iterator的构造
}//end
const_iterator end()const
{return nullptr;
}

封装set迭代器

对于插入到红黑树中的数据来说,用于比较的数据是不能被修改的,所以对set来说其存储的key值不能进行修改,所以set的普通迭代器的实质还是const迭代器。

template<class T>
class set
{public:typedef RBTree<T, T,SetofKey> Tree;    //查找类型和存储类型相同typedef typename Tree::const_iterator iterator;typedef typename Tree::const_iterator cosnt_iterator;//set的beginiterator begin()const{return _tree.begin();}//set的enditerator end()const{return _tree.end();}
}

迭代器实现后却出现了报错,说无法进行转化。 其原因是_tree.begin()返回的是普通迭代器,而set的普通迭代器实际上还是const迭代器,普通迭代器和const迭代器虽然是来自同一个模板,但是其是完全不同的两个类,所以此处需要用返回的普通迭代器中的数据来构造一个const迭代器。

对set迭代器进行修改

template<class T>
class set
{struct SetofKey{//重载()运算符const T& operator()(const T& date){return date;}};
public:typedef RBTree<T, T,SetofKey> Tree;    //查找类型和存储类型相同typedef typename Tree::iterator Iterator;  //用来表示普通迭代器typedef typename Tree::const_iterator iterator;typedef typename Tree::const_iterator cosnt_iterator;//set的beginiterator begin(){Iterator it = _tree.begin();  //接收返回的普通迭代器return iterator(it._node);}//set的enditerator end(){Iterator it=_tree.end();    return iterator(it._node);}
}

封装map迭代器

map的K也不能进行修改,但是T可以进行修改,所以map普通迭代器不变,关于如何保证map的K不被修改在后面进行分析。

template<class K,class V>
class map
{
public:typedef RBTree<K, pair<K,V>,MapofKey> Tree;    //查找类型和存储类型相同typedef typename Tree::iterator iterator;typedef typename Tree::const_iterator const_iterator;iterator begin(){return _tree.begin();}iterator end(){return _tree.end();}
}

以上就是map的iterator的封装了,但是还需要实现对Key值的不能修改,此处的实现就比较简单了,可以直接在pair的模板参数中使用const Key即可。

typedef RBTree<K, pair<const K,V>,MapofKey> Tree;    //查找类型和存储类型相同

修改对insert的返回值进行

可以看到在库中insert的返回值是pair对象,所以此处对原红黑树insert要进行修改,用newnode来创建iterator即可。

将return true修改为return make_pair(newnode,true)即可

与set迭代器封装一样,set的insert也需要先存储返回值,在进行转化。

map的[ ]解引用重载  

map的解引用重载的实现,底层还是insert,对没有的数据进行插入,对已经存在的数据返回T。

V& operator[](const K& key)
{pair<iterator, bool> ret = _tree.Insert({ key, V() });return ret.first._node->_date.second;
}

代码汇总

<map>

template<class K,class V>
class map
{struct MapofKey{//重载()运算符const K& operator()(const pair<K, V>& kv){return kv.first;}};
public:typedef RBTree<K, pair<const K,V>,MapofKey> Tree;    //查找类型和存储类型相同typedef typename Tree::iterator iterator;typedef typename Tree::const_iterator const_iterator;iterator begin(){return _tree.begin();}iterator end(){return _tree.end();}pair<iterator,bool> insert(const pair<K,V>& date){pair<iterator, bool> ret = _tree.Insert(date);return make_pair(ret.first._node, ret.second);}V& operator[](const K& key){pair<iterator, bool> ret = _tree.Insert({ key, V() });return ret.first._node->_date.second;}private:Tree _tree;
};

<set>

template<class T>
class set
{struct SetofKey{//重载()运算符const T& operator()(const T& date){return date;}};
public:typedef RBTree<T, T,SetofKey> Tree;    //查找类型和存储类型相同typedef typename Tree::iterator Iterator;typedef typename Tree::const_iterator iterator;typedef typename Tree::const_iterator cosnt_iterator;//set的beginiterator begin(){Iterator it = _tree.begin();return iterator(it._node);}//set的enditerator end(){Iterator it=_tree.end();return iterator(it._node);}pair<iterator, bool> insert(const T& date){pair<Iterator, bool> ret = _tree.Insert(date);return make_pair(ret.first._node, ret.second);}private:Tree _tree;
};

<RBTree>

enum Col
{RED,BLACK
};template<class  T>
struct RBTreeNode
{//默认构造函数RBTreeNode(const T& date):_date(date), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED){}T _date;RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;Col _col;
};//迭代器的实现
template<class T,class Ref ,class Ptr>
struct TreeIterator
{typedef RBTreeNode<T> Node;typedef TreeIterator<T, Ref, Ptr> Self;//构造函数TreeIterator(Node* node):_node(node){ }//解引用重载Ref operator*(){return _node->_date;}//重载->访问Ptr operator->(){return &(_node->_date);}//重载++Self operator++(){if (_node->_right){//找右树的最小节点Node* pcur = _node->_right;while (pcur->_left){pcur = pcur->_left;}_node = pcur;return *this;}else{//向上找Node* parent = _node->_parent;Node* pcur = _node;while (parent){if (pcur == parent->_left){_node = parent;return *this;}else{pcur = parent;parent = pcur->_parent;}}_node = nullptr;return *this;}}//==重载bool operator==(Self& it){return _node == it._node;}bool operator!=(Self& it){return _node != it._node;}Node* _node;
};template<class K, class T,class TofKey>
class RBTree
{typedef RBTreeNode<T> Node;
public:typedef TreeIterator<T, T&, T*> iterator;typedef TreeIterator<T, const T&, const T*> const_iterator;//beginiterator begin(){Node* pcur = _root;//找最小节点,即最左侧while (pcur && pcur->_left){pcur = pcur->_left;}return pcur;   //隐式类型转化,通过pcur指针进行iterator的构造}//enditerator end(){return nullptr;}//beginconst_iterator begin()const{Node* pcur = _root;//找最小节点,即最左侧while (pcur && pcur->_left){pcur = pcur->_left;}return pcur;   //隐式类型转化,通过pcur指针进行iterator的构造}//endconst_iterator end()const{return nullptr;}//进行左旋void RotateL(Node* parent){//调整parent的父节点,右节点//调整cur的父节点,左节点//调整cur_left的父节点//调整parent的父节点的指向Node* pparent = parent->_parent;Node* cur = parent->_right;Node* cur_left = cur->_left;cur->_parent = pparent;if (parent == _root)   //注意:如果parent是根,在旋转后要对根进行更新{_root = cur;}else{if (pparent->_left == parent){pparent->_left = cur;}else{pparent->_right = cur;}}parent->_right = cur_left;parent->_parent = cur;if (cur_left)cur_left->_parent = parent;cur->_left = parent;}//进行右旋void RotateR(Node* parent){//调整parent的父节点,左节点//调整cur的父节点,右节点//调整cur_right的父节点//调整parent的父节点的指向Node* pparent = parent->_parent;Node* cur = parent->_left;Node* cur_right = cur->_right;cur->_parent = pparent;if (parent == _root){_root = cur;}else{if (pparent->_left == parent){pparent->_left = cur;}else{pparent->_right = cur;}}parent->_left = cur_right;parent->_parent = cur;cur->_right = parent;if (cur_right)cur_right->_parent = parent;}pair<iterator,bool> Insert(const T date){Node* newnode = new Node(date);if (_root == nullptr){_root = newnode;   //根节点为空,直接进行赋值_root->_col = BLACK;return make_pair(newnode, true);}TofKey getkey;//根节点不为空,找节点插入位置Node* pcur = _root;Node* parent = nullptr;while (pcur){parent = pcur;if (getkey(pcur->_date) > getkey(date)){pcur = pcur->_left;}else if (getkey(pcur->_date) < getkey(date)){pcur = pcur->_right;}else{return make_pair(nullptr,false);   //相等不需要插入}}//找到节点的位置//进行插入if (getkey(parent->_date) > getkey(date)){parent->_left = newnode;}else{parent->_right = newnode;}newnode->_parent = parent;//检查节点是否满足要求//....//父节点是黑色,满足条件if (parent->_col == BLACK){_root->_col = BLACK;return make_pair(newnode, true);}else    //父节点是红色,此时出现连续的红色,需要进行调整{pcur = newnode;while (parent && parent->_col == RED){Node* grandparent = parent->_parent;if (parent == grandparent->_left)  //分类确定uncle节点{Node* uncle = grandparent->_right;if (uncle && uncle->_col == RED){//对节点进行变色uncle->_col = parent->_col = BLACK;grandparent->_col = RED;pcur = grandparent;   //继续向上调整parent = pcur->_parent;}else   //uncle节点是空或uncle节点是黑色{//判断旋转方式//上述if条件中已经确定了parent==grandparent->_leftif (pcur == parent->_left){//以grandparent为中心,进行右旋RotateR(grandparent);//进行变色parent->_col = BLACK;grandparent->_col = RED;_root->_col = BLACK;//此处parent是当前子树的根且是黑色,不用继续向上调整了return make_pair(newnode, true);}else   //cur==parent->_right{//需要进行双旋RotateL(parent);RotateR(grandparent);//调色pcur->_col = BLACK;grandparent->_col = parent->_col = RED;_root->_col = BLACK;return make_pair(newnode, true);}}}else  //parent=grandparent->rigth{Node* uncle = grandparent->_left;if (uncle && uncle->_col == RED){//对节点进行变色uncle->_col = parent->_col = BLACK;grandparent->_col = RED;pcur = grandparent;   //继续向上调整parent = pcur->_parent;}else   //uncle节点是空或uncle节点是黑色{//判断旋转方式//上述if条件中已经确定了parent==grandparent->_rightif (pcur == parent->_right){//以grandparent为中心,进行右旋RotateL(grandparent);//进行变色parent->_col = BLACK;grandparent->_col = RED;//此处parent是当前子树的根且是黑色,不用继续向上调整了_root->_col = BLACK;return make_pair(newnode, true);}else   //cur==parent->_left{//需要进行双旋RotateR(parent);RotateL(grandparent);//调色pcur->_col = BLACK;grandparent->_col = parent->_col = RED;_root->_col = BLACK;return make_pair(newnode, true);}}}}}_root->_col = BLACK;return make_pair(newnode, true);}bool Isbance(){int num = 0;  //记录根到叶子节点有多少个黑节点Node* pcur = _root;while (pcur){if (pcur->_col == BLACK){++num;}pcur = pcur->_left;}return Isbance(_root, num, 0);   //num是每条支路黑节点个数的参照}private:bool Isbance(Node* root, int num, int each)   //each记录当前之路黑节点个数{if (root == nullptr){if (each == num)  //每条路的黑色节点数相同return true;cout << "黑色节点个数不对" << endl;return false;}if (root->_col == BLACK){each++;}else{if (root->_parent->_col == RED)  //看父节点是不是红色{return false;cout << root->_date << " 红节点连续" << endl;}}return Isbance(root->_left, num, each) && Isbance(root->_right, num, each);}Node* _root = nullptr;
};

相关文章:

set和map封装

目录 set和map区别 set和map的插入 set和map的实现 修改红黑树的模板参数 修改比较时使用的变量 迭代器的实现 迭代器的定义 *解引用重载 ->成员访问重载 自增重载 重载 封装迭代器 RBTree迭代器封装 封装set迭代器 对set迭代器进行修改 封装map迭代器 修改…...

【Linux】Orin NX + Ubuntu22.04配置国内源

1、获取源 清华源 arm 系统的源,可以在如下地址获取到 https://mirror.tuna.tsinghua.edu.cn/help/ubuntu-ports/ 选择HTTPS,否则可能报错: 明文签署文件不可用,结果为‘NOSPLIT’(您的网络需要认证吗?)查看Orin NX系统版本 选择jammy的源 2、更新源 1)备份原配…...

Bazel中的Symbol, Rule, Macro, Target, Provider, Aspect 等概念

学习Bazel &#xff0c;就要学习Bazel 的规则定义&#xff0c; 弄清各个概念是重要的一个步骤。 在 Bazel 规则定义中&#xff0c;Symbol、Rule 和 Macro 是常见的概念。除此之外&#xff0c;Bazel 还有 Target、Provider、Aspect Repository、Package、 Workspace、 Configura…...

Open-Sora:开源AI视频生成的新星

一.引言 近年来&#xff0c;AI视频生成技术快速发展&#xff0c;从文本生成图像&#xff08;如Stable Diffusion、DALLE&#xff09;到文本生成视频&#xff08;如Runway、Pika&#xff09;&#xff0c;AI在多媒体创作领域的应用日益广泛。近期&#xff0c;Open-Sora作为一款开…...

【堆】《深入剖析优先级队列(堆):数据结构与算法的高效搭档》

文章目录 前言例题一、最后一块石头的重量二、数据流中的第 K 大元素三、前K个高频单词四、数据流的中位数 结语 前言 什么是优先级队列算法呢&#xff1f;它的算法原理又该怎么解释&#xff1f; 优先级队列&#xff08;堆&#xff09;算法是一种特殊的数据结构和算法&#xf…...

【CMOS输出缓冲器驱动强度】

一 、学习笔记 原始资料&#xff1a;https://www.ti.com.cn/cn/lit/an/zhcae18/zhcae18.pdf?ts1743589394832 Q1、电平转换芯片的其中一个关键指标是转换速率&#xff0c;转换速率跟什么因素有关系呢&#xff1f; 1、瞬态驱动强度 上升或下降时间用于评估瞬态驱动强度。需要…...

【C++】Cplusplus进阶

模板的进阶&#xff1a; 非类型模板参数 是C模板中允许使用具体值&#xff08;而非类型&#xff09;作为模板参数的特性。它们必须是编译时常量&#xff0c;且类型仅限于整型、枚举、指针、引用。&#xff08;char也行&#xff09; STL标准库里面也使用了非类型的模板参数。 …...

透明的卡组收费模式IC++

IC是信用卡处理商用来计算每笔交易相关费用的定价模型。与统一或混合定价相比&#xff0c;IC提供了额外的透明度。 作为企业主&#xff0c;了解IC定价的来龙去脉至关重要&#xff0c;以确定它是否对您的运营有意义。 什么是IC&#xff1f; IC或interchange plus是一种流行的定…...

虚拟试衣间微信小程序解决方案

目录 项目名称: 云尚衣橱 核心功能模块: 技术栈选型: 架构设计概览: 详细功能点实现思路: 数据库设计 (MongoDB 示例): 开发步骤建议: 关键注意事项和挑战: 项目名称: 云尚衣橱 核心功能模块: 用户系统 (User System) 我的衣柜 (My Wardrobe) 虚拟试衣间 (Vir…...

吾爱置顶软件,吊打电脑自带功能!

今天我给大家带来一款超棒的软件&#xff0c;它来自吾爱论坛的精选推荐&#xff0c;每一款都经过精心挑选&#xff0c;绝对好用&#xff01; S_Clock 桌面计时软件 这款软件的界面设计特别漂亮&#xff0c;简洁又大方。它是一款功能齐全的时钟计时倒计时软件&#xff0c;既能正…...

使用MFC ActiveX开发KingScada控件(OCX)

最近有个需求&#xff0c;要在KingScada上面开发一个控件。 原来是用的WinCC&#xff0c;WinCC本身是支持调用.net控件&#xff0c;就是winform控件的&#xff0c;winform控件开发简单&#xff0c;相对功能也更丰富。奈何WinCC不是国产的。 话说KingScada&#xff0c;国产组态软…...

应用安全系列之四十五:日志伪造(Log_Forging)之二

日志伪造(Log Forging)是一种常见的安全威胁&#xff0c;攻击者通过注入恶意内容破坏日志完整性。不同编程语言的防御方式有所不同&#xff0c;本文主要介绍Java、C#、Node.js、Rails(Ruby)和Go语言中的防护方法。 1、Java 在另外一篇博客里已经描述的比较详细&#xff0c;可…...

【AI论文】CodeARC:评估归纳程序合成中大语言模型代理的推理能力基准

摘要&#xff1a;归纳程序合成&#xff0c;或称示例编程&#xff0c;要求从输入输出示例中合成能够泛化到未见输入的函数。尽管大型语言模型代理在自然语言指导下的编程任务中展现出了潜力&#xff0c;但它们在执行归纳程序合成方面的能力仍待深入探索。现有的评估协议依赖于静…...

加密解密工具箱 - 专业的在线加密解密工具

加密解密工具箱 - 专业的在线加密解密工具 您可以通过以下地址访问该工具&#xff1a; https://toolxq.com/static/hub/secret/index.html 工具简介 加密解密工具箱是一个功能强大的在线加密解密工具&#xff0c;支持多种主流加密算法&#xff0c;包括 Base64、AES、RSA、DES…...

抖音短视频安卓版流畅度测评 - 真实

对于抖音短视频安卓版的流畅度&#xff0c;实际体验可以受到多方面因素的影响&#xff0c;比如设备性能、系统优化、网络情况和应用本身的优化程度。以下是一些常见的测评维度和抖音安卓版本流畅度的实际表现&#xff1a; 1.启动速度 抖音的启动速度通常较快&#xff0c;但如果…...

基于javaweb的SSM+Maven机房管理系统设计与实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论文…...

发动机试验台底座:汽车研发的关键支撑(北重制造厂家)

发动机试验台底座是汽车研发过程中的重要组成部分&#xff0c;它承载着发动机及相关部件&#xff0c;在试验过程中提供稳定的支撑。底座的设计和制造对于发动机试验的精度和可靠性至关重要&#xff0c;它需要具备足够的承载能力、稳定性和耐久性&#xff0c;以确保试验过程的准…...

firefox 136.0.4版本离线安装MarkDown插件

系统&#xff1a;centos7.9 firefox&#xff1a;136.0.4 1、下载firefox的版本 Directory Listing: /pub/firefox/releases/136.0.4/ 选择自己想要的版本&#xff0c;这边选的是 linux-x86-64/en-US/版本的。 Directory Listing: /pub/firefox/releases/136.0.4/linux-x86…...

Linux红帽:RHCSA认证知识讲解(九)标准输入输出、重定向、过滤器与管道

Linux红帽&#xff1a;RHCSA认证知识讲解&#xff08;九&#xff09;标准输入输出、重定向、过滤器与管道 前言一、标准输入与输出、重定向&#xff0c;使用过滤器筛选文件信息1.1 Linux 的标准输入与输出1.2 什么是输入重定向1.3 输出重定向1.4 标准错误输出重定向1.5 使用过滤…...

移动端六大语言速记:第6部分 - 错误处理与调试

移动端六大语言速记:第6部分 - 错误处理与调试 本文将对比Java、Kotlin、Flutter(Dart)、Python、ArkTS和Swift这六种移动端开发语言在错误处理与调试方面的特性,帮助开发者理解和掌握各语言的异常处理机制。 6. 错误处理与调试 6.1 异常处理 各语言异常处理的语法对比:…...

云计算:基础、概念与未来展望

摘要 云计算已从一个新兴技术概念演变为现代信息技术&#xff08;IT&#xff09;基础设施的基石。它彻底改变了企业和个人存储、访问、处理数据以及部署应用程序的方式。本文旨在深入探讨云计算的核心概念、基本原理、关键技术、服务模型、部署模式及其带来的优势与挑战。通过…...

hanzi-writer-miniprogram真机显示不出来Path2D问题已解决(真机能显示了!)

要么cdn字库问题&#xff0c;要么是下面的问题 cdn问题 即https://cdn.jsdelivr.net/npm/hanzi-writer-data的问题 见node_modules\hanzi-writer\dist\hanzi-writer.js的getCharDataUrl 这里笔画数据是在线请求cdn, 请求多了有时候也会失败 Path2D的问题 我安装的是最新的一…...

智慧园区大屏如何实现全局监测:监测意义、内容、方式

智慧园区的价值不容小觑呀&#xff0c;可以说园区的大部分数据都在这个大屏上&#xff0c;监测数据越多&#xff0c;那么大屏的价值就越大。很多小伙伴拿到需求后感觉无从下手&#xff0c;本文在这里智慧园区大屏可以监测哪些内容、监测的意义、监测的方式等&#xff0c;欢迎点…...

LangChain核心解析:掌握AI开发的“链“式思维

0. 思维导图 1. 引言 🌟 在人工智能快速发展的今天,如何有效地利用大语言模型(LLM)构建强大的应用成为众多开发者关注的焦点。前面的课程中,我们学习了正则表达式以及向量数据库的相关知识,了解了如何处理文档并将其附加给大模型。本章我们将深入探讨LangChain中的核心概…...

[CISSP] [6] 密码学和对称密钥算法

密码学的目标 1. 机密性&#xff08;Confidentiality&#xff09; 目标&#xff1a;保护信息不被未授权访问。 通过 加密&#xff08;Encryption&#xff09;技术确保数据只能被授权方解密和读取。主要方法&#xff1a; 对称加密&#xff08;AES、3DES&#xff09;&#xff…...

思维链编程模式下可视化医疗编程具体模块和流程架构分析(全架构与代码版)

引言 随着人工智能在医疗领域的广泛应用&#xff0c;医疗AI思维链可视化编程工具应运而生&#xff0c;旨在为非技术背景的医疗从业者提供便捷的AI模型开发平台。这个工具通过直观的可视化界面&#xff0c;简化了AI模型的构建过程&#xff0c;帮助用户高效完成数据处理、模型训…...

AI与玩具结合的可行性分析

文章目录 一、市场需求&#xff1a;教育与陪伴的双重驱动&#xff08;一&#xff09;教育需求&#xff08;二&#xff09;情感陪伴需求&#xff08;三&#xff09;消费升级 二、技术发展&#xff1a;赋能玩具智能化&#xff08;一&#xff09;AI技术的成熟&#xff08;二&#…...

软考又将迎来新的改革?

3月26日&#xff0c;工信部所属事业单位发布了一则招聘公告&#xff0c;其中&#xff0c;工信教考中心面相符合条件的博士招聘1名“考务处技术研究岗”的人员&#xff0c;具体岗位内容&#xff1a; 其岗位简介中&#xff0c;有一条“研究、制定考试技术改革方案&#xff0c;并组…...

Python入门(8):文件

1. 文件基本概念 文件&#xff1a;存储在计算机上的数据集合&#xff0c;Python 通过文件对象来操作文件。 文件类型&#xff1a; 文本文件&#xff1a;由字符组成&#xff0c;如 .txt, .py 二进制文件&#xff1a;由字节组成&#xff0c;如 .jpg, .mp3 2. 文件打开与关闭…...

HTML5 Video(视频)学习笔记

一、HTML5 视频简介 HTML5 引入了 <video> 元素&#xff0c;用于在网页上嵌入视频内容。这种方式取代了传统的 Flash 插件&#xff0c;使得视频的展示更加标准化和便捷。HTML5 的 <video> 元素为开发者提供了一种简单且兼容性强的方法来嵌入视频&#xff0c;同时也…...