数据结构 - 红黑树

文章目录

    • 前言
    • 一、红黑树介绍
      • 1、红黑树的概念
      • 2、红黑树的性质
    • 二、实现红黑树
      • 1、基本框架
      • 2、插入
      • 3、删除
      • 4、查找
      • 5、测试红黑树
      • 6、红黑树代码
    • 三、红黑树性能
    • 四、AVL树和红黑树的差别


前言

红黑树是一种二叉搜索树,所以学习前需要学会基本的二叉搜索树,并且需要了解左右旋转操作。

一、红黑树介绍

1、红黑树的概念

红黑树是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或 Black。
通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路 径会比其他路径长出俩倍,因而是接近平衡的,从而获得较高的查找、插入和删除性能。

2、红黑树的性质

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

这些性质确保了红黑树从根到叶子的最长路径不会是最短路径的两倍长,从而保持树的相对平衡。

二、实现红黑树

1、基本框架

(1)红黑树节点

//状态
enum Color
{BLACK,	//黑色RED	//红色
};//树节点
template<class K,class T>
struct RBTreeNode
{RBTreeNode<K,T>* _left;	//左RBTreeNode<K, T>* _right;	//右RBTreeNode<K, T>* _parent;	//父Color _color;	//状态表示pair<K, T> _val;	//数据//构造函数	状态默认红色RBTreeNode(const pair<K, T>& val = pair<K, T>()):_val(val),_left(nullptr),_right(nullptr),_parent(nullptr),_color(RED){}
};

(2)红黑树类

template<class K,class T>
class RBTree
{//重命名typedef RBTreeNode<K, T> Node;public:RBTree() {};~RBTree() {};bool Insert(const pair<K, T>& val)bool Erase(const K& key);Node* Find(const K& key)private:
// 右单旋
void RotateR(Node* parent)
{//左节点Node* L = parent->_left;//左子树右边第一个节点Node* Lr = L->_right;//parent的父亲Node* pparent = parent->_parent;//连接过程L->_right = parent;parent->_parent = L;//该节点可能为空if (Lr){Lr->_parent = parent;}parent->_left = Lr;//更新L的父节点L->_parent = pparent;//是根的情况if (pparent == nullptr){_root = L;}else{if (parent == pparent->_left) pparent->_left = L;else pparent->_right = L;}
}
//左旋转
void RotateL(Node* parent)
{//右边第一个节点Node* R = parent->_right;//右子树第一个左节点Node* Rl = R->_left;//父节点Node* pparent = parent->_parent;//连接过程parent->_right = Rl;if (Rl){Rl->_parent = parent;}R->_left = parent;//更新parent的父节点parent->_parent = R;//更新R的父节点R->_parent = pparent;//是根的情况if (nullptr == pparent){_root = R;}else{if (pparent->_left == parent) pparent->_left = R;else pparent->_right = R;}}Node* _root;
};

2、插入

红黑树的插入操作是一个复杂但高效的过程,它确保了树在插入新节点后仍然保持平衡。
(1)基本步骤:

1、找到插入位置:与二叉搜索树相同,首先通过比较节点值找到新节点应该插入的位置。
2、插入新节点:将新节点插入到找到的位置,并将其初始颜色设置为红色。这是因为将新节点设置为红色可以最小化对树平衡性的影响,同时满足红黑树的性质(对于破坏性质4来说,破坏性质3代价更小)。
3、调整树以保持平衡:插入红色节点后,可能会破坏红黑树的性质。为了恢复这些性质,需要进行一系列的旋转和重新着色操作。

(2)讨论插入节点后维持树的性质的情况
在这里插入图片描述

情况一:如果插入节点的p为黑色,不做处理也满足红黑树性质,结束。

情况二:p和u都是红色。
分析:通过性质可以推导出g为黑色
解决:将p和u变为黑色,将g变为红色。如果g是根节点,则将其变为黑色。否则,继续对g进行同样的调整。

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/36110af0e1864cbba9252b99458232d2.png

情况三:p为红,u为黑或者为空
分析:
a.如果是u不存在,那么cur一定是新插入的节点,因为如果不是新插入节点,那么cur和p就一定会存在一个黑色节点,那么每条路径的黑色节点就不一致了。
b.如果u存在且为黑,那么cur不是新插入节点且一定是黑,因为p是红u为黑,那么说明p向下这条路径一定存在一个黑色节点,如果cur是新插入就会是红,就会导致原来的树就不满足性质4,如果cur原来是红色节点那么就不满足性质3了(为情况二转变而来)。
c.经过旋转后的树已经是红黑树了,结束。

子情况一:cur、p、g都在一边
解决:都在左边,g变红p变黑,对g使用右旋转(都在右边,g边红p变黑,对g使用左旋转)。
在这里插入图片描述
子情况二:cur和p,p和g不同边
解决:cur和p在右边,p和g在左边,cur变黑,g变红,对p使用左旋转,再对g使用右旋转(:cur和p在左边,p和g在右边,cur变黑,g变红,对p使用右旋转,再对g使用左旋转)。
在这里插入图片描述

3、删除

红黑树是一种自平衡的二叉搜索树,它通过修改节点颜色及执行特定的旋转操作来确保树在添加或删除节点后继续保持平衡。删除操作是红黑树中最复杂的操作之一,因为它需要在删除节点后恢复红黑树的性质。
(1)基本步骤:

1、查找节点: 首先,需要在树中找到需要删除的节点。如果找不到,则直接结束。
2、删除节点:如果找到的节点有两个子节点,通常的做法是用它的后继节点(即右子树中的最小节点)来替换它,并删除原后继节点。这保证了被删除的节点最多只有一个非空子节点。
3、修复树: 删除节点后,需要修复树以保持红黑树的特性: 每个节点是红色或黑色。 根节点是黑色。 每个叶子节点(NIL节点,空节点)是黑色。如果一个节点是红色的,则它的子节点必须是黑色的。 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。

(2)讨论删除后恢复树的情况
情况一:cur是红色
解决:直接删除即可,不会影响红黑树的性质,结束。

情况二:cur为黑色,且cur存在一个子节点
分析:cur为黑色,且cur存在一个子节点,那么cur的子节点一定是红色。
解决:p连接cur的子节点,且cur子节点改为黑色,结束。

情况三:cur为黑色且不存在子节点
分析:此时想要不破坏性质就需要观察cur的兄弟节点b了。

子情况一:b为红色
解决:b在p的左侧,b变黑,p变红,再进行右旋转(b在p的右侧,b变黑,p变红,再进行左旋转),变化后仍然没有恢复红黑树的性质,需要再次对cur进行调整(此时可能会转化成其他情况),继续。

在这里插入图片描述

子情况二:b为黑,b的子节点都为空或者孩子节点都为黑
解决:将b改为红,如果p为红将p改为黑结束,如果p不为红,进行迭代,cur = p,继续。
在这里插入图片描述
子情况三:b为黑且存在一个红色子节点(剩余的一个子树任意)
假设:cur在p的左侧且b的右节点br为红
解决:将b变为p的颜色,p和br变为黑色,再对p左旋转,旋转完红黑树恢复,结束。
在这里插入图片描述
假设:cur在p的右侧且b的左节点bl为红
解决:将b变为p的颜色,p和bl变为黑色,再对p右旋,旋转完红黑树恢复,结束。
在这里插入图片描述
假设:cur在p的左侧且b的左节点bl为红
解决:将bl变为p的颜色,p和b变为黑色,先进行右旋转b再进行p左旋,旋转完红黑树恢复,结束。
在这里插入图片描述

假设:cur在p的右侧且b的右节点br为红
解决:将br变为p的颜色,p和b变为黑色,先进行对b左旋再进行p右旋,旋转完红黑树恢复,结束。
在这里插入图片描述

4、查找

红黑树查找与二叉搜索树一样。
步骤:

1、开始于根节点:查找操作从树的根节点开始。 比较节点值:将查找值与当前节点的值进行比较。
2、
如果查找值等于当前节点的值,则查找成功,返回该节点。
如果查找值小于当前节点的值,则移动到左子节点。
如果查找值大于当前节点的值,则移动到右子节点。

3、重复步骤2:继续比较并移动,直到找到包含查找值的节点,或者遇到一个叶子节点(NIL节点),这意味着查找值不在树中。
4、查找结束:如果找到包含查找值的节点,则返回该节点;否则,返回NULL或者一个特殊的值表示查找失败。

5、测试红黑树

通过判断根节点是否为黑+每一条路径黑色节点数量是否一样+是否存在父子都为红。
测试代码:

bool IsBalance()
{if (_root == nullptr)return true;if (_root->_color == RED){return false;}// 参考值int refNum = 0;Node* cur = _root;while (cur){if (cur->_color == BLACK){++refNum;}cur = cur->_left;}return Check(_root, 0, refNum);
}
bool Check(Node* root, int blackNum, const int refNum)
{if (root == nullptr){//cout << blackNum << endl;if (refNum != blackNum){cout << "存在黑色节点的数量不相等的路径" << endl;return false;}return true;}//cout << root->_val.first << endl;if (root->_color == RED && root->_parent->_color == RED){cout << root->_val.first << "存在连续的红色节点" << endl;return false;}if (root->_color == BLACK){blackNum++;}return Check(root->_left, blackNum, refNum)&& Check(root->_right, blackNum, refNum);
}
void test()
{RBTree<int, int> rb;vector<int> arr;for (int i = 1; i <= 10000; i++){arr.push_back(rand());}for (int i = 0; i < arr.size(); i++){int e = arr[i];rb.Insert({ e,e });if (rb.IsBalance())cout << "是红黑树" << endl;elseassert(false);}for (int i = 0; i < arr.size(); i++){rb.Erase(arr[i]);if (rb.IsBalance())cout << "是红黑树" << endl;elseassert(false);}
}

通过插入10000随机数观察是否报错。
在这里插入图片描述
在运行结束后仍然没有报错说明插入、删除没有问题。

6、红黑树代码

#pragma once
#include<iostream>
#include<vector>
#include<assert.h>
using namespace std;//状态
enum Color
{BLACK,	//黑色RED	//红色
};//树节点
template<class K,class T>
struct RBTreeNode
{RBTreeNode<K,T>* _left;	//左RBTreeNode<K, T>* _right;	//右RBTreeNode<K, T>* _parent;	//父Color _color;	//状态表示pair<K, T> _val;	//数据//构造函数	状态默认红色RBTreeNode(const pair<K, T>& val = pair<K, T>()):_val(val),_left(nullptr),_right(nullptr),_parent(nullptr),_color(RED){}
};template<class K,class T>
class RBTree
{//重命名typedef RBTreeNode<K, T> Node;public:RBTree() {};~RBTree() {};//插入bool Insert(const pair<K, T>& val){//找到放val的位置Node* cur = _root;//作为前驱指针,与val节点连接Node* parent = nullptr;while (cur){//向左if (cur->_val.first > val.first){parent = cur;cur = cur->_left;}//向右else if (cur->_val.first < val.first){parent = cur;cur = cur->_right;}//存在相同的值else{return false;}}//插入新节点cur = new Node(val);//不存在根节点,作为根节点if (parent == nullptr) _root = cur;//连接在前驱指针左侧else if (parent->_val.first > val.first){cur->_parent = parent;parent->_left = cur;}//连接在前驱指针右侧else{cur->_parent = parent;parent->_right = cur;}//父亲不为空且孩子和父亲都为红 --- 违规规则三while (parent != nullptr &&parent->_color == RED && cur->_color == RED){Node* grandfather = parent->_parent;	//祖父Node* uncle = nullptr;	//叔叔if (grandfather->_left == parent)uncle = grandfather->_right;elseuncle = grandfather->_left;//情况一 uncle存在且为红 - 祖父变红父亲和叔叔变黑,向上更新if (uncle != nullptr && uncle->_color == RED){parent->_color = uncle->_color = BLACK;grandfather->_color = RED;}//情况二 uncle不存在\存在else{//子情况一,cur 、parent在一边//都在左边 -- 使用右旋转if (grandfather->_left == parent && parent->_left == cur){RotateR(grandfather);grandfather->_color = RED;parent->_color = BLACK;}//都在右边 --- 使用左旋转else if (grandfather->_right == parent && parent->_right == cur){RotateL(grandfather);grandfather->_color = RED;parent->_color = BLACK;}//子情况二	cur与parent不再同一边//cur在右边parent在左边  --对parent左旋转再对grandfather右旋转else if (grandfather->_left == parent && parent->_right == cur){RotateL(parent);RotateR(grandfather);cur->_color = BLACK;grandfather->_color = RED;}//cur在左边parent在右边  --对parent右旋转再对grandfather左旋转else if (grandfather->_right == parent && parent->_left == cur){RotateR(parent);RotateL(grandfather);cur->_color = BLACK;grandfather->_color = RED;}//经过旋转后树达到平衡break;}//迭代cur = grandfather;parent = cur->_parent;}//根节点一定是黑色的_root->_color = BLACK;return true;}//删除bool Erase(const K& key){//从根节点开始搜索Node* cur = _root;//作为cur的前驱指针Node* parent = nullptr;//搜索查找while (cur){if (cur->_val.first > key){parent = cur;cur = cur->_left;}else if (cur->_val.first < key){parent = cur;cur = cur->_right;}elsebreak;}//找不到if (cur == nullptr) return false;//假设cur左右节点都存在,找右边最小值替换if (cur->_left != nullptr && cur->_right != nullptr){Node* tmp1 = cur->_right;Node* tmp2 = nullptr;while (tmp1->_left){tmp2 = tmp1;tmp1 = tmp1->_left;}cur->_val = tmp1->_val;//tmp1左边没有节点,自己就是最小的节点if (tmp2 == nullptr){parent = cur;cur = tmp1;}else{cur = tmp1;parent = tmp2;}}//如果是cur根节点if (parent == nullptr){if (cur->_left == nullptr){_root = cur->_right;if (_root != nullptr)_root->_color = BLACK;}else{_root = cur->_left;_root->_color = BLACK;}delete cur;return true;}//标记要删除的节点Node* deletecur = cur;//删除节点为红色,直接删除结束if (cur->_color == RED){if (cur->_left == nullptr){if (cur == parent->_left)	parent->_left = cur->_right;else parent->_right = cur->_right;}else{if (cur == parent->_right)	parent->_right = cur->_left;else parent->_right = cur->_left;}}//删除节点为黑色else{//存在左子树,变为黑色if (cur->_left != nullptr){cur->_left->_color = BLACK;if (cur == parent->_right)parent->_right = cur->_left;elseparent->_left = cur->_left;cur->_left->_parent = parent;}//存在右子树else if (cur->_right != nullptr){cur->_right->_color = BLACK;if (cur == parent->_left)	parent->_left = cur->_right;elseparent->_right = cur->_right;cur->_right->_parent = parent;}//不存在孩子else{while (parent != nullptr){int sign = 0;if (cur == parent->_left) sign = -1;else sign = 1;if (cur == deletecur){if (sign == -1) parent->_left = nullptr;else parent->_right = nullptr;}//兄弟节点Node* brothers = nullptr;if (sign == -1) brothers = parent->_right;else brothers = parent->_left;//兄弟节点为红色if (brothers->_color == RED){//变色brothers->_color = BLACK;parent->_color = RED;//在右边-》左转if (brothers == parent->_right){RotateL(parent);}//在左边-》右转else{RotateR(parent);}//重新更新brothersif (sign == -1) brothers = parent->_right;else brothers = parent->_left;}//兄弟节点的孩子节点为空或者孩子节点为黑if ((brothers->_left == nullptr || brothers->_left->_color == BLACK) && (brothers->_right == nullptr || brothers->_right->_color == BLACK)){brothers->_color = RED;//父亲为红色时直接结束if (parent->_color == RED){parent->_color = BLACK;break;}//更新继续cur = parent;parent = parent->_parent;}//兄弟节点不为空有一个孩子为红或者都为红else{//cur作为parend的左孩子if (sign == -1){//作为brothers右孩子--左旋if (brothers->_right && brothers->_right->_color == RED){brothers->_color = parent->_color;parent->_color = brothers->_right->_color = BLACK;RotateL(parent);}//作为brothers左孩子,先右旋再左旋else if (brothers->_left && brothers->_left->_color== RED){brothers->_left->_color = parent->_color;brothers->_color = parent->_color = BLACK;			RotateR(brothers);RotateL(parent);}}else{//作为brothers左孩子--右旋if (brothers->_left&&brothers->_left->_color == RED){brothers->_color = parent->_color;parent->_color = brothers->_left->_color = BLACK;RotateR(parent);}//作为brothers右孩子--先左旋再右旋else if (brothers->_right && brothers->_right->_color == RED){brothers->_right->_color = parent->_color;brothers->_color = parent->_color = BLACK;RotateL(brothers);RotateR(parent);}}//再旋转完后性质恢复,结束break;}}}}delete deletecur;return true;}//查找Node* Find(const K& key){//从根节点开始Node* cur = _root;while (cur){//大了就去左子树中搜索if (cur->_val.first > key){cur = cur->_left;}//小了就去右子树中搜索else if (cur->_val.first < key){cur = cur->_right;}else{//找到返回当前节点return cur;}}return nullptr;}bool IsBalance(){if (_root == nullptr)return true;if (_root->_color == RED){return false;}// 参考值int refNum = 0;Node* cur = _root;while (cur){if (cur->_color == BLACK){++refNum;}cur = cur->_left;}return Check(_root, 0, refNum);}
private:// 右单旋void RotateR(Node* parent){//左节点Node* L = parent->_left;//左子树右边第一个节点Node* Lr = L->_right;//parent的父亲Node* pparent = parent->_parent;//连接过程L->_right = parent;parent->_parent = L;//该节点可能为空if (Lr){Lr->_parent = parent;}parent->_left = Lr;//更新L的父节点L->_parent = pparent;//是根的情况if (pparent == nullptr){_root = L;}else{if (parent == pparent->_left) pparent->_left = L;else pparent->_right = L;}}//左旋转void RotateL(Node* parent){//右边第一个节点Node* R = parent->_right;//右子树第一个左节点Node* Rl = R->_left;//父节点Node* pparent = parent->_parent;//连接过程parent->_right = Rl;if (Rl){Rl->_parent = parent;}R->_left = parent;//更新parent的父节点parent->_parent = R;//更新R的父节点R->_parent = pparent;//是根的情况if (nullptr == pparent){_root = R;}else{if (pparent->_left == parent) pparent->_left = R;else pparent->_right = R;}}bool Check(Node* root, int blackNum, const int refNum){if (root == nullptr){//cout << blackNum << endl;if (refNum != blackNum){cout << "存在黑色节点的数量不相等的路径" << endl;return false;}return true;}//cout << root->_val.first << endl;if (root->_color == RED && root->_parent->_color == RED){cout << root->_val.first << "存在连续的红色节点" << endl;return false;}if (root->_color == BLACK){blackNum++;}return Check(root->_left, blackNum, refNum)&& Check(root->_right, blackNum, refNum);}Node* _root;
};

三、红黑树性能

  1. 时间复杂度 查找、插入和删除操作:红黑树通过特定的旋转和重新着色操作来保持树的平衡,从而确保这些操作的时间复杂度保持在O(log n),其中n是树中元素的数目。这使得红黑树在大数据集上仍然能够保持高效的性能。
  2. 平衡性 平衡因子:与AVL树不同,红黑树并不直接维护每个节点的平衡因子(左子树高度减去右子树高度)。相反,它通过维护一系列颜色相关的性质来间接保持树的平衡。这些性质包括节点的颜色(红色或黑色)、根节点是黑色、所有叶子节点是黑色、红色节点的两个子节点都是黑色等。
    路径长度限制:红黑树的这些性质确保了从根到任意叶子节点的最长路径不会超过最短路径的两倍。这种平衡性保证了在最坏情况下,查找、插入和删除操作的时间复杂度仍然是O(log
    n)。
  3. 插入和删除操作的性能 插入操作:在插入新节点时,红黑树会首先将其标记为红色,并通过一系列的旋转和重新着色操作来恢复树的平衡。由于红黑树的自平衡策略相对宽松,因此在插入操作中通常比AVL树具有更好的性能。
    删除操作:删除操作可能稍微复杂一些,因为它涉及到节点的删除以及后续可能的旋转和重新着色操作来恢复树的平衡。然而,与插入操作类似,删除操作的时间复杂度也保持在O(log
    n)。

四、AVL树和红黑树的差别

  1. 调整平衡的实现机制 AVL树:AVL树通过维护每个节点的平衡因子(左子树高度减去右子树高度)来实现平衡。在插入或删除节点后,如果某个节点的平衡因子绝对值大于1,则需要通过旋转操作来恢复平衡。AVL树的平衡条件是严格的,即所有节点的左右子树高度差的绝对值不超过1。
    红黑树:红黑树则通过节点的颜色(红色或黑色)和一系列旋转操作来保持树的平衡。红黑树的平衡条件相对宽松,它要求从根节点到叶子节点的所有路径上黑色节点的数量相同,从而间接保证了树的平衡性。在插入或删除节点后,红黑树通过重新着色和旋转操作来恢复平衡,且任何不平衡都可以在三次旋转之内解决。
  2. 性能差异 插入和删除操作:由于AVL树需要保持严格的平衡性,因此在插入或删除节点时可能需要进行更多的旋转操作,这在一定程度上降低了其性能。相比之下,红黑树通过非严格的平衡条件换取了更少的旋转次数,从而在插入和删除操作上表现出更高的效率。
    查找操作:在查找操作上,AVL树和红黑树的性能相当。它们都保持了二叉查找树的性质,可以在对数时间内完成查找操作。然而,由于AVL树的高度通常比红黑树更低(在相同节点数的情况下),因此从理论上讲,AVL树的查找效率可能会略高于红黑树,但这种差异在实际应用中往往可以忽略不计。
  3. 内存占用 AVL树需要存储每个节点的平衡因子,这增加了额外的内存开销。 红黑树则通过节点的颜色属性来间接维护平衡性,无需存储额外的平衡因子信息,因此在内存占用上更为节省。
  4. 适用场景 AVL树:适用于查找操作频繁且对内存占用要求不高的场景。由于AVL树保持了严格的平衡性,因此在查找性能上具有一定优势。然而,其插入和删除操作的性能相对较低,不适合数据变动频繁的场景。
    红黑树:适用于插入和删除操作频繁且对查找性能要求不是极端严格的场景。红黑树通过牺牲一定的平衡性来换取更高的插入和删除效率,因此在这些场景下表现出更好的整体性能。此外,红黑树的内存占用更低,也使其在一些内存受限的环境中更具优势。
    综上所述,AVL树和红黑树在调整平衡的实现机制、性能差异、内存占用和适用场景等方面存在显著的区别。在选择使用哪种数据结构时,需要根据具体的应用场景和需求进行综合考虑。

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

X-AnyLabeling标注软件使用方法

第一步 下载 官方X-AnyLabeling下载地址 github&#xff1a;X-AnyLabeling 第二步 配置环境 使用conda创建新的虚拟环境 conda create -n xanylabel python3.8进入环境 conda activate xanylabel进入X-AnyLabeling文件夹内&#xff0c;运行下面内容 依赖文件系统环境运行环…

昇思MindSpore 应用学习-CycleGAN图像风格迁移互换

日期 心得 昇思MindSpore 应用学习-CycleGAN图像风格迁移互换&#xff08;AI代码学习&#xff09; CycleGAN图像风格迁移互换 模型介绍 模型简介 CycleGAN(Cycle Generative Adversarial Network) 即循环对抗生成网络&#xff0c;来自论文 Unpaired Image-to-Image Trans…

数据中台 | 3分钟带你读懂数据中台的由来

1.数据中台产生的原因 数据中台的概念起源于中国阿里巴巴集团提出的“大中台&#xff0c;小前台”战略。这一理念的核心在于通过构建强大的中台体系&#xff0c;为前端的快速创新和个性化业务需求提供强有力的支持。具体到数据中台&#xff0c;其设计初衷是为了应对企业内部数…

如何解决Windows系统目录权限问题

目录 前言1. 为什么会出现权限问题2. 修改文件权限的步骤2.1 确定目标文件2.2 右键属性设置2.3 更改所有者2.4 修改权限2.5 确认修改 3. 替换文件3.1 拷贝新的文件3.2 验证替换结果 结语 前言 在Windows系统中&#xff0c;时常需要往C盘系统目录下拷贝或者替换文件。然而&…

Java面试还看传统八股文?快来看看这个场景题合集吧【附PDF】

以下就是这份面试场景文档↓ 这里有什么&#xff1f; ↓↓ 1.针对 2024 年面试行情的变化设计的面试场景题以及回答思路 2. 如何快速通过面试的详细攻略 3. 简历优化技巧 1.知己知彼才能百战百胜&#xff0c;如何做好面试前的准备工作 场景题答案以及更多场景题八股文一线大…

Spring Security学习笔记(二)Spring Security认证和鉴权

前言&#xff1a;本系列博客基于Spring Boot 2.6.x依赖的Spring Security5.6.x版本 上一篇博客介绍了Spring Security的整体架构&#xff0c;本篇博客要讲的是Spring Security的认证和鉴权两个重要的机制。 UsernamePasswordAuthenticationFilter和BasicAuthenticationFilter是…

docker 安装单机版redis

把这三个放上去 修改成自己的 按照自己需求来 照图片做 vim redis.conf vim startRedis.sh mv startRedis.sh deployRedis.sh sh deployRedis.sh docker run --privilegedtrue \ --name dev.redis --restartalways \ --network dev-net \ -v ./config/redis.conf:/etc/r…

编译原理期末复习-按考点

编译原理期末复习-按考点 Ocean University of China 第一章 引论 翻译器、编译器、解释器 翻译器&#xff1a;把一种语言变成另外一种语言&#xff08;语义等价&#xff09; 编译器&#xff1a;翻译器的一种 解释器&#xff1a;不产生目标代码&#xff0c;解释执行源程序&a…

24年第三届钉钉杯大学生大数据挑战赛浅析

需要完整资料&#xff0c;请关注WX&#xff1a;“小何数模”&#xff01; 本次钉钉杯大数据挑战赛的赛题已正式出炉&#xff0c;无论是赛题难度还是认可度&#xff0c;该比赛都是仅次于数模国赛的独一档&#xff0c;可以用于国赛前的练手训练。考虑到大家解题实属不易&#xf…

CentOS 7.x 的 YUM 仓库问题

背景 CentOS Linux 7 的生命周期&#xff08;EOL&#xff09;已经于 2024 年 6 月 30 日终止这意味着 CentOS 7.x 的官方镜像站点将不再提供服务&#xff0c;导致在使用 yum 安装或更新程序时可能会遇到 错误。本文将介绍如何解决这一问题&#xff0c;使得你可以继续在 CentOS…

17 敏捷开发—Scrum(2)

从上一篇 「16 敏捷开发实践&#xff08;1&#xff09;」中了解了Scrum是一个用于开发和维护复杂产品的框架&#xff0c;是一个增量的、迭代的开发过程。一般由多个Sprint&#xff08;迭代冲刺&#xff09;组成&#xff0c;每个Sprint长度一般为2-4周。下面全面介绍Scrumde 角色…

[Windows CMD] 查看网络配置 ipconfig

ipconfig 是一个网络命令工具&#xff0c;用于显示所有适配器&#xff08;网络接口&#xff09;的 IPv4 和 IPv6 配置信息。这个命令在 Windows 操作系统中非常常用&#xff0c;也存在于其他一些基于 IP 的网络系统中&#xff0c;如 macOS 和 Linux&#xff08;在这些系统中通常…

Android中广播接收器BroadcastReceiver学习

目录 一 前言二 分类2.1 标准广播2.1.1 动态注册2.1.2 静态注册2.1.3 带权限的标准广播 2.2 有序广播2.3 系统广播 一 前言 BroadcastReceiver是四大组件之一&#xff0c;用于组件间通信&#xff0c;底层是binder机制。注&#xff1a;&#xff08;贴的代码是Compose写的且不规…

unity2D游戏开发07整合游戏

角色类 设置角色类,在Scripts下新建MonoBehaviours文件夹,并将MovementController拖进去 在MonoBehaviours新建c#脚本,命名为Character,双击打开编辑 编写代码 using System.Collections; using System.Collections.Generic; using UnityEngine;public abstract class Ch…

【QT】QT 窗口(菜单栏、工具栏、状态栏、浮动窗口、对话框)

Qt 窗口是通过 QMainWindow类来实现的。 QMainWindow 是一个为用户提供主窗口程序的类&#xff0c;继承自 QWidget 类&#xff0c;并且提供了⼀个预定义的布局。QMainWindow 包含一个菜单栏&#xff08;Menu Bar&#xff09;、多个工具栏&#xff08;Tool Bars&#xff09;、…

2024最新Selenium面试题(附带答案),建议收藏备用

一.你在TestNG中使用了哪些注解&#xff1f; TestBeforeSuiteAfterSuiteBeforeTestAfterTestBeforeClassAfterClassBeforeMethodAfterMethod 二.如何从Excel中读取数据&#xff1f; FileInputStream fs new FileInputStream(“excel文件路径”); Workbook wb WorkbookFact…

LLM模型之基于MindSpore通过GPT实现情感分类

前言 # 该案例在 mindnlp 0.3.1 版本完成适配&#xff0c;如果发现案例跑不通&#xff0c;可以指定mindnlp版本&#xff0c;执行!pip install mindnlp0.3.1 !pip install mindnlp !pip install jieba %env HF_ENDPOINThttps://hf-mirror.com 导入对应的包 import osimport m…

目标检测 YOLOv5-7.0 详细调试自制数据集实战

目标检测 YOLOv5-7.0 详细调试&自制数据集实战 一、项目介绍及环境配置&#xff08;一&#xff09;项目解读&#xff08;二&#xff09;版本选择&#xff08;三&#xff09;环境配置 二、如何利用YOLOv5进行预测&#xff08;detect.py&#xff09;&#xff08;一&#xff0…

计算机毕业设计-程序论文-基于 Java 的高校教资报名系统的设计与实现

本系统开发采用技术为JSP、Bootstrap、Ajax、SSM、Java、Tomcat、Maven 此文章为本人亲自指导加编写&#xff0c;禁止任何人抄袭以及各类盈利性传播&#xff0c; 相关的代码部署论文ppt代码讲解答辩指导文件都有可私要 项目源码&#xff0c;请关注❥点赞收藏并私信博主&#x…

Python 高阶语法

前言&#xff1a; 我们通过上篇文章学习了Python的基础语法&#xff0c;接下来我们来学习Python的高阶语法 1.初识对象 在Python中我们可以做到和生活中那样&#xff0c;设计表格、生产表格、填写表格的组织形式的 面向对象包含 3 大主要特性&#xff1a;  封装  继承 …