红黑树的介绍与实现
前言
前面我们介绍了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,本期分享就到这里,好兄弟,我们下期再见!
结束语:简单的事重复做,重复的是坚持做!
相关文章:
红黑树的介绍与实现
前言 前面我们介绍了AVL树,AVL树是一棵非常自律的树,有着严格的高度可控制!但是正它的自律给他带来了另一个问题,即虽然他的查找效率很高,但是插入和删除由于旋转而导致效率没有那么高。我们上一期的结尾说过经常修改…...
easyexcel将csv转为excel处理数字问题
使用easyexcel可以将csv格式的文件转为.xlsx文件,但是csv中有很多数字,比如:"123","12.34","-111",默认情况下会将其作为字符串写入.xlsx文件,就如同下面一样,字符类型的数字…...
DDMA信号处理以及数据处理的流程---随机目标生成
Hello,大家好,我是Xiaojie,好久不见,欢迎大家能够和Xiaojie一起学习毫米波雷达知识,Xiaojie准备连载一个系列的文章—DDMA信号处理以及数据处理的流程,本系列文章将从目标生成、信号仿真、测距、测速、cfar…...
爬虫实现思路
现在的人工智能太强大了,只要有问题,输入后就能给出大致的实现思路;我看了下确实没问题,只需要更改一些细节基本就能拿来就用;下面是我实验经历: 问题: c# 书写爬虫爬取按动物名称,…...
神经网络 torch.nn---Non-Linear Activations (ReLU)
ReLU — PyTorch 2.3 documentation torch.nn - PyTorch中文文档 (pytorch-cn.readthedocs.io) 非线性变换的目的 非线性变换的目的是为神经网络引入一些非线性特征,使其训练出一些符合各种曲线或各种特征的模型。 换句话来说,如果模型都是直线特征的…...
【微服务】使用kubekey部署k8s多节点及kubesphere
kubesphere官方部署文档 https://github.com/kubesphere/kubesphere/blob/master/README_zh.md kubuctl命令文档 https://kubernetes.io/zh-cn/docs/reference/kubectl/ k8s资源类型 https://kubernetes.io/zh-cn/docs/reference/kubectl/#%E8%B5%84%E6%BA%90%E7%B1%BB%E5%9E…...
目标检测数据集 - 垃圾桶满溢检测数据集下载「包含VOC、COCO、YOLO三种格式」
数据集介绍:垃圾桶满溢检测数据集,真实场景高质量图片数据,涉及场景丰富,比如城市道边垃圾桶满溢、小区垃圾桶满溢、社区垃圾桶满溢、农村道边垃圾桶满溢、垃圾集中处理点垃圾桶满溢、公园垃圾桶满溢数据等。数据集标注标签划分为…...
6.9总结(省赛排位赛1)
省赛排位赛1省赛排名赛1 - Virtual Judge (vjudge.net) 思路: 其实就是一个斐波拉契数列,当前项前两项之和,先将范围内的数全部存起来放进一个数组,再进行累加查询 代码: #define _CRT_SECURE_NO_WARNINGS 1 #incl…...
58.CountdownLatch
用来进行线程同步协作,等待所有线程完成倒计时。 构造参数用来初始化等待计数值,await方法用来等待计数归零,countDown方法用来让计数减一。 CountdownLatch普通使用 @Slf4j public class CountdownLatchDemo {public static void main(String[] args) {CountDownLatch c…...
Java数据结构准备工作---常用类
文章目录 前言1.包装类1.1.包装类基本知识1.2.包装类的用途1.3.装箱和拆箱1.3.1.装箱:1.3.2.拆箱 1.4 包装类的缓存问题 2.时间处理类2.1.Date 时间类(java.util.Date)2.2.DateFormat 类和 SimpleDateFormat 类2.3.Calendar 日历类 3.其他常用类3.1.Math类3.2.Rando…...
SD 使用教程
SD 换脸步骤 使用Stable Diffusion (SD) 进行换脸的基本步骤可以从以下几个方面概述,这里以一种常见的方式为例,结合了插件的使用来简化流程: 准备工作 安装必要的软件和插件:首先,确保你已经安装了Stable Diffusion…...
Sylar---协程调度模块
协程调度模块: 首先是协程任务类FiberAndThread,包括协程,函数,指定的线程;提供了五个构造函数,只传协程的智能指针,只传函数对象,传协程智能指针的指针,函数对象指针,还…...
iOS Hook 崩溃
0x00 崩溃重现 被 Hook 的类,是这样的: interface ViewController : UIViewController endimplementation ViewController - (void)loadView {[super loadView];NSLog("%s", __func__); }- (void)test {NSLog("%s", __func__); }-…...
区间预测 | Matlab实现LSTM-ABKDE长短期记忆神经网络自适应带宽核密度估计多变量回归区间预测
区间预测 | Matlab实现LSTM-ABKDE长短期记忆神经网络自适应带宽核密度估计多变量回归区间预测 目录 区间预测 | Matlab实现LSTM-ABKDE长短期记忆神经网络自适应带宽核密度估计多变量回归区间预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现LSTM-ABKDE长…...
linux内核下rapidio(TSI721)相关笔记汇总
1 驱动的安装 和 主要功能(doorbell, DMA, rionet)的简单测试 linux5.4 下使用rapidio(tsi721)的笔记记录_kernel-rapidio-CSDN博客 2 机理分析 linux内核下,rapidio网络系统建立的过程(枚举 和 发现)_linux rapidio-CSDN博客 linux内核下,(rapidio)T…...
从GPT-4到GPT-4o:人工智能的进化与革命
从GPT-4到GPT-4o:人工智能的进化与革命 近期,OpenAI推出了最新版本的人工智能模型——GPT-4o,引发了广泛的关注和讨论。在这篇文章中,我们将对GPT-4o进行全面评价,包括与前一版本GPT-4的对比分析,GPT-4o的…...
【Java】/*抽象类和接口*/
目录 一、抽象类和抽象方法 1.1 概念 1.2 特性 1.3 作用 二、接口 2.1 概念及定义 2.2 特性 2.3 实例:笔记本电脑 2.4 一个类可以实现多个接口 2.5 一个接口可以继承多个接口 2.6 Comparable接口 2.7 Comparator接口 2.8 Cloneable接口 2.9 浅拷贝和深…...
TCP/IP协议介绍——三次握手四次挥手
TCP/IP(Transmission Control Protocol/Internet Protocol,传输控制协议/网际协议)是指能够在多个不同网络间实现信息传输的协议簇。TCP/IP协议不仅仅指的是TCP 和IP两个协议,而是指一个由FTP、SMTP、TCP、UDP、IP等协议构成的协议…...
[C++]基于C++opencv结合vibe和sort tracker实现高空抛物实时检测
【vibe算法介绍】 ViBe算法是一种高效的像素级视频背景建模和前景检测算法。以下是对该算法的详细介绍: 一、算法原理 ViBe算法的核心思想是通过为每个像素点存储一个样本集,利用该样本集与当前像素值进行比较,从而判断该像素是否属于背景…...
Apache Doris 基础 -- 数据表设计(模式更改)
用户可以通过schema Change操作修改现有表的模式。表的模式主要包括对列的修改和对索引的修改。这里我们主要介绍与列相关的Scheme更改。对于与索引相关的更改,可以查看数据表设计/表索引,查看每个索引的更改方法。 1、术语 基本表(Base Ta…...
【机器学习】【遗传算法】【项目实战】药品分拣的优化策略【附Python源码】
仅供学习、参考使用 一、遗传算法简介 遗传算法(Genetic Algorithm, GA)是机器学习领域中常见的一类算法,其基本思想可以用下述流程图简要表示: (图参考论文:Optimization of Worker Scheduling at Logi…...
电子电气架构 ---车载安全防火墙
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…...
解决selenium加载网页过慢影响程序运行时间的问题
在用selenium爬取动态加载网页时,发现网页内容都全部加载完了,但是页面还在转圈,并且获取页面内容的代码也没有执行,后面了解到selenium元素操作等方法是需要等待页面所有元素完全加载完成后才开始执行的,所以在页面未…...
何为云防护?有何作用
云防护又称云防御。随着Internet互联网络带宽的增加和多种DDOS 黑客工具的不断发布,云计算越演越热,DDOS拒绝服务攻击的实施越来越容易,DDOS攻击事件正在成上升趋势。出于商业竞争、打击报复和网络敲诈等多种因素,导致很多IDC 托管…...
2024050402-重学 Java 设计模式《实战责任链模式》
重学 Java 设计模式:实战责任链模式「模拟618电商大促期间,项目上线流程多级负责人审批场景」 一、前言 场地和场景的重要性 射击🏹需要去靶场学习、滑雪🏂需要去雪场体验、开车🚗需要能上路实践,而编程…...
centos7安装字体
1.安装命令 yum install fontconfig #字体库命令 yum install mkfontscale #更新字体命令2.安装字体(注意权限问题) 进入目录 /usr/share/fonts ,该目录是 centos7 字体库的默认安装目录。在该目录下创建一个文件夹 ekp (名字…...
Llama模型家族之使用 ReFT技术对 Llama-3 进行微调(三)为 ReFT 微调准备模型及数据集
LlaMA 3 系列博客 基于 LlaMA 3 LangGraph 在windows本地部署大模型 (一) 基于 LlaMA 3 LangGraph 在windows本地部署大模型 (二) 基于 LlaMA 3 LangGraph 在windows本地部署大模型 (三) 基于 LlaMA…...
学习Canvas过程中2D的方法、注释及感悟一(通俗易懂)
1.了解Canvas: Canvas是前端一个很重要的知识点,<canvas>标签用于创建画布绘制图形,通过JavaScript进行操作。它为开发者提供一个动态绘制图形的区域,用于创建图标、游戏动画、图像处理等。 对于能够熟练使用Canvas的开发者…...
《TCP/IP网络编程》(第十三章)多种I/O函数(2)
使用readv和writev函数可以提高数据通信的效率,它们的功能可以概括为**“对数据进行整合传输及发送”**。 即使用writev函数可以将分散在多个缓冲中的数据一并发送,使用readv函数可以由多个缓冲分别接受,所以适当使用他们可以减少I/O函数的调…...
Java集合汇总
Java中的集合框架是Java语言的核心部分,提供了强大的数据结构来存储和操作对象集合。集合框架位于java.util包中,主要可以分为两大类:Collection(单列集合)和Map(双列集合)。下面是对它们的总结…...
wordpress 美术馆/大型网站建设平台
模板介绍 精美PPT模板设计,简约创意个人简历求职竞聘PPT模板。一套岗位晋升幻灯片模板,内含橙色多种配色,精美风格设计,动态播放效果,精美实用。 一份设计精美的PPT模板,可以让你在汇报演讲时脱颖而出。 …...
网站推广方案策划书/怎么可以让百度快速收录视频
熬夜删掉Linux中删除不掉的文件<?xml:namespace prefix o ns "urn:schemas-microsoft-com:office:office" />今天在做关于shell的作业时不知咋的生成了一个一–z开头的文件,怎么删都删不掉,如图:<?xml:namespace pref…...
上海微网站建设/低价刷粉网站推广
为什么80%的码农都做不了架构师?>>> 如果nginx一直出现 nginx bad gateway 5XX 之类的 首先用python manager.py runserver 0.0.0.0:8000 并且把debug改为True 输出调试信息 因为99%的可能性是项目源码中出现了问题 或者是uwsgi和nginx链接的socke…...
北京网站优化托管/引流推广网站
配置kotlin的环境变量,添加到path 下载kotlin的编译器工具 下载后解压放到固定目录下,找到bin文件夹添加到path,配置完成后使用 路径如:D:\kotlinc\bin 命令: kotlinc -version 查看是否安装成功 安装插件 安装Kot…...
网站开发算是固定资产吗/今日广州新闻头条
Camtasia是一款专业的屏幕录制和软件,用户可以通过它来录制自己的电脑屏幕,包括实时动画、PPT播放,兼以音频录制、视频制作等功能,支持用户一站式完成屏幕录制和后期处理操作。 自软件发行以来,Camtasia每个版本都在不…...
wordpress首页文章摘要/seo工程师招聘
1、WHERE字句的查询条件里有不等于号(WHERE column!...),MYSQL将无法使用索引2、类似地,如果WHERE字句的查询条件里使用了函数(如:WHERE DAY(column)...),MYSQL将无法使用索引3、在J…...