【数据结构进阶】红黑树超详解 + 实现(附源码)

🌟🌟作者主页:ephemerals__
🌟🌟所属专栏:数据结构

目录

前言

一、红黑树介绍

二、红黑树原理详解

三、红黑树的实现

1. 节点定义

2. 红黑树类型定义及接口声明

3. 红黑树的插入(重点)

颜色设置

平衡调整

总代码 

4. 红黑树的查找

5. 中序遍历、拷贝构造和析构

6. 检查红黑树是否合法

7. 程序全部代码

总结


前言

        在传统二叉搜索树的基础上,我们学习了AVL树,它通过独特的平衡机制,确保了稳定高效的插入、查找和删除操作。然而,由于其频繁的平衡调整,可能使性能收到一定影响。因此,另一种自平衡二叉搜索树——红黑树应运而生。本篇文章,我们将深入探讨红黑树的实现原理,带你解开其简洁而深邃的设计之美。

        与AVL树相同,之后的红黑树实现当中,我们会将键值对(pair)作为节点数据域。

        如果你不是很了解二叉搜索树、AVL树或pair,可以参阅这两篇文章:

【数据结构】二叉搜索树(二叉排序树)-CSDN博客

【数据结构进阶】AVL树深度剖析 + 实现(附源码)-CSDN博客

        注:若无特殊说明,本文所提到的所有“路径”都指根节点到NULL的路径。

一、红黑树介绍

        红黑树(Red-Black-Tree)是一种自平衡二叉搜索树,但它并非像AVL树那样“严格平衡”,而是允许一定的不平衡存在,在保证增删查改效率没有太大影响的情况下,显著减少了平衡调整的次数,提升总体效率。

        AVL树一般通过节点的“平衡因子”来维持平衡,而红黑树通过给节点“着色”,确保其高效性。

在非空情况下,红黑树的性质(约束条件)如下:

1. 它是一棵二叉搜索树

2. 每一个节点都会被着色,不是黑色就是红色

3. 根节点必须为黑色

4. 对于一个红色节点,它的孩子或为空,或是黑色。也就是说路径上不能有连续红色节点

5. 从根节点到NULL节点的所有路径上黑色节点的数量都相同

有了以上约束条件,就可确保其没有一条路径长度能够超出其他路径的2倍,从而保证高效操作。 

如上图,每一条路径上都有相同数量的黑色节点。 

二、红黑树原理详解

        那么,红黑树为什么能够控制路径长度呢?

        来看一个极端情况:

        假设一棵红黑树的一条路径上有n个黑色节点,那么由于其所有路径上的黑色节点数量是相同的,所以其所有路径上都一定有n个黑色节点。对于这棵树,最长的可能路径上的节点就是一黑一红一黑一红(要确保无连续红色节点)......一共有2n个节点最短的可能路径上的节点是全黑的,一共有n个节点那么其他可能的路径长度都在n~2n之间。所以说没有一条路径长度能够超出其他路径长度的两倍,也就确保了根节点左右子树的高度比一定在1~2之间

接下来,我们分析一下红黑树的效率。

        设一棵红黑树一共有N个节点,它的最短可能路径的长度h,由于可能的路径长度都在h~2h之间,那么节点数N就满足 2^{h}-1\leq N\leq 2^{2h}-1 。由此可知,在路径最短情况下,其进行增删查改的时间消耗为O(logN);路径最长情况下,进行查找的时间消耗为O(2logN)。因此红黑树增删查改的时间复杂度为O(logN)

        红黑树的平衡控制相对AVL树较为抽象,但由于那几点约束条件,控制了最长路径和最短路径之比,间接地使红黑树达到了“近似平衡”,增删查改地时间消耗不会过大,并且相比AVL树,旋转调整次数会更少。

三、红黑树的实现

1. 节点定义

        红黑树节点及其颜色定义如下:

enum Color//枚举值表示颜色
{RED,BLACK
};//节点
template<class K, class V>
struct RBTreeNode
{pair<K, V> _kv;//数据域RBTreeNode<K, V>* _left;//指向左孩子的指针RBTreeNode<K, V>* _right;//指向右孩子的指针RBTreeNode<K, V>* _parent;//指向父亲的指针Color _col;//颜色RBTreeNode(const pair<K, V>& kv)//节点构造:_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr){}
};

与AVL树相同,红黑树也采用三叉链表结构,更有利于节点之间的控制访问。

在节点构造当中,我们并没有设置节点的初始颜色,之后在实现节点插入的实现当中,我们会重点讨论初始颜色的问题。

2. 红黑树类型定义及接口声明

        我们需要实现的接口如下:

//红黑树类
template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public://强制生成无参构造RBTree() = default;//拷贝构造RBTree(const RBTree<K, V>& t);//析构函数~RBTree();//插入bool Insert(const pair<K, V>& kv);//查找Node* Find(const K& key);//中序遍历void Inorder();//判断是否为合法红黑树bool IsValidRBTree();
private:Node* _root = nullptr;//根节点指针
};

3. 红黑树的插入(重点)

        为了保持红黑树增删查改的高效性,插入操作要保证满足红黑树的性质

        红黑树的插入过程大致如下:

1. 首先按照二叉搜索树的插入规则确定插入节点的位置。

2. 插入新节点,并确定新节点的颜色。

3. 为保持红黑树的性质,需要对原有树结构进行平衡调整


颜色设置

 我们首先来探讨新节点的颜色设置问题

        如果新插入的节点是根节点(树为空),毋庸置疑,为黑色。并且此时整个结构已经满足红黑树性质,插入完成。

        如果新插入的节点不是根节点,那么能否设置初始颜色为黑色呢?我们假设新插入的节点为黑色

不难发现,插入节点4之后,已经不满足红黑树性质5->2->4->NULL这条路径上有3个黑色节点,而5->7->6->NULL这条路径上有2个黑色节点实际上,如果插入黑色节点,不管插入到什么位置,该条路径上的黑色节点数都会增加,而其他路径上的黑色节点数不变,此时一定会违反红黑树的约束条件

那么,我们插入一个红色节点

此时可以看到,插入之后,整个结构依然满足红黑树的性质。当然,这是因为节点2是黑色节点,此时插入结束。若一个红色节点插入在红色节点的下方,出现连续红色节点,那么也会违反约束条件

总之,若插入黑色节点,一定违反红黑树规则,而插入红色节点,可能违反红黑树规则。所以我们退而求其次,将新节点(除根节点外)设置为红色


平衡调整

确定了新节点颜色之后,我们探讨平衡调整的问题

        之前已经提到,我们如果将新节点插入在红色节点的下方,就需要进行平衡调整。有两种情况需要分别讨论。

情况1:仅变色

如上图所示, parent和uncle都是红色,grandfather为黑色,此时插入新节点cur,出现连续红色节点。这种情况下,将parent和uncle变黑,grandfather变红,整个结构就满足了红黑树的性质

        但上图是一种具体情况,如果整棵树是另一棵树的子树,且节点4是红色的,那么变色之后就会再次出现连续的红色节点(节点2和节点4),此时就需要继续向上调整将grandfather作为新的cur,然后找到新的parent和uncle,进行变色或做其他调整(之后会讲解),然后再向上检查,直到遇到根节点或者无连续红色节点为止。

通过观察上图,不难发现:针对需要进行平衡调整的情况,新节点插入之后,parent一定为红,否则无需调整;grandfather一定为黑,否则未插入节点时就已经出现连续红色节点,红黑树原本就有问题。 那么,重要变量就是uncle节点

总结:当uncle为红时,仅需变色:将parent和uncle变黑,grandfather变红。然后将grandfather作为新的cur,找到新的parent和uncle,继续判断、调整,直到遇到根节点或无连续红色节点。

情况2:旋转+变色

        刚才提到uncle是重要变量,那么我们就给出uncle的其他可能情况(不存在或为黑),进而分析各种调整场景。

分类讨论之前,我们首先需要明确需要进行平衡调整的情况下,uncle和cur的关系

一个节点被标记为cur(确定插入位置之后),仅有两种情况:

1. 它是新增节点;2. 它是场景1向上调整时标记的节点


当uncle不存在时,cur一定是新增节点。原因:若cur不是新增节点,则其必然是向上调整时标记的节点,那么一定发生了变色,也就是说cur所在的某条路径A上至少有两个黑色节点(包括一个根节点)。而uncle不存在,则从根节点到uncle(NULL)位置的路径上的黑色节点数一定少于路径A,两条路径黑色节点数量不一致,不满足红黑树性质。

当uncle为黑时,cur一定时向上调整时标记的节点。原因:uncle为黑,则从根节点到uncle的路径B上至少有两个黑色节点。如果cur是新增节点,那么从根节点到cur的路径上的黑色节点数一定少于路径B,两条路径黑色节点数量不一致,不满足红黑树性质。

uncle不存在:

这种情况下,如果只是改变parent和grandfather的颜色,并不能解决问题:变色之后路径4->2->1->NULL上有1个黑色节点,而路径4->NULL上没有黑色节点,不满足红黑树性质

        此时就要配合旋转操作来解决问题。根据三个节点的相对位置,需要我们分情况进行单旋或双旋,从而调整树的结构:

单旋+变色:

可以看到,我们以grandfather为旋转点,进行右/左单旋,然后将parent变黑,grandfather变红,整个结构满足红黑树性质,并且该部分的根已经变成黑色,无需继续向上调整,插入结束

双旋+变色:

双旋完成后,将cur变黑,grandfather变红,整个结构满足红黑树,并且该部分的根已经变成黑色,无需继续向上调整,插入结束

总结:当uncle不存在时,需要根据实际情况进行旋转+变色。单旋完成后要将parent变黑,grandfather变红;双旋完成后要将cur变黑,grandfather变红。操作结束后,该部分的根成为黑色,不会出现连续红色节点,无需再向上调整。 

uncle为黑:

        刚才已经提到,uncle为黑时,cur一定是向上调整时标记的节点。所以我们使用抽象图来表示插入状况:

如图所示, a、b、c、d、e分别表示相应黑色节点数量的子树,当a、b的黑色节点数由n-1变为n时,说明发生了变色,使得cur变为红色。此时由于uncle是黑色,如果直接将parent变黑,grandfather变红,那么根节点到a、b、c路径上的黑色节点数与到d、e的路径不相等,不满足红黑树的性质。所以要以grandfather为旋转点,分情况进行旋转再变色

不难发现,uncle为黑时的旋转、变色逻辑与uncle不存在时完全相同,这里博主就不再一一列举各种旋转情况。总结:当uncle为黑时,需要根据实际情况进行旋转+变色。单旋完成后要将parent变黑,grandfather变红;双旋完成后要将cur变黑,grandfather变红。操作结束后,该部分的根成为黑色,不会出现连续红色节点,无需再向上调整。 


注意:平衡调整完成之后,整棵树的根节点可能变为红色。所以平衡调整结束后,一定要将根节点设置为黑色

红黑树插入的总体过程

总代码 

红黑树插入代码实现:

//插入
bool Insert(const pair<K, V>& kv)
{if (_root == nullptr)//树为空,直接插入{_root = new Node(kv);_root->_col = BLACK;return true;}//先查找合适的插入位置Node* parent = nullptr;Node* cur = _root;while (cur){if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(kv);cur->_col = RED;//插入红色节点if (kv.first < parent->_kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;//parent为红,进行平衡调整while (parent && parent->_col == RED){//确定grandfatherNode* grandfather = parent->_parent;if (parent == grandfather->_left){//确定uncleNode* uncle = grandfather->_right;if (uncle && uncle->_col == RED)//uncle为红,仅变色{parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续向上判断cur = grandfather;parent = cur->_parent;}else//uncle为黑或不存在,旋转+变色{if (cur == parent->_left)//右单旋{RotateR(grandfather);//变色parent->_col = BLACK;grandfather->_col = RED;}else//左右双旋{RotateL(parent);RotateR(grandfather);//变色cur->_col = BLACK;grandfather->_col = RED;}break;//旋转完成,直接跳出循环}}else{//确定uncleNode* uncle = grandfather->_left;if (uncle && uncle->_col == RED)//uncle为红,仅变色{parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续向上判断cur = grandfather;parent = cur->_parent;}else//uncle为黑或不存在,旋转+变色{if (cur == parent->_right)//左单旋{RotateL(grandfather);//变色parent->_col = BLACK;grandfather->_col = RED;}else//右左双旋{RotateR(parent);RotateL(grandfather);//变色cur->_col = BLACK;grandfather->_col = RED;}break;//旋转完成,直接跳出循环}}}//最后将根节点设置为黑色_root->_col = BLACK;return true;
}//右单旋
void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR) subLR->_parent = parent;Node* ppNode = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (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;parent->_right = subRL;if (subRL) subRL->_parent = parent;Node* ppNode = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parent == _root){_root = subR;subR->_parent = nullptr;}else{if (ppNode->_left == parent) ppNode->_left = subR;else ppNode->_right = subR;subR->_parent = ppNode;}
}

4. 红黑树的查找

        红黑树的查找逻辑与传统二叉搜索树相同,注意按照查找。

代码如下:

//查找
Node* Find(const K& key)
{Node* cur = _root;while (cur){if (key < cur->_kv.first){cur = cur->_left;//小了往左走}else if (key > cur->_kv.first){cur = cur->_right;//大了往右走}else{return cur;//找到了,返回}}return nullptr;//没找到,返回空指针
}

5. 中序遍历、拷贝构造和析构

        三个函数的实现逻辑与AVL树完全相同。注意拷贝时不要忘记parent指针。

代码如下:

//中序遍历
void Inorder()
{_Inorder(_root);
}
void _Inorder(Node* root)
{if (root == nullptr) return;_Inorder(root->_left);cout << root->_kv.first << ' ' << root->_kv.second << endl;_Inorder(root->_right);
}
//拷贝构造
RBTree(const RBTree<K, V>& t)
{_root = _Copy(t._root);
}
Node* _Copy(Node* root, Node* parent = nullptr)
{if (root == nullptr) return nullptr;Node* NewRoot = new Node(root->_kv);NewRoot->_col = root->_col;//复制颜色NewRoot->_parent = parent;//设置父指针//递归拷贝左子树和右子树NewRoot->_left = _Copy(root->_left, NewRoot);NewRoot->_right = _Copy(root->_right, NewRoot);return NewRoot;
}
//析构函数
~RBTree()
{_Destroy(_root);
}
void _Destroy(Node* root)
{if (root == nullptr) return;_Destroy(root->_left);_Destroy(root->_right);delete root;
}

6. 检查红黑树是否合法

        判断一棵红黑树是否合法,就要判断它是否满足红黑树的性质。可以从以下几点入手:

1. 检查根节点是否为黑色。

2. 当一个节点为红色时,判断其父亲是否是黑色。

3. 检查各个路径上的黑色节点数是否相等。

对于第三点,可以先遍历一条路径(可以选择走最左路径,避免递归),记录路径上的黑色节点个数,然后再判断其他所有路径上的黑色节点个数是否与之一致。

代码实现:

//判断是否为合法红黑树
bool IsValidRBTree()
{//空树,合法if (_root == nullptr) return true;//根节点为红,非法if (_root->_col == RED){cout << "根节点为红" << endl;return false;}int refNum = 0;//记录黑色节点个数Node* cur = _root;while (cur){if (cur->_col == BLACK) refNum++;cur = cur->_left;}//递归检查所有路径return _Check(_root, 0, refNum);
}//路径检查
bool _Check(Node* root, int num, const int refNum)
{if (root == nullptr){//遍历到空,进行黑色节点比较if (num != refNum){cout << "黑色节点数量不相等" << endl;return false;}return true;}//检查是否有连续红色节点if (root->_col == RED && root->_parent->_col == RED){cout << "有连续红色节点" << endl;return false;}//记录当前路径的黑色节点数if (root->_col == BLACK) num++;//递归检查左子树和右子树return _Check(root->_left, num, refNum) && _Check(root->_right, num, refNum);
}

接下来我们写一段代码,插入一组数据,验证红黑树的合法性:

int main()
{RBTree<int, int> t;int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };for (auto& e : a){t.Insert({ e,e });}t.Inorder();cout << t.IsValidRBTree() << endl;return 0;
}

运行结果:

7. 程序全部代码

红黑树实现全部代码如下:

#include <iostream>
#include <utility>
using namespace std;enum Color//枚举值表示颜色
{RED,BLACK
};//节点
template<class K, class V>
struct RBTreeNode
{pair<K, V> _kv;//数据域RBTreeNode<K, V>* _left;//指向左孩子的指针RBTreeNode<K, V>* _right;//指向右孩子的指针RBTreeNode<K, V>* _parent;//指向父亲的指针Color _col;//颜色RBTreeNode(const pair<K, V>& kv)//节点构造:_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED){}
};//红黑树类
template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public://强制生成无参构造RBTree() = default;//拷贝构造RBTree(const RBTree<K, V>& t){_root = _Copy(t._root);}//析构函数~RBTree(){_Destroy(_root);}//插入bool Insert(const pair<K, V>& kv){if (_root == nullptr)//树为空,直接插入{_root = new Node(kv);_root->_col = BLACK;return true;}//先查找合适的插入位置Node* parent = nullptr;Node* cur = _root;while (cur){if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(kv);if (kv.first < parent->_kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;//parent为红,进行平衡调整while (parent && parent->_col == RED){//确定grandfatherNode* grandfather = parent->_parent;if (parent == grandfather->_left){//确定uncleNode* uncle = grandfather->_right;if (uncle && uncle->_col == RED)//uncle为红,仅变色{parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续向上判断cur = grandfather;parent = cur->_parent;}else//uncle为黑或不存在,旋转+变色{if (cur == parent->_left)//右单旋{RotateR(grandfather);//变色parent->_col = BLACK;grandfather->_col = RED;}else//左右双旋{RotateL(parent);RotateR(grandfather);//变色cur->_col = BLACK;grandfather->_col = RED;}break;//旋转完成,直接跳出循环}}else{//确定uncleNode* uncle = grandfather->_left;if (uncle && uncle->_col == RED)//uncle为红,仅变色{parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续向上判断cur = grandfather;parent = cur->_parent;}else//uncle为黑或不存在,旋转+变色{if (cur == parent->_right)//左单旋{RotateL(grandfather);//变色parent->_col = BLACK;grandfather->_col = RED;}else//右左双旋{RotateR(parent);RotateL(grandfather);//变色cur->_col = BLACK;grandfather->_col = RED;}break;//旋转完成,直接跳出循环}}}//最后将根节点设置为黑色_root->_col = BLACK;return true;}//查找Node* Find(const K& key){Node* cur = _root;while (cur){if (key < cur->_kv.first){cur = cur->_left;//小了往左走}else if (key > cur->_kv.first){cur = cur->_right;//大了往右走}else{return cur;//找到了,返回}}return nullptr;//没找到,返回空指针}//中序遍历void Inorder(){_Inorder(_root);}//判断是否为合法红黑树bool IsValidRBTree(){//空树,合法if (_root == nullptr) return true;//根节点为红,非法if (_root->_col == RED){cout << "根节点为红" << endl;return false;}int refNum = 0;//记录黑色节点个数Node* cur = _root;while (cur){if (cur->_col == BLACK) refNum++;cur = cur->_left;}//递归检查所有路径return _Check(_root, 0, refNum);}
private://右单旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR) subLR->_parent = parent;Node* ppNode = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (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;parent->_right = subRL;if (subRL) subRL->_parent = parent;Node* ppNode = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parent == _root){_root = subR;subR->_parent = nullptr;}else{if (ppNode->_left == parent) ppNode->_left = subR;else ppNode->_right = subR;subR->_parent = ppNode;}}//中序遍历void _Inorder(Node* root){if (root == nullptr) return;_Inorder(root->_left);cout << root->_kv.first << ' ' << root->_kv.second << endl;_Inorder(root->_right);}//拷贝构造Node* _Copy(Node* root, Node* parent = nullptr){if (root == nullptr) return nullptr;Node* NewRoot = new Node(root->_kv);NewRoot->_col = root->_col;//复制颜色NewRoot->_parent = parent;//设置父指针//递归拷贝左子树和右子树NewRoot->_left = _Copy(root->_left, NewRoot);NewRoot->_right = _Copy(root->_right, NewRoot);return NewRoot;}//销毁void _Destroy(Node* root){if (root == nullptr) return;_Destroy(root->_left);_Destroy(root->_right);delete root;}//路径检查bool _Check(Node* root, int num, const int refNum){if (root == nullptr){//遍历到空,进行黑色节点比较if (num != refNum){cout << "黑色节点数量不相等" << endl;return false;}return true;}//检查是否有连续红色节点if (root->_col == RED && root->_parent->_col == RED){cout << "有连续红色节点" << endl;return false;}//记录当前路径的黑色节点数if (root->_col == BLACK) num++;//递归检查左子树和右子树return _Check(root->_left, num, refNum) && _Check(root->_right, num, refNum);}Node* _root = nullptr;//根节点指针
};int main()
{RBTree<int, int> t;int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };for (auto& e : a){t.Insert({ e,e });}t.Inorder();cout << t.IsValidRBTree() << endl;return 0;
}

总结

        红黑树通过引入节点颜色和一系列平衡规则,在保持高效查询性能的同时,优化了插入和删除操作的复杂度。与AVL树相比,红黑树对平衡的要求较为宽松,避免了频繁的旋转调整,从而提升了动态操作的效率。尽管红黑树的结构较为复杂,但它通过颜色标记、旋转操作以及路径黑色节点数量的控制,成功实现了查找、插入和删除操作的平衡。在实际应用中,红黑树被广泛用于操作系统、数据库等领域,发挥着其重要的作用。如果你觉得博主讲的还不错,就请留下一个小小的赞在走哦,感谢大家的支持❤❤❤

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

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

相关文章

计算机网络 (57)改进“尽最大努力交付”的服务

前言 计算机网络中的“尽最大努力交付”服务是网络层的一种数据传输方式。这种服务的特点是网络层只负责尽力将数据报从源端传输到目的端&#xff0c;而不保证数据传输的可靠性。 一、标记与分类 为数据分组打上标记&#xff1a; 给不同性质的分组打上不同的标记&#x…

ThinkPHP 8模型与数据的插入、更新、删除

【图书介绍】《ThinkPHP 8高效构建Web应用》-CSDN博客 《2025新书 ThinkPHP 8高效构建Web应用 编程与应用开发丛书 夏磊 清华大学出版社教材书籍 9787302678236 ThinkPHP 8高效构建Web应用》【摘要 书评 试读】- 京东图书 使用VS Code开发ThinkPHP项目-CSDN博客 编程与应用开…

【数据可视化】全国星巴克门店可视化

&#x1f9d1; 博主简介&#xff1a;曾任某智慧城市类企业算法总监&#xff0c;目前在美国市场的物流公司从事高级算法工程师一职&#xff0c;深耕人工智能领域&#xff0c;精通python数据挖掘、可视化、机器学习等&#xff0c;发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…

密码无关认证:金融机构如何解决密码问题

密码安全问题&#xff0c;依然是金融行业面临的重大挑战。尽管密码简单易用&#xff0c;但许多金融机构仍然依赖这种方式进行身份认证。幸运的是&#xff0c;随着技术的发展&#xff0c;密码无关认证已经成为一种更加安全、便捷的选择&#xff0c;它能够为数字银行带来更好的用…

【Redis】持久化机制

目录 前言&#xff1a; RDB 触发RDB持久化方法有俩种&#xff1a; 1.手动触发 2.自动触发 RDB文件的优缺点&#xff1a; AOF: AOF工作机制&#xff1a;​编辑 ​编辑重写机制&#xff1a; 前言&#xff1a; Redis是一个内存数据库&#xff0c;将数据存储在内存中&…

Vue基础(2)

19、组件之间传递数据 组件与组件之间不是完全独立的&#xff0c;而是有交集的&#xff0c;那就是组件与组 件之间是可以传递数据的 传递数据的解决方案就是 props ComponentA.vue <template><!-- 使用ComponentB组件&#xff0c;并传递title属性 --><h3>…

Java操作Excel导入导出——POI、Hutool、EasyExcel

目录 一、POI导入导出 1.数据库导出为Excel文件 2.将Excel文件导入到数据库中 二、Hutool导入导出 1.数据库导出为Excel文件——属性名是列名 2.数据库导出为Excel文件——列名起别名 3.从Excel文件导入数据到数据库——属性名是列名 4.从Excel文件导入数据到数据库…

08-ArcGIS For JavaScript-通过Mesh绘制几何体(Cylinder,Circle,Box,Pyramid)

目录 概述代码实现1、Mesh.createBox2、createPyramid3、Mesh.createSphere4、Mesh.createCylinder 完整代码 概述 对于三维场景而言&#xff0c;二位的点、线、面&#xff0c;三维的圆、立方体、圆柱等都是比较常见的三维对象&#xff0c;在ArcGIS For JavaScript中我们知道点…

DAY6,使用互斥锁 和 信号量分别实现5个线程之间的同步

题目 请使用互斥锁 和 信号量分别实现5个线程之间的同步 代码&#xff1a;信号量实现 void* task1(void* arg); void* task2(void* arg); void* task3(void* arg); void* task4(void* arg); void* task5(void* arg);sem_t sem[5]; //信号量变量int main(int argc, const …

19_PlayerPres持久化_创建角色窗口

创建脚本 编写脚本 using UnityEngine; //功能 : 角色创建界面 public class CreateWnd : WindowsRoot{protected override void InitWnd(){base.InitWnd();//TODO//显示一个随机名字} }创建角色窗口CreateWnd.cs应该在玩家点击 进入游戏 按钮后显示 所以在 登录窗口LoginWnd…

热更新杂乱记

热更新主要有一个文件的MD5值的比对过程&#xff0c;期间遇到2个问题&#xff0c;解决起来花费了一点时间 1. png 和 plist 生成zip的时候再生成MD5值会发生变动。 这个问题解决起来有2种方案&#xff1a; &#xff08;1&#xff09;.第一个方案是将 png和plist的文件时间改…

【2024年华为OD机试】 (C卷,100分)- 用户调度问题(JavaScriptJava PythonC/C++)

一、问题描述 问题描述 在通信系统中&#xff0c;有 n 个待串行调度的用户&#xff0c;每个用户可以选择 A、B、C 三种调度策略。不同的策略会消耗不同的系统资源。调度规则如下&#xff1a; 相邻用户不能使用相同的调度策略&#xff1a;例如&#xff0c;如果第 1 个用户选择…

FPGA中场战事

2023年10月3日,英特尔宣布由桑德拉里维拉(Sandra Rivera)担任“分拆”后独立运营的可编程事业部首席执行官。 从数据中心和人工智能(DCAI)部门总经理,转身为执掌该业务的CEO,对她取得像AMD掌门人苏姿丰博士类似的成功,无疑抱以厚望。 十年前,英特尔花费167亿美元真金白银…

从手动到智能:自动化三维激光扫描

三维扫描&#xff0c;是通过先进三维扫描技术获取产品和物体的形面三维数据&#xff0c;建立实物的三维图档&#xff0c;满足各种实物3D模型数据获取、三维数字化展示、3D多媒体开发、三维数字化存档、逆向设计、产品开发、直接3D打印制造或辅助加工制造等一系列的应用。 传统的…

电容的一些常用数值

如果是滤高频信号的小电容一般采用100nF 如果是滤低频信号的大电容一般采用10uF(10000nF) 比如这个LDO降压 两个一起用滤波效果会更好 如果想要供电引脚悬空&#xff0c;按理不能悬空&#xff0c;所以应该接大电阻接地&#xff0c;一般采用5.1KΩ 比如这个6Pin USB-TypeC的…

编写子程序

实验内容、程序清单及运行结果 编写子程序&#xff08;课本实验10&#xff09; 1.显示字符串 问题显示字符串是现象工作中经常用到的功能&#xff0c;应该编写一个通用的子程序来实现这个功能。我们应该提供灵活的调用接口&#xff0c;使调用者可以决定显示的位置&#xff0…

亚马逊新店铺流量怎么提升?自养号测评新趋势

在竞争激烈的电商市场中&#xff0c;亚马逊新店铺如何在众多竞争者中脱颖而出&#xff0c;提升流量成为一大难题。对于新手卖家来说&#xff0c;掌握正确的流量提升策略至关重要。本文将为您揭秘亚马逊新店铺流量提升的方法&#xff0c;助您快速打开市场&#xff0c;实现业绩增…

FPGA自分频产生的时钟如何使用?

对于频率比较小的时钟&#xff0c;使用clocking wizard IP往往不能产生&#xff0c;此时就需要我们使用代码进行自分频&#xff0c;自分频产生的时钟首先应该经过BUFG处理&#xff0c;然后还需要进行时钟约束&#xff0c;处理之后才能使用。

JQuery基本介绍和使用方法

JQuery基本介绍和使用方法 W3C 标准给我们提供了⼀系列的函数, 让我们可以操作: ⽹⻚内容⽹⻚结构⽹⻚样式 但是原⽣的JavaScript提供的API操作DOM元素时, 代码⽐较繁琐, 冗⻓. 我们可以使⽤JQuery来操作⻚⾯对象. jQuery是⼀个快速、简洁且功能丰富的JavaScript框架, 于20…

Go语言中的值类型和引用类型特点

一、值类型 值类型的数据直接包含值&#xff0c;当它们被赋值给一个新的变量或者作为参数传递给函数时&#xff0c;实际上是创建了原值的一个副本。这意味着对新变量的修改不会影响原始变量的值。 Go中的值类型包括&#xff1a; 基础类型&#xff1a;int&#xff0c;float64…