红黑树的介绍与实现

前言

前面我们介绍了AVL树,AVL树是一棵非常自律的树,有着严格的高度可控制!但是正它的自律给他带来了另一个问题,即虽然他的查找效率很高,但是插入和删除由于旋转而导致效率没有那么高。我们上一期的结尾说过经常修改的话就不太适合AVL树了,而红黑树更加适合!OK,本期就来介绍一下赫赫有名的红黑树!

本期内容介绍

什么是红黑树

红黑树的实现

红黑树的效率分析以及应用

什么是红黑树?

红黑树是1972年由Rudolf Bayer发明的,当时被称为平衡二叉B树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的 " 红黑树 "。

红黑树是一种二叉搜索树,但在每个结点增加了一个存储位表示结点的颜色,可以是红色或黑色通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径比其他的路径长出两倍,因而是接近平衡的!

OK,这就是一棵红黑树:

最长节点的路径就是一黑一红的交替,最短的就是两黑:

红黑树的性质

1、每个结点要么是红色,要么是黑色

2、根节点是黑色

3、如果一个结点是红色,则它的两个孩子结点是黑色

4、对于任意一个节点,从该结点到其所有的后代叶子结点的简单路径上,均包含相同的黑色结点

5、每个叶子结点就是黑色的(此处的叶子结点指的是空节点)

上面的这5条性质就是限制红黑树平衡的规则!其中4最重要的下来是3,基本所有的操作都是围着4进行的!!!

红黑树的实现

OK,上面介绍了红黑树是一种二叉搜索树,只不过是在每个结点添加了一个存储颜色的颜色位,所以它的大框架还是和搜索树一样的,所以我们就先搭一个框架出来!

enum Col//颜色
{RED,BLACK
};template<class K, class V>
struct RBTreeNode
{RBTreeNode<K, V>* _left;//左孩子RBTreeNode<K, V>* _right;//右孩子RBTreeNode<K, V>* _parent;//父结点pair<K, V> _kv;//数据域Col _col;//颜色RBTreeNode(const pair<K,V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(RED)//新插入的节点默认是红色{}
};template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:RBTree():_root(nullptr),_size(0){}private:Node* _root;//根节点size_t _size;//节点的数量
};

思考:新插入的节点应该是红色还是黑色?为什么?

新插入的节点一定是红色!

因为新插入的节点是红色可能违反性质3,但一定不违反性质4

如果新插入的是黑色一定违反性质4,也就是在部分子路径上增加了黑色节点。所以插入的新节点一定是红色,即使红色违反了性质3也是比较好控制的!

OK,这里依旧是采用的三叉链,原因是方便找父亲

红黑树的插入

上面介绍了,红黑树的本质是一种二叉搜索树,所以先不管它的颜色和高度如何调节,先把搜索树的那一套给整出来:

bool Insert(const pair<K, V>& kv)
{Node* cur = _root;//当前节点Node* parent = nullptr;//插入位置的父亲//第一次插入if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;//根节点是黑色return true;}//寻找插入的位置while (cur){if (cur->_kv.first < kv.first)//插入节点的key比当前节点的key大{parent = cur;cur = cur->_right;//去右边找}else if(cur->_kv.first > kv.first)//插入节点的key比当前节点的key小{parent = cur;cur = cur->_left;//去左边找}else{return false;//插入的节点存在}}//找到插入位置cur = new Node(kv);//链接if (parent->_kv.first > cur->_kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;//颜色和高度调整//....return true;
}

OK,还是先来验证一下当前的逻辑对不对,所以走个中序看看是不是有序即可:由于中序要根节点,而类外面是无法访问的,所以我们还是和以前一样搞成子函数或提供get和set方法;这里就搞成子函数了:

中序遍历

void _InOrder(Node* root)
{if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);
}

OK,没问题,下面我们就来讨论一下红黑树的维持平衡的方式:变色和旋转!

红黑树的变色和旋转

由于新插入的节点一定是红色的,此时分为两种情况,1、父亲为黑  2、父亲为红

如果父亲为黑,不违反任何性质,插入结束;

如果父亲为红,看看叔叔,此时叔叔有三类情况:1、叔叔存在且为红  2、叔叔不存在  3、叔叔存在为黑

如果,叔叔存在且为红:变色(将父亲和叔叔变黑色,将爷爷变红色),继续更新

如果,叔叔不存在或存在且为黑,旋转 + 变色(如果孩子是父亲的左/右,先对孩子父亲进行左/右旋,在对爷爷进行左/右)

OK,这里看着可能会有些迷,看看下面的导图会很清楚:

OK,理解了上述的表达,下面我来画图解释一下:

parent是黑色插入结束

parent为红,叔叔存在且为红(变色)

父亲和叔叔变黑,爷爷变红,继续向上更新

这里只画了parent在爷爷的左边的情况,如果parent在爷爷的右边和这个是一样的!

parent为红,叔叔不存在会存在为黑(变色 + 旋转)

如果parent在爷爷的左,且cur在父亲的左,对爷爷进行右单旋;

如果parent在爷爷的左,且cur在父亲的右,先对parent左单旋,在对爷爷进行右单旋;

如果parent在爷爷的右,且cur在父亲的右,对爷爷进行左单旋;

如果parent在爷爷的右,且cur在父亲的左,先对parent右单旋,在对爷爷进行左单旋;

OK,废话不多说直接上代码:

bool Insert(const pair<K, V>& kv)
{Node* cur = _root;//当前节点Node* parent = nullptr;//插入位置的父亲//第一次插入if (_root == nullptr){_root = new Node(kv);_size++;//插入成功节点数+1_root->_col = BLACK;//根节点是黑色return true;}//寻找插入的位置while (cur){if (cur->_kv.first < kv.first)//插入节点的key比当前节点的key大{parent = cur;cur = cur->_right;//去右边找}else if(cur->_kv.first > kv.first)//插入节点的key比当前节点的key小{parent = cur;cur = cur->_left;//去左边找}else{return false;//插入的节点存在}}//找到插入位置cur = new Node(kv);//链接if (parent->_kv.first > cur->_kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;//颜色和高度调整while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (parent == grandfather->_left)//父亲在爷爷的左{Node* uncle = grandfather->_right;//叔叔就是父亲的右//父亲存在且为红 -> 变色if (uncle && uncle->_col == RED){grandfather->_col = RED;parent->_col = uncle->_col = BLACK;//继续向上调整cur = grandfather;parent = cur->_parent;}else//叔叔不存在或存在但为黑 -> 变色 + 旋转{if (cur == parent->_left)//cur在父亲的左{//		   g//	   p		u// cRotateR(grandfather);//旋转parent->_col = BLACK;//变色grandfather->_col = RED;}else//cur在父亲的右{//		  g//	  p		  u//        cRotateL(parent);//旋转RotateR(grandfather);cur->_col = BLACK;//变色grandfather->_col = RED;}break;//旋转后不需要再向上更新了}}else//parent在爷爷的右{Node* uncle = grandfather->_left;//叔叔在父亲的左//叔叔存在且为红 -> 变色if (uncle && uncle->_col == RED){grandfather->_col = RED;parent->_col = uncle->_col = BLACK;//继续向上调整cur = grandfather;parent = cur->_parent;}else//叔叔不存在或存在但为黑 -> 变色 + 旋转{if (cur == parent->_left)//cur在父亲的左 {//		  g//	  u		   p//        cRotateR(parent);//旋转RotateL(grandfather);cur->_col = BLACK;//变色grandfather->_col = RED;}else{//		  g//	  u		   p//                 cRotateL(grandfather);//旋转parent->_col = BLACK;//变色grandfather->_col = RED;}break;//旋转后不需要再向上更新了}}}_root->_col = BLACK;//保证根节点永远是黑色_size++;//插入成功节点数+1return true;
}

旋转

红黑树的旋转没有,AVL的复杂,只有左右单旋且没有平衡因子!整体的逻辑和AVL一样的,这里不在详细介绍了!

void RotateR(Node* parent)
{Node* subL = parent->_left;//父亲的左Node* subLR = subL->_right;//左子树的右Node* ppNode = parent->_parent;//parent的父节点,方便旋转后的链接parent->_left = subLR;//将左子树的右给父亲的做if (subLR)subLR->_parent = parent;subL->_right = parent;//parent做左子树的右parent->_parent = subL;if (parent == _root)//parent是根{_root = subL;//此时的新根就是subLppNode = nullptr;}else//parent不是根{//将新的根连接到ppNodeif (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}
}void RotateL(Node* parent)
{Node* subR = parent->_right;//父亲的右Node* subRL = subR->_left;//右子树的左Node* ppNode = parent->_parent;//parent的父节点,方便旋转后的链接parent->_right = subRL;//将右子树的左连接到parent的右if (subRL)subRL->_parent = parent;subR->_left = parent;//parent连接到subR的左parent->_parent = subR;if (parent == _root)//parent是根{_root = subR;//此时的新根就是subRppNode = nullptr;}else//parent不是根{//将新的根连接到ppNodeif (ppNode->_left == parent){ppNode->_left = subR;}else{ppNode->_right = subR;}subR->_parent = ppNode;}
}

OK,我们验证一下,判断一下是否是平衡的:

是否平衡

先获取任意一条路径的黑色节点,然后通过dfs进行检查每个结点是不是符合红黑树的规则!

如果出现连续的红色节点,不符合!判断方式:当出现红色节点时,检查其父节点是否是红色的,如果是则不符合!

如走到空了,检查该条路径的黑色节点和一开始求出的是否一致,不一致则不符合!

当前节点符合,去检查其左右!

bool IsBalance()
{if (_root && _root->_col == RED){return false;//根为红,一定不是红黑树}int black = 0;//获取任意一条路径的黑色节点(这里是最左路)Node* cur = _root;while (cur){if (cur->_col == BLACK){black++;}cur = cur->_left;}return Check(_root, black, 0);
}
bool Check(Node* root, const int black, int num)
{if (root == nullptr){//当走到叶子节点的时候和其他路径的黑色节点的个数不一样if (black != num){return false;}return true;}//存在连续的红色节点if (root->_col == RED && root->_parent && root->_parent->_col == RED){cout << root->_kv.first << " :存在连续的红色节点" << endl;return false;}//遇到黑色节点++if (root->_col == BLACK){num++;}//当前节点符合红黑树,它的左右子树也要都符合return Check(root->_left, black, num) && Check(root->_right, black, num);
}

OK,验证一下:

OK,么有问题!下面把其他的接口补一下!

Size

由于我们提前记录了_size所以直接返回成员_size即可!

size_t Size()
{return _size;
}

Find

和以前的搜索树一样,大了去右边找,小了去左边找!

Node* Find(const K& key)
{Node* cur = _root;while (cur){if (cur->_kv.first < key)//插入节点的key比当前节点的key大{cur = cur->_right;//去右边找}else if (cur->_kv.first > key)//插入节点的key比当前节点的key小{cur = cur->_left;//去左边找}else{return cur;//找到了}}return nullptr;//没找到
}

再来一组随机的测试用例:插入1亿个随机值,看看时间和是否平衡(注意这里一亿个节点在32位debug下可能内存空间不够,可以把他改成64的release地址空间大一点)

void Test()
{const int N = 100000000;vector<int> v;v.reserve(N);srand(time(0));for (size_t i = 0; i < N; i++){v.push_back(rand() + i);//cout << v.back() << endl;}size_t begin2 = clock();RBTree<int, int> t;for (auto e : v){t.Insert(make_pair(e, e));//cout << "Insert:" << e << "->" << t.IsBalance() << endl;}size_t end2 = clock();cout << "time :" << end2 - begin2 << endl;cout << t.IsBalance() << endl;
}

红黑树的删除:请参考这篇博客 :红黑树的删除

全部源码

#pragma onceenum Col//颜色
{RED,BLACK
};template<class K, class V>
struct RBTreeNode
{RBTreeNode<K, V>* _left;//左孩子RBTreeNode<K, V>* _right;//右孩子RBTreeNode<K, V>* _parent;//父结点pair<K, V> _kv;//数据域Col _col;//颜色RBTreeNode(const pair<K,V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(RED)//新插入的节点默认是红色{}
};template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:RBTree():_root(nullptr),_size(0){}bool Insert(const pair<K, V>& kv){Node* cur = _root;//当前节点Node* parent = nullptr;//插入位置的父亲//第一次插入if (_root == nullptr){_root = new Node(kv);_size++;//插入成功节点数+1_root->_col = BLACK;//根节点是黑色return true;}//寻找插入的位置while (cur){if (cur->_kv.first < kv.first)//插入节点的key比当前节点的key大{parent = cur;cur = cur->_right;//去右边找}else if(cur->_kv.first > kv.first)//插入节点的key比当前节点的key小{parent = cur;cur = cur->_left;//去左边找}else{return false;//插入的节点存在}}//找到插入位置cur = new Node(kv);//链接if (parent->_kv.first > cur->_kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;//颜色和高度调整while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (parent == grandfather->_left)//父亲在爷爷的左{Node* uncle = grandfather->_right;//叔叔就是父亲的右//父亲存在且为红 -> 变色if (uncle && uncle->_col == RED){grandfather->_col = RED;parent->_col = uncle->_col = BLACK;//继续向上调整cur = grandfather;parent = cur->_parent;}else//叔叔不存在或存在但为黑 -> 变色 + 旋转{if (cur == parent->_left)//cur在父亲的左{//		   g//	   p		u// cRotateR(grandfather);//旋转parent->_col = BLACK;//变色grandfather->_col = RED;}else//cur在父亲的右{//		  g//	  p		  u//        cRotateL(parent);//旋转RotateR(grandfather);cur->_col = BLACK;//变色grandfather->_col = RED;}break;//旋转后不需要再向上更新了}}else//parent在爷爷的右{Node* uncle = grandfather->_left;//叔叔在父亲的左//叔叔存在且为红 -> 变色if (uncle && uncle->_col == RED){grandfather->_col = RED;parent->_col = uncle->_col = BLACK;//继续向上调整cur = grandfather;parent = cur->_parent;}else//叔叔不存在或存在但为黑 -> 变色 + 旋转{if (cur == parent->_left)//cur在父亲的左 {//		  g//	  u		   p//        cRotateR(parent);//旋转RotateL(grandfather);cur->_col = BLACK;//变色grandfather->_col = RED;}else{//		  g//	  u		   p//                 cRotateL(grandfather);//旋转parent->_col = BLACK;//变色grandfather->_col = RED;}break;//旋转后不需要再向上更新了}}}_root->_col = BLACK;//保证根节点永远是黑色_size++;//插入成功节点数+1return true;}Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_kv.first < key)//插入节点的key比当前节点的key大{cur = cur->_right;//去右边找}else if (cur->_kv.first > key)//插入节点的key比当前节点的key小{cur = cur->_left;//去左边找}else{return cur;//找到了}}return nullptr;//没找到}void InOrder(){return _InOrder(_root);}size_t Size(){return _size;}bool IsBalance(){if (_root && _root->_col == RED){return false;//根为红,一定不是红黑树}int black = 0;//获取任意一条路径的黑色节点(这里是最左路)Node* cur = _root;while (cur){if (cur->_col == BLACK){black++;}cur = cur->_left;}return Check(_root, black, 0);}private:void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);}void RotateR(Node* parent){Node* subL = parent->_left;//父亲的左Node* subLR = subL->_right;//左子树的右Node* ppNode = parent->_parent;//parent的父节点,方便旋转后的链接parent->_left = subLR;//将左子树的右给父亲的做if (subLR)subLR->_parent = parent;subL->_right = parent;//parent做左子树的右parent->_parent = subL;if (parent == _root)//parent是根{_root = subL;//此时的新根就是subLppNode = nullptr;}else//parent不是根{//将新的根连接到ppNodeif (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}}void RotateL(Node* parent){Node* subR = parent->_right;//父亲的右Node* subRL = subR->_left;//右子树的左Node* ppNode = parent->_parent;//parent的父节点,方便旋转后的链接parent->_right = subRL;//将右子树的左连接到parent的右if (subRL)subRL->_parent = parent;subR->_left = parent;//parent连接到subR的左parent->_parent = subR;if (parent == _root)//parent是根{_root = subR;//此时的新根就是subRppNode = nullptr;}else//parent不是根{//将新的根连接到ppNodeif (ppNode->_left == parent){ppNode->_left = subR;}else{ppNode->_right = subR;}subR->_parent = ppNode;}}bool Check(Node* root, const int black, int num){if (root == nullptr){//当走到叶子节点的时候和其他路径的黑色节点的个数不一样if (black != num){return false;}return true;}//存在连续的红色节点if (root->_col == RED && root->_parent && root->_parent->_col == RED){cout << root->_kv.first << " :存在连续的红色节点" << endl;return false;}//遇到黑色节点++if (root->_col == BLACK){num++;}//当前节点符合红黑树,它的左右子树也要都符合return Check(root->_left, black, num) && Check(root->_right, black, num);}private:Node* _root;//根节点size_t _size;//节点的数量
};

红黑树的效率分析以及应用

红黑树和AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(log_2 N),红黑树不追 求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数, 所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

红黑树的应用

C++的STL库中的map/set/multimap/multiset底层都是红黑树实现的

一些Java的库;例如: TreeMap和TreeSet等

一些Linux的内核,例如:进程调度等

OK,本期分享就到这里,好兄弟,我们下期再见!

结束语:简单的事重复做,重复的是坚持做!

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为何PHP使用率 大幅度下降!需求量几乎为零!

用PHP的人越来越少的主要原因包括&#xff1a;市场竞争加剧、新技术的出现、性能和安全问题、以及开发者社区的变化。市场竞争加剧是其中一个突出的因素。随着Python、Node.js等现代编程语言的崛起&#xff0c;它们提供了更好的性能、更简洁的语法和更丰富的框架&#xff0c;逐…

技术与业务的完美融合:大数据BI如何真正提升业务价值

数据分析有一点经典案例 沃尔玛的啤酒和尿布案例 开始做BI的时候&#xff0c;大家肯定都看过书&#xff0c;那么一定也看过一个经典的案例&#xff0c;就是沃尔玛的啤酒和尿布的案例。这个案例确实很经典&#xff0c;但其实是一个失败的案例。为什么这么说呢&#xff1f;很明显…

Spring Boot 实现动态数据源配置

前言 之前在CSDN博客以及某站看了教程&#xff0c;不免觉得有点不知如何下手&#xff0c;好在最后融合了以下&#xff0c;得出了一个比较简单的配置动态数据源的过程。 首先项目是Spring Boot的单体项目&#xff0c;我们的需求是要连接多个数据库&#xff0c;那么就需要配置多个…

Nvidia/算能 +FPGA+AI大算力边缘计算盒子:大疆RoboMaster AI挑战赛

NVIDIA Jetson TX2助力机器人战队斩获RoboMaster AI挑战赛冠亚军 一个汇聚数百万机器人专家与研究人员的赛场&#xff0c;一场兼具工程、策略和团队挑战的较量&#xff0c;说的正是近日刚刚在澳大利亚布里斯本ICRA大会上闭幕的大疆RoboMaster AI挑战赛今年的冠军I Hiter以及亚军…

nodejs最新某东h5st(4.7.2)参数分析与javascript逆向纯算法还原(含算法源码)(2024-06-09)

一、作者声明&#xff1a; 文章仅供学习交流与参考&#xff01;严禁用于任何商业与非法用途&#xff01;否则由此产生的一切后果均与作者无关&#xff01;如有侵权&#xff0c;请联系作者本人进行删除&#xff01; 二 、写在前面 h5st从4.1一路更新到4.7.2&#xff0c;逐渐vmp…

【Redis】什么是Redis缓存 雪崩、穿透、击穿?(一篇文章就够了)

目录 什么是Redis? Redis的正常存储流程&#xff1f; 什么是Redis缓存雪崩&#xff1f; 缓存雪崩 缓存预热 缓存失效时间的随机性 什么是Redis缓存穿透&#xff1f; 缓存穿透 缓存空对象 BloomFilter&#xff08;布隆过滤器&#xff09; 什么是Redis缓存击穿&#…

VBA高级应用30例应用2实现在列表框内及列表框间实现数据拖动

《VBA高级应用30例》&#xff08;版权10178985&#xff09;&#xff0c;是我推出的第十套教程&#xff0c;教程是专门针对高级学员在学习VBA过程中提高路途上的案例展开&#xff0c;这套教程案例与理论结合&#xff0c;紧贴“实战”&#xff0c;并做“战术总结”&#xff0c;以…

深度学习简单概述

概述 理论上来说&#xff0c;参数越多的模型复杂度越高、容量越大&#xff0c;这意味着它能完成更复杂的学习任务。但复杂模型的训练效率低&#xff0c;易陷入过拟合。随着云计算、大数据时代的到来&#xff0c;计算能力的大幅提高可以缓解训练的低效性&#xff0c;训练数据的…

大型零售企业总部到分公司数据发放,有没有更优化的方案?

大型零售企业在市场经济中扮演重要角色&#xff0c;是保证基础商品生产、流通和供给的重要一环。随着企业发展&#xff0c;很多大型零售企业都会在全国、乃至全球各地开设分公司&#xff0c;用以降低生产和运营成本&#xff0c;更好地提供本地化服务。 为了保证总部与分公司间信…

GAT1399协议分析(9)--图像上传

一、官方定义 二、wirechark实例 有前面查询的基础,这个接口相对简单很多。 请求: 文本化: POST /VIID/Images HTTP/1.1 Host: 10.0.201.56:31400 User-Agent: python-requests/2.32.3 Accept-Encoding: gzip, deflate Accept: */* Connection: keep-alive content-type:…

软件游戏提示msvcp120.dll丢失的解决方法,总结多种靠谱的解决方法

在电脑使用过程中&#xff0c;我们可能会遇到一些错误提示&#xff0c;其中之一就是“找不到msvcp120.dll”。那么&#xff0c;msvcp120.dll是什么&#xff1f;它对电脑有什么影响&#xff1f;有哪些解决方法&#xff1f;本文将从以下几个方面进行探讨。 一&#xff0c;了解msv…

【漏洞复现】Apache OFBiz 路径遍历导致RCE漏洞(CVE-2024-36104)

0x01 产品简介 Apache OFBiz是一个电子商务平台&#xff0c;用于构建大中型企业级、跨平台、跨数据库、跨应用服务器的多层、分布式电子商务类应用系统。是美国阿帕奇(Apache)基金会的一套企业资源计划(ERP)系统。该系统提供了一整套基于Java的Web应用程序组件和工具。 0x02 …

docker从入门到精通

一、Docker基本命令 1. Docker的常用命令 帮助命令 docker version # docker版本信息 docker info # 系统级别的信息&#xff0c;包括镜像和容器的数量 docker 命令 --help 帮助文档 镜像命令 docker images 查看所有本地主机上的镜像 [rootiZ2zeg4ytp0whqtmxbsqiiZ…

C++ MPI多进程并发

下载 用法 mpiexec -n 8 $PROCESS_COUNT x64\Debug\$TARGET.exe 多进程并发启动 mpiexec -f hosts.txt -n 3 $PROCESS_COUNT x64\Debug\$TARGET.exe 联机并发进程&#xff0c;其它联机电脑需在相同路径下有所有程序 //hosts.txt 192.168.86.16 192.168.86.123 192.168…

八、C语言:操作符详解

一、移位操作符 1.1左移操作 左边丢弃&#xff0c;右边补0 1.2右移操作 算数右移&#xff1a;右边丢弃&#xff0c;左边补原符号位 逻辑右移&#xff1a;右边丢弃&#xff0c;左边补0 int main() {int a -1;int b a >> 1;printf("b%d\n",b);return 0; } 原码…

医学领域科技查新点提炼方法!---附案例分析

医学领域的查新项目研究范围较广&#xff0c;涉及基础医学、临床医学、中医学、预防医学、卫生学、特种医学等众多与人类健康和疾病有关的科学。查新目的主要包括立项、成果鉴定和报奖&#xff0c;有的期刊投稿也要求作者提供查新报告。 医学领域查新项目的两极化较明显&#…

2024年CKA模拟系统制作 | step-by-step | 1、基础环境准备

目录 一、软件环境 二、虚拟网络环境准备 1、编辑虚拟网络 2、网络设置 三、新建虚拟主机 1、新建目录 2、新建虚拟主机 四、系统安装 1、装载系统镜像 2、开启虚拟机 3、选择语言 4、键盘选择 5、网络配置 6、代理设置 7、设置软件源 8、存储设置 9、名称设置 …

Edge浏览器视频画中画可拉动进度条插件Separate Window

平时看一些视频的时候&#xff0c;一般需要编辑一些其他的东西&#xff0c;于是开启画中画&#xff0c;但是画中画没有进度条&#xff0c;需要大幅度的倒退前进得回到原视频&#xff0c;很麻烦。这里推荐插件Separate Window&#xff0c;可实现画中画进度条拉动和播放sudu的调节…

Qt图标字体文件中提取字体保存为图片

本文借用别人写的一个IconHelper来做说明。 1. 加载一个字体文件 QScopedPointer<IconHelper> iconHelper(new IconHelper(":/fa-regular-400.ttf", "Font Awesome 6 Pro Regular"));构造函数 IconHelper::IconHelper(const QString &fontFile…

JS(JavaScript)的引用方式介绍与代码演示

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…