C++ - 红黑树 介绍 和 实现

前言

 前面 学习了 AVL树,AVL树虽然在 查找方面始终拥有 O(log N )的极高效率,但是,AVL 树在插入 ,删除等等 修改的操作当中非常的麻烦,尤其是 删除操作,在实现当中细节非常多,在实现上非常难掌控。具体可以看以下两篇文章:
C++ - AVL 树 介绍 和 实现 (上篇)_chihiro1122的博客-CSDN博客

C++ - AVL树实现(下篇)- 调试小技巧_chihiro1122的博客-CSDN博客

 而 红黑树 在构建就下个对容易一些了。

红黑树 介绍 

红黑树和 AVL树一样,都是 二叉平衡搜索树,红黑树的构建是在 每个结点除了 孩子的链接关系,和值以外,加一个位置存储该结点的颜色,一个结点的颜色只有   red 和 black 两种。红黑树通过对 任意一条路径上的各个结点的颜色限制确保没有一条路径会比其他路径长出两倍

 相比于 AVL树,红黑树对于平衡的要求更加宽松,他只是要求最长路径最多是 最短路径的两倍;

而 AVL树 是严格限死了 左右子树高度不能超过 2 。

也就是说,红黑树在高度上 要比 AVL树要高一些。

虽然 红黑树 在平衡上更加的宽松,但是实际的效率却依旧非常的高,并不比 AVL树差,至于为什么我们在下述来验证。

而且 ,红黑树不会进行 旋转,尽管在 AVL树当中旋转是常量级的,但是,数量多了之后,还是有很多的消耗。

在AVL树当中,插入和删除都要经过很多次的旋转。也就造成不小的消耗。

而 AVL树 相比于 红黑树 多出来的消耗在 红黑树看来是没有必要的。

 如下图当中的分析:

红黑树树的性质(规则)

  • 每个结点不是红色就是黑色
  •  根节点是黑色的
  • 如果一个节点是红色的,则它的两个孩子结点必须是黑色的 。(每一条从根结点开始的路径,没有重复的红色结点)
  • 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点。(红黑树当中每一条路径上,黑色结点数是相同的)(在一条路径当中,黑色结点的占比一定 大于 或 等于 1 /2 )
  • 每一个 “叶子结点” 都是黑色的,注意:此处的叶子结点不是我们以前理解的 最后一个结点,在红黑树当中的 这里的 “叶子结点” 实际上是最后一个 NULL 结点(所有的 NIL 结点都是 黑色的)。如下图所示:
     

 在红黑树当中,对 这个 NULL 结点进行了重新定义,取名为 NIL 结点而且我们上述所说的路径,是指:从根结点到最后的 NIL 结点,为一条路径。

 就比如上述,如果不加上 NULL ,那么计算出来的路径数目就是 5 条;但是如果加上 NULL ,来计算的话就是 11 条,而11 跳才是正确。

 也就是说,如果你不加上最后一个 NULL 结点来用 红黑树的规则来判断 一颗树是不是红黑树的话,机会出错,比如下述例子:
 

 如果,你按照我们以前认为,最后一个结点就是叶子结点,而且在算上路径时候没有加上最后的NULL。那么你就会认为,这是一颗红黑树;

但,实际上这不是一颗红黑树。 

 其实,按照上述 的 第五条性质,其实就是在每一条路径的后面都加上了 一个 黑色结点。

insert()插入函数 

 首先我们来想一下,当一颗红黑树当中已经有结点了,如下所示,那么当我们插入一个结点是插入红色的结点还是 插入一个黑色的结点好呢?

 如果插入 黑色结点就会违反 条件4,;如果插入 红色结点就会违反 条件3;

如果要违反条件的话,可能是违反 条件3更好。

因为 ,如果违反条件4,就会影响全部路径;而如果 违反条件3 就只会 影响一个路径,不会是全部路径。

如下所示,我们插入一个黑色结点:
 

 在插入A结点之后,到A结点的这条路径的 黑色结点的个数就 +1了,但是其他路径上都没有变化,此时就影响到其他 路径了。

但是如果插入的是一个红色的结点,只影响一个路径,所以,当发生错误的时候我们可以修正:

 上述插入的B结点,在 22 的下面,22 是红色结点,这个位置就不行但是如果是插入在 15 的下面,15 是一个黑色的结点,是可以的。

 如果 红色结点 B 插入到 22 的下面,此时 B 的父亲结点是 红色的,说明B 一定会有 父亲的父亲,也就是说 grandfather。因为 整棵树只有根结点才没有父亲,而根结点必须是 黑色的:

 如上所示,红色结点一定有 父亲。也就是说,出现上述情况的话,grandfather 结点是肯定存在的。

 红黑树的插入,修改过程

 我们上述讨论了,插入的结点必须是红色的结点,插入红色的结点的话,就会影响到父亲(新插入结点的路径)。

 具体例子理解

所以,此时我们要从该路径上进行修改。

 此时违法的规则就是 B 和 22 两个红色结点连在一起了,所以我们就想着把 22 边黑,但是变黑的话,就会影响这棵树当中的黑节点个数。

所以,在更新完 25 这颗子树之后,可能会像 AVL 树当中的平衡因子一样往上更新的。

如上述图,单次对于子树的修改:
需要先找到 新插入结点 父亲的 亲兄弟(uncle)。

如果 uncle 存在且为 红,那么就把 parent 和 uncle 都变 黑:

 但是此时我们发现,变黑之后,对于 22 和 27 这两条路径来说,黑色结点个数相比于其他路径,在增多了一个,所以,此时我们在让 grandfather 结点变红,就可以解决黑色结点个数问题:

 但是,对于 grandfather 结点不一定都是这种的处理方法:

  • 如果 grandfather 结点没有父亲,说明此时 grandfather 结点是 整棵树的根结点,那么把 grandfather 结点变红就行。
  • 如果 grandfather 有父亲,grandfather 是 黑色,就结束了。
  • 如果 grandfather 有父亲,grandfather 是 红色,有和上述问题一样了,需要找 uncle。

 我们继续来看上述例子:
 

 在上述修改之后, 发现 17 和 25 又是两个红色结点链接在一起了,此时就要对这个链接关系进行修改,此时就好比是 新插入了 25 结点。

找 uncle(8),uncle 为红,就把 parent 和 uncle 一起改为黑,此时的 grandfather 没有父亲结点了,就不用再变黑了:

 如果按照上述的过程来修改的话,我们发现,相当于是在每一条路径当中都增加了一个 黑色结点。

黑色结点其实是有好处的,比如上述的例子的那种,我们插入的新结点一定是 红色的,那么插入在黑色结点后面就不用像上述一样进行修改,如上图所示,我们只有在 6 后面和在 B 后面插入结点才会像上述一样进行修改。

 上述是 有uncle的情况,如果 parent 没有亲兄弟,也就是 cur 没有 uncle的话,应该怎么办呢?

 如上述所示,cur 没有 uncle,我们不能直接把 parent 变黑,因为会多出一个 黑色结点;有人又说,多出来一个黑色结点的话,把 grandfather 在变 红不就行了,但是 如果把 grandfather 变红的话,指向 uncle 的路径上就少一个 黑色结点了

而且我们发现,如果在 cur 位置插入的话,13 的右子树的上的路径长度已经比 左子树 上的路径长度多出 2 倍了。

所以,不管是否 多出 2 倍,在没有 uncle的情况下,我们应该进行旋转来调整位置

判断旋转方式还是和在 AVL树当中判断方式一样,旋转方式请看一下博客:

C++ - AVL 树 介绍 和 实现 (上篇)_chihiro1122的博客-CSDN博客

 像上述,就是发生了的右单旋,但是旋转之后发现,颜色上还是 违反了规则。

所以此时要变色。

所以,当uncle 不存在的时候,处理规则就是 旋转 + 变色。 

 uncle存在且为红的情况,在修改之后也有可能会出现 最长路径和最短路径 长度超过两倍的情况(uncle 为黑的情况):
 

 修改:

 此时我们发现,如果我们单纯的把 17 变黑,已经不能解决问题了,此时也是需要旋转的。

13 的右子树已经明显的高了,要对右子树进行旋转,此时发生左单旋。如果是下述情况就是 双旋:
 

 cur , parent , grandfather 构成折线,就要发生双旋。

但是,在 AVL 树当中我们判断旋转的方式是利用 平衡因子的方式来判断的,但是在红黑树当中没有平衡因子,我们需要判断  cur , parent , grandfather 三者的链接关系来 确认要哪一种旋转。

 我没发现,红黑树优势不仅仅在于旋转变少了,他是在修改过程当中就会把很多结点变黑,那么在以后插入当中,插入到黑节点后面就不必再进行修改了。

 小总结

红黑树的插入,关键看 uncle。

  • 如果 uncle 存在且为红,变色+ 往上进行处理
  • 如果 uncle存在且为黑,旋转+变色+往上进行处理
  • 如果 uncle不存在,旋转 + 变色 + 往上进行处理

 抽象图理解 红黑树插入过程

 按照上述说明,出现需要修改的情况,都是 红红黑的结构,也就是 cur 为红, parent 为红,grandfather 为黑的情况,而修改过程当中,判别不用的修改方式,是看 uncle 的。

我们先来总结一下 解决方案

  • 情况一: cur为红,p为红,g为黑,u存在且为红。                                                                                                                                                                                                                                       解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。                                  
  • 情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑。                                                                                                                                                                                                                         解决方法:p为g的左孩子,cur为p的左孩子,则进行右单旋转;                                                               p为g的右孩子,cur为p的右孩子,则进行左单旋转。                                                               p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;                                                           p为g的右孩子,cur为p的左孩子,则针对p做右单旋转

具体情况分析:

情况一:

 比如上述是 的抽象图,他可能是 一颗子树,也可能是 整棵树。

那么,当我们在 a ,b 后面插入的话,可能就会应发 情况一

当 c/d/e 子树每条路径当中只有 一个 黑色结点

 如上述情况我们就要使用 情况一的处理方式了。

 对于情况一,因为不旋转,对于新结点的插入位置,是不管的,主要是 在 a 和 b 的四个位置插入都是引发情况一,或者不引发。

至于 cur 可能本来就是 grandfather 的黑色结点,只是在 a,b当中没有个进行修改,把 grandfather 修改为了 红色。

当然,还有 cur 本来就是 新增结点的情况,也就是 a  和 b 两个子树都是 空:

 但是处理方式还是情况一的方式。


 当 c/d/e 子树每条路径当中有两个黑色结点:

 

 c/d/e 子树每条路径当中有两个黑色结点的情况太多了,上图当中只是简单列出一些。

在 上述 只有一个黑结点的路径当中,只用一层就修改,就修改到了 cur 结点;而在当前 路径当中有两个黑结点,就是修改两层,才修改到 cur 结点的颜色的。(这里的层,一层就是 一个 cur parent grandfather 组合子树)

 反正就是 路径当中有两个黑色结点的情况,要比 只有一个黑结点的情况复杂得多,但是最终还是属于情况一,都是使用 情况一的方式来进行解决。

 情况二:

 情况二当中的uncle有两种情况:
一种是 uncle不存在:

 如果 uncle 不存在,那么 cur 一定是新插入的结点,因为如果 cur 不是新插入结点,那么 cur 和 p 当中一定至少有一个是 黑色的。

 另一种是 uncle 的颜色是 黑色:

 如果 uncle 是黑色的话,cur 这个结点一定不是 新插入的结点,因为,假设在插入之前,也就相当于是没有 cur 这个结点,g - > p -> null 路径 和  g -> u -> ········ 路径 两个路径上 黑色结点个数不一样。所以,插入之前的树不可能是红黑树。

所以,uncle 为黑的这种情况,一定是  cur 当中的子树发生了修改,往上修改的时候影响到了 cur :

 修改过程如下:都是旋转 + 变色的方式。只不过旋转当中有四种方式,具体要看 cur parent grandfather 三者之间的链接关系

  像上述这种情况, c 子树的路径当中每条之后一个黑结点,d 和 e 可能为空,也可能有一个 红结点。

 同样的,c  d  e 三个子树当中的黑色结点可以按照上述的规律增加,那么就是不同的具体例子。

比如,c是 两个黑色结点的红黑树,那么 d 和 e 就是一个结点的红黑树。

 红黑树的结果是无穷无尽的,每一种结构也相对复杂,但是上述都是属于 情况二,解决方式都是一样的。

  • 情况二当中,旋转+变色之后,就不需要网上进行处理了,为在旋转+变色之后,这棵子树的根结点一定是 黑色的,不会是 红色的,那么黑色的结点不管和 红色的结点还是和 黑色的结点链接都是不会出现问题的。
  • 在情况一当中之所以要继续往上更新,是因为,情况一修改出来的根结点是红色的。

 红黑树的验证

 红黑树的检测分为两步:

  • 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
  •  检测其是否满足红黑树的性质

 检测是否满足红黑树性质:
 

bool CheckColor(Node* root, int blacknum, int benchamark){// 当走到叶子结点的 null 指针处,也就是 NIL结点处if (root == nullptr){// 如果计算出的路径黑色结点长度 和 外部计算的不一样// 说明不是红黑树if (blacknum != benchamark){cout << "路径黑色结点个数不一样" << endl;return false;}return true;}// 用于递归计算 路径的黑色结点个数if (root->_color == BLACK)blacknum++;// 如果当前结点为 红色,且当前结点的父亲也是红色,就不是红黑树if (root->_parent && root->_parent->_color == RED && root->_color == RED){cout << "有连续红色" << endl;return false;}// 左右子树递归return CheckColor(root->_left, blacknum, benchamark)&& CheckColor(root->_right , blacknum, benchamark);}// 外部调用接口bool isBalance(){return isBalance(_root);}// 内部封装函数bool isBalance(Node* root){if (root == nullptr)return true;// 如果整棵树的 根结点不是 黑色的就不是红黑树if (root->_color != BLACK){cout << "根结点不是黑色" << endl;return false;}// 基准值// 在递归外部计算出左路第一条路径的 黑色结点值int benchmark = 0;Node* cur = root;while(cur){if (cur->_color == BLACK)benchmark++;cur = cur->_left;}return CheckColor(root, 0, benchmark);}

我们使用两个函数相互调用来实现,外层函数判断整棵树的根结点是不是 黑色的;

而且计算左边第一条路径的黑色结点个数。其实可以随便找一条路径,因为这个只是一个基准值,不需要是正确的,因为红黑树当中的每一条路径都需要黑色结点数相同。

然后用内层函数递归遍历红黑树。

内层函数当中,计算出每一条黑色结点的个数;判断是否有连续的红色结点;

检测其是否满足二叉搜索树,就直接中序遍历打印就行了,这里就不实现了。

 红黑树的删除

红黑树的删除和 AVL树一样,都比插入要复杂,可以看下面这个博客来了解:
红黑树 - _Never_ - 博客园 (cnblogs.com)

完整代码实现

 

#pragma once// 节点的颜色
enum Color { RED, BLACK };// 红黑树节点的定义
template<class K, class V>
struct RBTreeNode
{RBTreeNode(pair<K ,V> kv  , Color color = RED): _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _color(color){}RBTreeNode<K, V>* _left; // 节点的左孩子RBTreeNode<K, V>* _right; // 节点的右孩子RBTreeNode<K, V>* _parent; // 节点的双亲(红黑树需要旋转,为了实现简单给出该字段)pair<K, V> _kv; // 节点的值域Color _color; // 节点的颜色
};template<class K ,class V>
class RBTree
{typedef RBTreeNode<K,V> Node;
public:bool insert(pair<K , V> kv){// 搜索二叉树的插入逻辑// // 如果当前树为空,直接用头指针只想新结点if (_root == nullptr){_root = new Node(kv);_root->_color = BLACK;return true;}// 不为空接着走Node* cur = _root;    // 用于首次插入时候指针的迭代Node* parent = nullptr;while (cur){// 如果当前新插入的 key 值比 当前遍历的结点 key 值大if (cur->_kv.first < kv.first){// 往右迭代器parent = cur;cur = cur->_right;}// 反之else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{// 如果相等,就不插入,即插入失败return false;}}// 此时已经找到 应该插入的位置cur = new Node(kv);cur->_color = RED;// 再次判断大小,判断 cur应该插入到 parent 的那一边if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}// 链接 新插入结点 cur 的_parent 指针cur->_parent = parent;// 红黑树调整高度(平衡高度)的逻辑while (parent && parent->_color == RED){// parent 为 红,parent->_parent 一定不为空Node* grandfather = parent->_parent;// 如果父亲是在 祖父的左if (parent == grandfather->_left){Node* uncle = grandfather->_right;// 叔叔存在且为红if (uncle && uncle->_color == RED){// 变色parent->_color = uncle->_color = BLACK;grandfather->_color = RED;// 向上迭代cur = grandfather;parent = cur->_parent;}// uncle 不存在 或者 存在且为黑else{if (cur == parent->_left){//       g//     p//  cRotateR(grandfather);parent->_color = BLACK;grandfather->_color = RED;}else  // cur == parent->_right{//       g//     p//         cRotateL(parent);RotateR(grandfather);cur->_color = BLACK;grandfather->_color = RED;}break; // 不需要再往上更新}}else  // parent = grandfather->_right{Node* uncle = grandfather->_left;// uncle 存在 且 uncle 的颜色是红色if (uncle && uncle->_color == RED){parent->_color = uncle->_color = BLACK;grandfather->_color = RED;cur = grandfather;parent = cur->_parent;}else // 不存在 或者 存在且为黑色{if (cur == parent->_right){//  g//     p//        cRotateL(grandfather);parent->_color = BLACK;grandfather->_color = RED;}else // cur = parent->_left{//   g//     p//  cRotateR(parent);RotateL(grandfather);cur->_color = BLACK;grandfather->_color = RED;}break; // 不用再往上更新}}}// 不管上述如何修改,红黑树的根结点永远是黑的// 所以我们这里既直接硬性处理_root->_color = BLACK;return true;}void RotateL(Node* parent){Node* cur = parent->_right; // 存储 parent 的右孩子Node* curleft = cur->_left; // 存储 cur 的左孩子parent->_right = curleft;if (curleft)                // 判断 cur 的左孩子是否为空{curleft->_parent = parent;  // 不为空就 修改 cur 的左孩子的_parent 指针}cur->_left = parent;// 留存一份 根结点指针Node* ppnode = parent->_parent;parent->_parent = cur;// 如果parent 是根结点if (parent == _root){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}}void RotateR(Node* parent){Node* cur = parent->_left;Node* curRight = cur->_right;parent->_left = curRight;if (curRight){curRight->_parent = parent;}cur->_right = parent;Node* ppnode = parent->_parent;parent->_parent = cur;if (parent == _root){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}}bool CheckColor(Node* root, int blacknum, int benchamark){// 当走到叶子结点的 null 指针处,也就是 NIL结点处if (root == nullptr){// 如果计算出的路径黑色结点长度 和 外部计算的不一样// 说明不是红黑树if (blacknum != benchamark){cout << "路径黑色结点个数不一样" << endl;return false;}return true;}// 用于递归计算 路径的黑色结点个数if (root->_color == BLACK)blacknum++;// 如果当前结点为 红色,且当前结点的父亲也是红色,就不是红黑树if (root->_parent && root->_parent->_color == RED && root->_color == RED){cout << "有连续红色" << endl;return false;}// 左右子树递归return CheckColor(root->_left, blacknum, benchamark)&& CheckColor(root->_right , blacknum, benchamark);}// 外部调用接口bool isBalance(){return isBalance(_root);}// 内部封装函数bool isBalance(Node* root){if (root == nullptr)return true;// 如果整棵树的 根结点不是 黑色的就不是红黑树if (root->_color != BLACK){cout << "根结点不是黑色" << endl;return false;}// 基准值// 在递归外部计算出左路第一条路径的 黑色结点值int benchmark = 0;Node* cur = root;while(cur){if (cur->_color == BLACK)benchmark++;cur = cur->_left;}return CheckColor(root, 0, benchmark);}
private:Node* _root = nullptr;
};

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

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

相关文章

如何套用模板制作大屏?

在山海鲸可视化的资源中心里内置了大量的二维、三维大屏模板&#xff0c;大家可以根据需要找到自己想要的模板&#xff0c;然后点击下载直接进行使用。 有需要可自行前往哔哩哔哩账号中观看相关内容的视频教程↓↓↓ 山海鲸可视化的个人空间-山海鲸可视化个人主页-哔哩哔哩视频…

cocos2dx查看版本号的方法

打开文件&#xff1a;项目根目录\frameworks\cocos2d-x\docs\RELEASE_NOTES.md 知道引擎版本号的意义&#xff1a; 1.面试中经常被问到(面试官想知道你会不会查版本号&#xff0c;你会查也不一定会去看&#xff0c;如果你去看了说明你是一个有心人&#xff0c;或者想深入研究下…

Java内存泄漏知识(软引用、弱引用等)

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、商业变现、人工智能等&#xff0c;希望大家多多支持。 未经允许不得转载 目录 一、导读二、概览三、相关知识3.1 内存…

工作中Git管理项目和常见问题处理

工作中Git管理项目和常见问题处理 Git仓库的管理方式为什么会出现无法push到线上处理方法 Git仓库的管理方式 共用统一仓库,不同开发人员使用不同分支 步骤 下载代码 git clone <url>查看分支 git branch创建并切换分支 git checkout -b dev分支名称保持和远程分支一…

计算机视觉与深度学习-图像分割-视觉识别任务03-实例分割-【北邮鲁鹏】

目录 参考定义Mark R-CNN结构思路Mask R-CNN训练阶段使用的Mask样例Mask R-CNN实例分割结果Mask R-CNN检测姿态 参考 论文题目&#xff1a;Mask R-CNN 论文链接&#xff1a;论文下载 论文代码&#xff1a;Facebook代码链接&#xff1b;Tensorflow版本代码链接&#xff1b; K…

微信里怎么添加阅读付费链接

在微信中添加阅读付费链接为主题&#xff0c;首先需要开通微信支付商户号&#xff0c;然后创建自定义菜单&#xff0c;并设置跳转到付费链接的逻辑。以下是详细步骤&#xff1a; 注册并开通微信支付商户号 在微信开放平台上注册并开通微信支付商户号。这一步需要营业执照、法…

出现 conda虚拟环境默认放在C盘 解决方法

目录 1. 问题所示2. 原理分析3. 解决方法3.1 方法一3.2 方法二1. 问题所示 通过conda配置虚拟环境的时候,由于安装在D盘下,但是配置的环境默认都给我放C盘 通过如下命令:conda env list,最后查看该环境的确在C盘下 2. 原理分析 究其根本原因,这是因为默认路径没有足够的…

go sync.Map包装过的对象nil值的判断

被sync.Map包装过的nil 对象&#xff0c;是不能直接用if xxx nil的方式来判断的 func testnil() *interface{} {return nil }func main() {var ptr *interface{}test : testnil()//p &Person{}fmt.Printf("ptr 的值为 : %v\n", ptr)fmt.Printf("ptr 的值…

uni-app:实现元素在屏幕中的居中(绝对定位absolute)

一、实现水平居中 效果 代码 <template><view><view class"center">我需要居中</view></view> </template><style>.center {position: absolute;left:50%;transform: translateX(-50%);border:1px solid black;} </s…

20个提升效率的JS简写技巧,告别屎山!

JavaScript 中有很多简写技巧&#xff0c;可以缩短代码长度、减少冗余&#xff0c;并且提高代码的可读性和可维护性。本文将介绍 20 个提升效率的 JS 简写技巧&#xff0c;助你告别屎山&#xff0c;轻松编写优雅的代码&#xff01; 移除数组假值 可以使用 filter() 结合 Bool…

智能机器学习:人工智能的下一个巨大飞跃

文章目录 第1节&#xff1a;智能机器学习的背景1.1 传统机器学习1.2 人工智能 第2节&#xff1a;智能机器学习的定义2.1 智能机器学习的原理2.1.1 自主学习2.1.2 强化学习2.1.3 自适应性 2.2 智能机器学习的关键技术2.2.1 深度学习2.2.2 强化学习算法2.2.3 自然语言处理&#x…

Stable Diffusion 参数介绍及用法

大模型 CheckPoint 介绍 作用&#xff1a;定调了作图风格&#xff0c;可以理解为指挥者 安装路径&#xff1a;models/Stable-diffusion 推荐&#xff1a; AnythingV5Ink_v32Ink.safetensors cuteyukimixAdorable_midchapter2.safetensors manmaruMix_v10.safetensors counterf…

线性约束最小方差准则(LCMV)波束形成算法仿真

常规波束形成仅能使得主波束对准目标方向&#xff0c;从而在噪声环境下检测到目标&#xff0c;但无法对复杂多变的干扰做出响应&#xff0c;所以不能称之为真正意义上的自适应滤波。自适应阵列处理指的是采用自适应算法对空间阵列接收的混合信号进行处理&#xff0c;又可称为自…

晨控CK-FR08系列读写器与LS可编程逻辑控制器MODBUSRTU连接手册

晨控CK-FR08系列读写器与LS可编程逻辑控制器MODBUSRTU连接手册 晨控CK-FR08是一款基于射频识别技术的高频RFID标签读卡器&#xff0c;读卡器工作频率为13.56MHZ&#xff0c;支持对I-CODE 2、I-CODE SLI等符合ISO15693国际标准协议格式标签的读取。读卡器内部集成了射频部分通信…

【Linux】生产者和消费者模型

生产者和消费者概念基于BlockingQueue的生产者消费者模型全部代码 生产者和消费者概念 生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。 生产者和消费者彼此之间不直接通讯&#xff0c;而通过这个容器来通讯&#xff0c;所以生产者生产完数据之后不用等待…

微信小程序 工具使用(HBuilderX)

微信小程序 工具使用:HBuilderX 一 HBuilderX 的下载二 工具的配置2.1 工具 --> 设置 --> 运行配置2.1.1 微信开发者工具路径2.1.2 node 运行配置 2.2 插件 工具 --> 插件安装2.2.1 下载插件 三 微信小程序端四 同步运行五 BUG5.1 nodemon在终端无法识别 一 HBuilderX…

AnV-X6使用及总结

目录 1 简介2 安装3 基础概念3.1 画布Graph3.2 基类Cell3.3 节点Node3.4 边Edge 4 使用4.1 创建节点4.2 节点连线4.3 事件系统 5 总结 1 简介 AntV是一个数据可视化&#xff08;https://x6.antv.antgroup.com/&#xff09;的工具&#xff08;https://antv.vision/zh/ &#xf…

【深度学习实验】卷积神经网络(四):自定义二维汇聚层:最大汇聚(max pooling)和平均汇聚(average pooling)

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 三、实验内容 0. 导入必要的工具包 1. Conv2D&#xff08;二维卷积层&#xff09; 2. Pool2D&#xff08;二维汇聚层&#xff09; 理论知识 a. 初始化 b. 前向传播(最大汇聚层) c. 前向传播(平均汇聚…

智能的障碍:符号化

基于事实与价值叠加的算计与基于事实的计算有着明显的区别。 基于事实的计算是指根据已有的客观事实和数据进行计算和推理。在这种计算中&#xff0c;只考虑和利用与事实相关的信息和数据&#xff0c;目的是得出合理的、基于客观事实的结论。例如&#xff0c;使用数学公式和逻辑…

CentOS密码重置

背景&#xff1a; 我有一个CentOS虚拟机&#xff0c;但是密码忘记了&#xff0c;偶尔记起可以重置密码&#xff0c;于是今天尝试记录一下&#xff0c;又因为我最近记性比较差&#xff0c;所以必须要记录一下。 过程&#xff1a; 1、在引导菜单界面&#xff08;grub&#xff…