C++ | 红黑树

 前言

本篇博客讲解c++中数据结构红黑树,看这篇博客之前请先去看:

C++ | AVL树_c++ avl树能有重复节点吗-CSDN博客

💓 个人主页:普通young man-CSDN博客

⏩ 文章专栏:C++_普通young man的博客-CSDN博客

⏩ 本人giee:   普通小青年 (pu-tong-young-man) - Gitee.com

      若有问题 评论区见📝

🎉欢迎大家点赞👍收藏⭐文章
————————————————


红黑树的概念

        红黑树是一种特殊的二叉搜索树,其每个节点包含一个额外的存储位来表示该节点的颜色,颜色可以是红色黑色。通过施加特定的颜色约束规则于从根到任意叶子节点的所有路径上,红黑树保证了没有一条路径会比其他路径长出两倍以上,从而保持树的整体平衡性。

与AVL树相比,两者都是自平衡二叉搜索树,但它们在实现平衡的方式和效率上有所不同:

  • 红黑树:通过对插入或删除操作后的树结构进行重新着色和旋转操作来维持一种宽松的平衡状态。它确保任何路径上的黑色节点数量相同,并且不允许连续两个红色节点出现。
  • AVL树:对每个节点都维护一个平衡因子(左右子树高度差),并通过旋转操作严格保持这棵树的高度尽可能低,即绝对平衡。

红黑树的规则

  1. 节点颜色每个节点要么是红色,要么是黑色。

  2. 根节点颜色根节点必须是黑色。

  3. 红色节点的子节点如果一个节点是红色,则它的两个子节点都必须是黑色。换句话说,任意路径上不会出现连续的红色节点。

  4. 黑色深度一致对于树中的任意一个节点,从该节点到其可达的任何叶子节点(即NULL或哨兵节点)的所有简单路径上,黑色节点的数量(称为黑色深度)必须相同

了解:

《算法导论》等书籍上补充了⼀条每个叶⼦结点(NIL)都是⿊⾊的规则。他这⾥所指的叶⼦结点 不是传统的意义上的叶⼦结点,⽽是我们说的空结点,有些书籍上也把NIL叫做外部结点。NIL是为了 ⽅便准确的标识出所有路径,《算法导论》在后续讲解实现的细节中也忽略了NIL结点,所以我们知道 ⼀下这个概念即可。

实际编程实现中,这些NIL节点通常被隐式处理或者直接忽略,特别是在高级编程语言中,因为它们通常不显式地表示这些空节点。

结合上面概念,看下方图片:

观看图片的时候看一下这个,避免大家去看平衡因子,可能会觉得是咋保持平衡的,其实就是这个颜色规则来保持平衡


红黑树的效率

如何确保最长路径不超过最短路径的二倍?

这个问题其实就被红黑树的规则给抹杀了,我们可以看一个图:

会发现每条路径的黑节点是相同的,这个我们可以参考红黑树的第四条规则,所以极端场景下,最短路径 就就是全是黑色结点的路径,假设最短路径长度为bh(black height),由规则2和规则3可知,任意⼀条路径不会有连续的红色结点,所以极端场景下,最长的路径就是⼀黑⼀红间隔组成,那么最长路径的长度为2*bh。

综合红黑树的4点规则而言,理论上的全⿊最短路径和⼀黑⼀红的最长路径并不是在每棵红黑树都 存在的。假设任意⼀条从根到NULL结点路径的长度为x,那么bh<=h<=2*bh   

 两种特殊情况:

全是最短路径:

全是最长路径:

通过这些我们就可以看出红黑树的效率:

在红黑树中,假设 N为树中节点的数量,为从根节点到叶节点的最短路径长度。根据红黑树的性质,我们可以得出以下关系:

2^{h - 1}  <= N <  2^{2h - 1}

通过对上述不等式进行推导,可以得出 h = logN 。这意味着在红黑树中,增删查改操作在最坏情况下,即沿着最长路径进行操作时,其时间复杂度为 O(log N) 。因为最长路径长度与最短路径长度 h相关,大致为 2h ,所以时间复杂度为 O(2\log N),简化后仍为 O(log N)

相较于AVL树,红黑树的定义和规则更为抽象。AVL树通过直接控制节点高度差来维持平衡,较为直观。而红黑树则是借助四条颜色约束规则,间接实现了树的近似平衡。尽管二者在效率上处于同一量级,但在插入相同数量节点的情况下,由于红黑树对平衡的控制相对宽松,其旋转次数会更少
 


红黑树实现

红黑树的结构

// 枚举值表⽰颜⾊ 
enum Colour
{RED,BLACK
};
// 这⾥我们默认按key/value结构实现 template<class K, class V>
struct RBTreeNode
{// 这⾥更新控制平衡也要加⼊parent指针 pair<K, V> _kv;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Colour _col;RBTreeNode(const pair<K, V>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr){}
};
template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:
private:Node* _root = nullptr;
};

红黑树的插入

插入是这棵树的主要核心,因为如果你插入错误就不是红黑树

红黑树插入节点规则及处理流程

在红黑树中插入一个新节点时,需要遵循特定的规则,并根据不同情况进行相应处理,具体步骤如下:

步骤 1:确定新增节点的初始颜色
  • 空树插入:若红黑树为空,将新增节点的颜色设置为黑色。这是因为空树插入黑色节点不会违反红黑树的任何规则。
  • 非空树插入:若红黑树非空,将新增节点的颜色设置为红色。这是为了避免破坏红黑树的规则 4(从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点),因为插入黑色节点很可能会破坏此规则,且该规则相对较难维护。
步骤 2:检查插入后是否违反规则

插入节点后,需要检查是否违反红黑树的规则,主要关注规则 3(每个红色节点的两个子节点都是黑色,即从每个叶子到根的所有路径上不能有两个连续的红色节点)。根据父节点的颜色,分以下两种情况处理:

情况 1:父节点为黑色

若父节点为黑色,插入红色节点后没有违反红黑树的任何规则,插入操作结束。

情况 2:父节点为红色

若父节点为红色,插入红色节点后违反了规则 3。由于红黑树的性质,此时祖父节点(父节点的父节点)必定为黑色(因为不能有两个连续的红色节点)。接下来,需要根据叔叔节点(父节点的兄弟节点)的颜色进行进一步的分类处理:

  • 关键颜色固定:此时新增节点 c 为红色,父节点 p 为红色,祖父节点 g 为黑色,这三个节点的颜色是固定的,处理的关键在于叔叔节点 u 的颜色情况
  • 根据叔叔节点 u 分类处理:后续需要根据叔叔节点 u 的颜色分为几种不同情况分别进行旋转和颜色调整操作,以恢复红黑树的性质。具体的分类及处理方式将根据叔叔节点 u 是红色还是黑色,以及节点的相对位置(如左子树或右子树)来确定。

红黑树插入调整:情况1 - 变色

当在红黑树中插入新节点 c 后,若出现 c 为红色、其父节点 p 为红色、祖父节点 g 为黑色,且叔叔节点 u 存在且为红色的情况,可按以下方式处理:

处理步骤

  1. 颜色调整:将 p和 u 的颜色变为黑色,g  的颜色变为红色。
  2. 持续更新:把  g 当作新的 c ,继续向上进行更新操作。

原理分析

由于 p 和 u 原本为红色,g 为黑色,将p 和 u变为黑色后,g 左子树路径和右子树路径各自增加了一个黑色节点。接着把 g 变为红色,相当于保持了以 g为根的子树中每条路径上黑色节点的数量不变。同时,这一操作解决了 c  和 p 连续为红色节点的问题,避免违反红黑树 “不能有两个连续红色节点” 的规则。

d/e/f代表每条路径拥有hb个⿊⾊结点的⼦树,a/b代表每 条路径拥有hb-1个⿊⾊结点的根为红的⼦树,hb>=0

在分析过程中,分别展示了 hb == 0hb == 1 和 hb == 2 的具体情况组合。当 hb 等于 2 时,组合情况多达上百亿种。这些具体样例的作用是辅助我们理解问题,实际上,无论情况数量有多少、情况本身多么复杂,处理方式都是一致的,即进行变色操作后继续向上处理。因此,我们只需关注抽象图来把握整体处理逻辑即可。

红黑树插入调整:情况2 - 单旋(AVL树)+变色

当在红黑树中插入新节点 c 后,若 c 为红色、其父节点 p 为红色、祖父节点 g 为黑色,且叔叔节点 u 存在且为红色时:

节点情况分析

  • 若 u 不存在,c 一定是新增节点。
  • 若 u 存在且为黑,c 一定不是新增节点,c 之前为黑色,是在 c 的子树中插入新节点后,经过类似情况 1 的变色处理,c 从黑色变为红色更新上来的。

处理必要性

为解决 p 和 c 连续红色节点的问题,p 必须变为黑色。又因为 u 不存在或者为黑色,单纯变色无法解决问题,需要结合旋转和变色操作。

p 是 g 的右子节点,c 是 p 的右子节点(左单旋)

  • 操作步骤:以 g 为旋转点进行左单旋,然后将 p 变为黑色,g 变为红色。
  • 操作效果:旋转和变色后,p 成为这棵子树新的根节点。这保证了子树中黑色节点的数量不变,消除了连续的红色节点。并且,无论 p 的父节点是黑色、红色还是为空,都不会违反红黑树规则,无需再往上更新。
    g              p/ \            / \u   p   -->    g   c\c

p 是 g 的左子节点,c 是 p 的左子节点(右单旋)

  • 操作步骤:以 g 为旋转点进行右单旋,然后将 p 变为黑色,g 变为红色。
  • 操作效果:旋转和变色后,p 成为这棵子树新的根节点。既保证了子树中黑色节点的数量不变,又避免了连续的红色节点。而且,不管 p 的父节点状态如何,都不会违反红黑树规则,无需再往上更新。
    g              p/ \            / \p   u   -->    c   g/
c
红黑树插入调整:情况1 - 双旋(AVL树)+变色

当在红黑树中插入节点后,出现 c 为红色、其父节点 p 为红色、祖父节点 g 为黑色,且叔叔节点 u 不存在或者 u 存在但为黑色的情况,具体分析与处理如下:

节点情况分析

  • u 不存在:此时 c 必定是新增节点。
  • u 存在且为黑c 并非新增节点,此前 c 为黑色。是在 c 的子树中插入节点后,经过类似情况 1(叔叔节点为红色时的变色处理)的操作,c 从黑色变为红色并更新上来。

处理必要性分析

为解决 p 和 c 连续红色节点违反红黑树规则的问题,p 必须变为黑色。由于 u 不存在或者为黑色,单纯的变色操作无法恢复红黑树性质,需要结合旋转与变色操作。

情况一:p 是 g 的左子节点,c 是 p 的右子节点

  • 操作步骤
    1. 以 p 为旋转点进行左单旋。
    2. 接着以 g 为旋转点进行右单旋。
    3. 将 c 变为黑色,g 变为红色。
  • 操作效果:操作完成后,c 成为该子树新的根节点。这样既保证了子树中各路径上黑色节点数量不变,又消除了连续的红色节点。而且,无论 c 的父节点状态(黑色、红色或为空)如何,都不会违反红黑树规则,无需再向上更新。
    g             g            c/ \           / \          / \p   u   -->   c   u  -->   p   g\           /                 / \c         p                 p   u

情况二:p 是 g 的右子节点,c 是 p 的左子节点

  • 操作步骤
    1. 以 p 为旋转点进行右单旋。
    2. 再以 g 为旋转点进行左单旋。
    3. 将 c 变为黑色,g 变为红色。
  • 操作效果:操作结束后,c 成为子树新的根节点。同样保证了子树黑色节点数量不变,避免了连续红色节点的出现。并且,不管 c 的父节点状态怎样,都不违反红黑树规则,无需继续向上处理。
    g             g            c/ \           / \          / \u   p   -->   u   c  -->   g   p/             \        / \c               p      u   p

红黑树的插入代码实现

//红黑树
template<class K, class T>
class Red_BlackTree
{typedef  Red_BlackTree_Node<T>  Node;
public://插入//插入bool insert(const T& kv) {//判空:如果是空根节点就是黑色if (_root == nullptr){_root = new Node(kv);_root->_cr = _black;return true;}Node* parent = nullptr;Node* cur = _root;//不为空就插入红色while (cur){if (cur->_kv.first > kv.first) {parent = cur;cur = cur->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(kv);cur->_cr = _red;//链接节点if (parent->_kv.first > kv.first){parent->_left = cur;}else if (parent->_kv.first < kv.first) {parent->_right = cur;}//判断三插链当中的_parent指向cur->_parent = parent;//parent是红色插入说明出现连续的红(违反规则)while (parent && parent->_cr == _red){Node* grandfather = parent->_parent;//parent 如果是leftif (parent == grandfather->_left){//   g// p   u//uncle存在且为红Node* uncle = grandfather->_right;if (uncle && uncle->_cr == _red) {uncle->_cr = _black;parent->_cr = _black;grandfather->_cr = _red;//继续向上更新cur = grandfather;parent = cur->_parent;}//uncle不存在或为黑else{if (parent->_left == cur) {Rotati_r(grandfather);parent->_cr = _black;grandfather->_cr = _red;}else{Rotati_LR(grandfather);cur->_cr = _black;grandfather->_cr = _red;}break;}}else{//   g// u   pNode* uncle = grandfather->_left;//uncle存在且为红if (uncle && uncle->_cr == _red) {uncle->_cr = _black;parent->_cr = _black;grandfather->_cr = _red;//继续向上更新cur = grandfather;parent = cur->_parent;}//uncle不存在或为黑else{if (parent->_right == cur) {Rotati_l(grandfather);parent->_cr = _black;grandfather->_cr = _red;}else{Rotati_RL(grandfather);cur->_cr = _black;grandfather->_cr = _red;}break;}}}//更新到最后不管_root是不是红都设置为黑_root->_cr = _black;return true;}//右旋转void Rotati_r(Node* parent) {Node* subL = parent->_left;Node* sublr = subL->_right;//旋转parent->_left = sublr;//判断是否是空,为空不能链接避免野指针if (sublr)sublr->_parent = parent;//存储parent节点方便后续链接Node* Pparent = parent->_parent;subL->_right = parent;parent->_parent = subL;//判断是否是root或则是局部旋转方便链接上下节点if (parent == _root) {_root = subL;subL->_parent = nullptr;}else {if (Pparent->_left == parent) {Pparent->_left = subL;}else if (Pparent->_right == parent){Pparent->_right = subL;}subL->_parent = Pparent;}}//左旋转void Rotati_l(Node* parent) {Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* parentParent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parentParent == nullptr){_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}}//左右旋void Rotati_LR(Node* parent) {Rotati_l(parent->_left);Rotati_r(parent);}//右左旋void Rotati_RL(Node* parent) {Rotati_r(parent->_right);Rotati_l(parent);}private:Node* _root = nullptr;
};

红黑树的查找

直接看代码吧,这个已经非常简单了,按⼆叉搜索树逻辑实现即可,搜索效率为O(logN)

	Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return nullptr;}

红黑树的验证

在红黑树中,判断其是否符合要求时,直接获取最长路径和最短路径并检查最长路径是否不超过最短路径的 2 倍这种方式不可行。因为即便满足该条件,红黑树仍可能在颜色方面不满足规则,虽然当前可能看似正常,但后续继续插入节点时仍会出现问题。所以,应该检查红黑树的四条基本规则,只要满足这四条规则,就能确保最长路径不超过最短路径的 2 倍。以下是对四条规则检查方法的具体说明:

规则 1:节点颜色类型检查

此x。由于在代码实现中通常会枚举颜色类型,因此在定义节点颜色时就天然保证了该规则的实现,无需额外的检查操作。

规则 2:根节点颜色检查

规则规定根节点必须是黑色。检查时,直接查看红黑树的根节点颜色是否为黑色即可。

规则 3:红色节点子节点颜色检查

规则要求每个红色节点的两个子节点都是黑色。采用前序遍历的方式进行检查,若正向检查红色节点的孩子节点会不太方便,因为孩子节点有两个且不一定都存在。所以,反过来检查每个节点(除根节点外)的父节点颜色更为便捷。若当前节点为红色,而其父节点也为红色,则违反了该规则。

规则 4:各路径黑色节点数量检查

规则要求从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑色节点。同样使用前序遍历,在遍历过程中,利用形参记录从根节点到当前节点的黑色节点数量 blackNum。当前序遍历遇到黑色节点时,blackNum 加 1。当遍历到空节点时,就得到了一条路径上的黑色节点数量。选取任意一条路径的黑色节点数量作为参考值,依次与其他路径的黑色节点数量进行比较,若存在不同,则违反该规则。

// 递归检查红黑树是否满足规则 3(不存在连续红色节点)和规则 4(每条路径上黑色节点数量相同)
// root: 当前递归检查的子树的根节点
// blackNum: 从根节点到当前节点路径上的黑色节点数量
// refNum: 作为参考的一条路径上的黑色节点数量
bool Check(Node* root, int blackNum, const int refNum)
{// 当前节点为空,说明已经遍历完一条路径if (root == nullptr){// 前序遍历⾛到空时,意味着⼀条路径⾛完了 // 比较当前路径的黑色节点数量和参考值if (refNum != blackNum){// 若不相等,输出提示信息,说明存在黑色节点数量不相等的路径cout << "存在⿊⾊结点的数量不相等的路径" << endl;return false;}// 若相等,说明当前路径满足规则 4return true;}// 检查规则 3,即是否存在连续的红色节点// 检查孩⼦不太⽅便,因为孩⼦有两个,且不⼀定存在,反过来检查⽗亲就⽅便多了 // 若当前节点为红色且其父节点也为红色,违反规则 3if (root->_col == RED && root->_parent->_col == RED){// 输出提示信息,指出哪个节点存在连续红色节点cout << root->_kv.first << "存在连续的红⾊结点" << endl;return false;}// 若当前节点为黑色,更新从根节点到当前节点路径上的黑色节点数量if (root->_col == BLACK){blackNum++;}// 递归检查左子树和右子树// 只有当左子树和右子树都满足规则时,整个子树才满足规则return Check(root->_left, blackNum, refNum)&& Check(root->_right, blackNum, refNum);
}// 判断红黑树是否平衡(即是否满足红黑树的所有规则)
bool IsBalance()
{// 若根节点为空,空树被认为是平衡的红黑树if (_root == nullptr)return true;// 规则 2:根节点必须为黑色,若根节点为红色,不满足红黑树规则if (_root->_col == RED)return false;// 计算参考值,即从根节点到其最左子节点路径上的黑色节点数量// 参考值 int refNum = 0;Node* cur = _root;// 沿着左子树向下遍历while (cur){// 若当前节点为黑色,参考值加 1if (cur->_col == BLACK){++refNum;}cur = cur->_left;}// 调用 Check 函数开始递归检查整棵树return Check(_root, 0, refNum);
}

递归展开图:

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

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

相关文章

C语言从入门到进阶

视频&#xff1a;https://www.bilibili.com/video/BV1Vm4y1r7jY?spm_id_from333.788.player.switch&vd_sourcec988f28ad9af37435316731758625407&p23 //枚举常量 enum Sex{MALE,FEMALE,SECRET };printf("%d\n", MALE);//0 printf("%d\n", FEMALE…

WebSocket 详解:全双工通信的实现与应用

目录 一、什么是 WebSocket&#xff1f;&#xff08;简介&#xff09; 二、为什么需要 WebSocket&#xff1f; 三、HTTP 与 WebSocket 的区别 WebSocket 的劣势 WebSocket 的常见应用场景 WebSocket 握手过程 WebSocket 事件处理和生命周期 一、什么是 WebSocket&#xf…

一文讲解Java中Object类常用的方法

在Java中&#xff0c;经常提到一个词“万物皆对象”&#xff0c;其中的“万物”指的是Java中的所有类&#xff0c;而这些类都是Object类的子类&#xff1b; Object主要提供了11个方法&#xff0c;大致可以分为六类&#xff1a; 对象比较&#xff1a; public native int has…

51.和的逆运算问题|Marscode AI刷题

1.题目 问题描述 n 个整数两两相加可以得到 n(n - 1) / 2 个和。我们的目标是&#xff1a;根据这些和找出原来的 n 个整数。 按非降序排序返回这 n 个数&#xff0c;如果无解&#xff0c;输出 "Impossible"。 测试样例 样例1&#xff1a; 输入&#xff1a;n 3, …

Zemax 中的屋脊棱镜建模

光学棱镜是一种透明的光学元件&#xff0c;其表面平坦且抛光&#xff0c;可以折射光线。最常见的棱镜类型是三棱镜&#xff0c;它有两个三角形底座和三个矩形或略呈梯形的表面。棱镜通常由玻璃或丙烯酸等材料制成。当光线以一定角度进入棱镜时&#xff0c;它会在穿过棱镜时发生…

目标跟踪之sort算法(3)

这里写目录标题 1 流程1 预处理2 跟踪 2 代码 参考&#xff1a;sort代码 https://github.com/abewley/sort 1 流程 1 预处理 1.1 获取离线检测数据。1.2 实例化跟踪器。2 跟踪 2.1 轨迹处理。根据上一帧的轨迹预测当前帧的轨迹&#xff0c;剔除到当前轨迹中为空的轨迹得到当前…

双目立体校正和Q矩阵

立体校正 对两个摄像机的图像平面重投影&#xff0c;使二者位于同一平面&#xff0c;而且左右图像的行对准。 Bouguet 该算法需要用到双目标定后外参(R&#xff0c;T) 从上图中可以看出&#xff0c;该算法主要分为两步&#xff1a; 使成像平面共面 这个办法很直观&#xff…

C语言练习(29)

13个人围成一圈&#xff0c;从第1个人开始顺序报号1、2、3。凡报到“3”者退出圈子&#xff0c;找出最后留在圈子中的人原来的序号。本题要求用链表实现。 #include <stdio.h> #include <stdlib.h>// 定义链表节点结构体 typedef struct Node {int num;struct Nod…

5.2 软件需求分析

文章目录 需求分析的意义软件需求的组成需求分析的5个方面需求分析方法 需求分析的意义 需求分析解决软件“做什么”的问题。由于开发人员比较熟悉计算机而不熟悉领域业务&#xff0c;用户比较熟悉领域业务而不熟悉计算机&#xff0c;双方需要通过交流&#xff0c;制定出完整、…

理解神经网络:Brain.js 背后的核心思想

温馨提示 这篇文章篇幅较长,主要是为后续内容做铺垫和说明。如果你觉得文字太多,可以: 先收藏,等后面文章遇到不懂的地方再回来查阅。直接跳读,重点关注加粗或高亮的部分。放心,这种“文字轰炸”不会常有的,哈哈~ 感谢你的耐心阅读!😊 欢迎来到 brain.js 的学习之旅!…

GPU上没程序在跑但是显存被占用

原因&#xff1a;存在僵尸线程&#xff0c;运行完但是没有释放内存 查看僵尸线程 fuser -v /dev/nvidia*关闭僵尸线程 pkill -9 -u 用户名 程序名 举例&#xff1a;pkill -9 -u grs python参考&#xff1a;https://blog.csdn.net/qq_40206371/article/details/143798866

大数据Hadoop入门3

第五部分&#xff08;Apache Hive DML语句和函数使用&#xff09; 1.课程内容大纲和学习目标 2.Hive SQL-DML-load加载数据操作 下面我们随机创建文件尝试一下 先创建一个hivedata文件夹 在这个文件夹中写一个1.txt文件 下面使用beeline创建一张表 只要将1.txt文件放在t_1文件…

网易云音乐歌名可视化:词云生成与GitHub-Pages部署实践

引言 本文将基于前一篇爬取的网易云音乐数据, 利用Python的wordcloud、matplotlib等库, 对歌名数据进行深入的词云可视化分析. 我们将探索不同random_state对词云布局的影响, 并详细介绍如何将生成的词云图部署到GitHub Pages, 实现数据可视化的在线展示. 介绍了如何从原始数据…

const的用法

文章目录 一、C和C中const修饰变量的区别二、const和一级指针的结合const修饰的量常出现的错误是:const和一级指针的结合总结&#xff1a;const和指针的类型转换公式 三、const和二级指针的结合 一、C和C中const修饰变量的区别 C中&#xff1a;const必须初始化&#xff0c;叫常…

AI DeepSeek

DeepSeek 文字解析 上传图片解析 视乎结果出入很大啊&#xff0c;或许我们应该描述更加清楚自己的需求。

996引擎 - NPC-动态创建NPC

996引擎 - NPC-动态创建NPC 创建脚本服务端脚本客户端脚本添加自定义音效添加音效文件修改配置参考资料有个小问题,创建NPC时没有控制朝向的参数。所以。。。自己考虑怎么找补吧。 多重影分身 创建脚本 服务端脚本 Mir200\Envir\Market_Def\test\test001-3.lua -- NPC八门名…

css粘性定位超出指定宽度失效问题

展示效果 解决办法&#xff1a;外层容器添加display:grid即可 完整代码 <template><div class"box"><div class"line" v-for"items in 10"><div class"item" v-for"item in 8">drgg</div>&…

Git客户端工具

Git&#xff08;读音为/gɪt/&#xff09;是一个开源的分布式版本控制系统&#xff0c;可以有效、高速地处理从很小到非常大的项目版本管理。 [1]也是Linus Torvalds为了帮助管理Linux内核开发而开发的一个开放源码的版本控制软件。 可使用工具TortoiseGit&#xff0c;官网下载…

Time Constant | RC、RL 和 RLC 电路中的时间常数

注&#xff1a;本文为 “Time Constant” 相关文章合辑。 机翻&#xff0c;未校。 How To Find The Time Constant in RC and RL Circuits June 8, 2024 &#x1f4a1; Key learnings: 关键学习点&#xff1a; Time Constant Definition: The time constant (τ) is define…

七、深入了解SpringBoot的配置文件

一、配置端口号 通过配置文件application.properties配置修改端口号 修改 application.properties 文件 #端口号修改成 9090 server.port9090运行结果&#xff0c;观察日志 二、配置文件格式 Spring Boot 配置⽂件有以下三种&#xff1a; • application.properties • ap…