C++数据结构——红黑树

一,关于红黑树

红黑树也是一种平衡二叉搜索树,但在每个节点上增加一个存储位表示节点的颜色,颜色右两种,红与黑,因此也称为红黑树。

通过对任意一条从根到叶子的路径上各个节点着色方式的限制,红黑树可以确保没有一条路径会比其他路径长出两倍,因此是“近似平衡”。

下面就是一棵红黑树:

结合上面的图,我们可以了解下红黑树成立的各种条件:

①每个节点不是红色就是黑色

②根节点是黑色的

③如果一个节点是红色的,那么这个红色节点的两个孩子节点都是黑色的

④对于每个节点,从该节点到其所有后代叶结点的路径上,黑色节点的数量相同

⑤每个叶子节点都是黑色的(这里所说的叶子节点是上图中的空节点)

二,红黑树节点和结构定义

2.1 节点

//用枚举来标识颜色
enum Colour
{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;Colour _col;RBTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}
};

2.2 结构

template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public://此处实现各种成员函数和接口private:Node* _root = nullptr;
}

三,红黑树插入*

3.1 基本插入

基本插入也和之前的差不多,直接上代码:

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 (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);if (parent->_kv.first < kv.first){parent->_right = cur;}else{ parent->_left = cur;}cur->_parent = parent;//插入完成,开始改变颜色和调整旋转cur->_col = RED;//把新插入的节点都搞成红色,因为如果插入后改为黑色,一定违反规则四:每条路径的黑色节点数量相同//如果父亲是黑色节点或者插入前是空树,直接摊牌不玩了,不进入循环while (parent && parent->_col == RED)//父节点存在且父节点为红色{//这里的循环控制平衡,具体看下面的各种情况}
}

按搜索树的性质插入之后就是要判断红黑树的性质是否遭到破坏。

新插入节点默认为红色,因此:如果双亲节点颜色是黑色,那么没有违反红黑树的任何性质,不需要调整,就是上面代码最后的循环直接跳过;但是如果新插入的节点的双亲为红色,就违反了上面的规则“红节点的孩子为黑”,也就是出现了连续的红节点,需要调整,就是进入上面的循环部分

此时就要分下面的几条情况来讨论,维持平衡的方法的关键就是看叔叔也就是父亲的兄弟节点的状态

3.2 情况一:uncle节点存在且为红

3.2.1 cur是新增节点

如下图(假设cur是新增):

uncle存在且为红时,我们直接将父节点和叔叔节点都变成黑,再将爷爷节点变为红,但是只这样做肯定不行,比如下图:

一次调整过后就会出现上面的情况,所以需要不断往上调整

3.2.2 cur不是新增节点

如下图:

如上图,cur本身是黑色,是树中原来的节点,因为子树有新增变成了红,所以对于cur节点有两种情况符合情况一:

①本身是新增,默认新增节点为红

②子树有新增,通过情况一的向上调整变成了红 

3.3 情况二+情况三:uncle不存在或者存在为黑

从情况一我们可以看出,cur可能是新节点也可能不是新节点,但最终结果都是变红。

如果uncle节点不存在,那么cur一定是新增,如果cur不是新增,最开始循环的条件为父节点存在且为红,又因为红节点的孩子为黑这条性质,cur必定为黑,但是由于uncle不存在,导致以爷爷节点为根的子树左右子树黑节点数量不一样。

所以如果uncle不存在,cur必定为新增,如下图:

而对于uncle节点存在且为黑时, 那么cur原来肯定是黑色的因为父节点是红的,右因为左右两边黑色节点数量要一致,cur的孩子也为红,这时候在cur孩子后面新增节点时,就变成了情况一,会不断向上调整颜色,也会把cur变成红色,如下图:

所以,情况二和情况三的最终情况都是cur为红,所以可以放在一起讨论

3.4 插入代码

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 (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);if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//插入完成,开始改变颜色和调整旋转cur->_col = RED;//把新插入的节点都搞成红色,因为如果插入后改为黑色,一定违反规则四:每条路径的黑色节点数量相同//如果父亲是黑色节点或者插入前是空树,直接摊牌不玩了,不进入循环while (parent && parent->_col == RED)//父节点存在且父节点为红色{//找祖父Node* grandfather = parent->_parent;assert(grandfather);assert(grandfather->_col == BLACK);//红黑树的关键看叔叔,也就是父亲的兄弟if (parent == grandfather->_left)//父亲在爷爷的左边,叔叔分为几种情况分开讨论{Node* uncle = grandfather->_right;//父亲在左边,那么叔叔就在右边//情况一:uncle存在且为红if (uncle && uncle->_col == RED){//父亲和叔叔变黑是为了替代祖父的黑,祖父变红是为了保持黑色节点数量不变parent->_col = uncle->_col = BLACK;//把父亲和叔叔变黑grandfather->_col = RED;//把祖父变红//继续往上处理 -- 将祖父当成新增节点,循环往上处理cur = grandfather;parent = cur->_parent;}//情况二+三,uncle不存在 + 存在且为黑else{//新增在左边:右单旋+变色//      g            p//   p     u  --> c     g //c                         uif (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}//新增在右边:左单旋 + 右双旋+变色//     g//  p     u   -->   //    celse{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else//父亲在右边,叔叔可能在左边  (parent == grandfather->_right){Node* uncle = grandfather->_left;//情况一if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续往上处理cur = grandfather;parent = cur->_parent;}else //情况二+三,uncle不存在 + 存在且为黑{//新增在右边:左单旋+变色//      g//   u     p//            cif (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}//新增在左边:左右双旋+变色//     g//  u     p//       celse{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}//循环结束_root->_col = BLACK;return true;
}

四,其他接口实现

4.1 左旋转函数

//左旋转函数
void RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;//subRL可能是空if (subRL){subRL->_parent = parent;}//记录一下要旋转的parent节点的_parent,用于当parent是子树根时的调整Node* ppNode = parent->_parent;subR->_left = parent;parent->_parent = subR;//parent是整棵树的根if (_root == parent){_root = subR;subR->_parent = nullptr;}else//parent是子树的根{if (ppNode->_left == parent){ppNode->_left = subR;}else{ppNode->_right = subR;}subR->_parent = ppNode;}
}

4.2 右旋转函数

//右旋转函数
void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;//subLR可能是空if (subLR){subLR->_parent = parent;}//记录一下要旋转的parent节点的_parent,用于当parent是子树根时的调整Node* ppNode = parent->_parent;subL->_right = parent;parent->_parent = subL;//parent是整颗树的根if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}
}

4.3 检查函数

public:bool IsBalance() //根据规则来检查{if (_root && _root->_col == RED){cout << "根节点不是黑色" << endl;return false;}//定义基准值,用来判断每条路径的黑色节点数量是否相同int benchmark = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){++benchmark;}cur = cur->_left;}//检查连续红色节点return _Check(_root,0,benchmark);}
private:bool _Check(Node* root,int blackNum,int benchmark){if (root == nullptr){if (benchmark != blackNum){cout << "某条路径黑色节点数量不相等";return false;}return true;}if (root->_col == BLACK){++blackNum;}if (root->_col == RED && root->_parent && root->_parent->_col == RED){cout << "存在连续红节点" << endl;return false;}return _Check(root->_left,blackNum,benchmark) && _Check(root->_right,blackNum,benchmark);}

 4.4 打印

void InOrder()
{_InOrder(_root);cout << endl;
}
private:
void _InOrder(Node* root)
{if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);
}

4.5 查找

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;}}
}

4.6 析构

~RBTree()
{_Destroy(_root);_root == nullptr;
}
private:void _Destroy(Node* root){if (root == nullptr){return;}_Destroy(root->_left);_Destroy(root->_right);delete root;root == nullptr;}

五,红黑树源代码和测试代码

源代码(RBTree.h)

#pragma once
//最长路径不超过最短路径的两倍//红黑树和AVL树相比来说,红黑树的优点是旋转的次数更少
//如果两个树都插入1万个树,查找时,AVL树最多要查找30次,红黑树最多查找60次
// 但是这种差别对于CPU来说可以忽略不计,所以论综合性能,还是红黑树更胜一筹
#include<iostream>
#include<assert.h>
using namespace std;//用枚举来标识颜色
enum Colour
{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;Colour _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: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;}}}~RBTree(){_Destroy(_root);_root == nullptr;}//插入时和搜索树一样的插入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 (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);if (parent->_kv.first < kv.first){parent->_right = cur;}else{ parent->_left = cur;}cur->_parent = parent;//插入完成,开始改变颜色和调整旋转cur->_col = RED;//把新插入的节点都搞成红色,因为如果插入后改为黑色,一定违反规则四:每条路径的黑色节点数量相同//如果父亲是黑色节点或者插入前是空树,直接摊牌不玩了,不进入循环while (parent && parent->_col == RED)//父节点存在且父节点为红色{//找祖父Node* grandfather = parent->_parent;assert(grandfather);assert(grandfather->_col == BLACK);//红黑树的关键看叔叔,也就是父亲的兄弟if (parent == grandfather->_left)//父亲在爷爷的左边,叔叔分为几种情况分开讨论{Node* uncle = grandfather->_right;//父亲在左边,那么叔叔就在右边//情况一:uncle存在且为红if (uncle && uncle->_col == RED){//父亲和叔叔变黑是为了替代祖父的黑,祖父变红是为了保持黑色节点数量不变parent->_col = uncle->_col = BLACK;//把父亲和叔叔变黑grandfather->_col = RED;//把祖父变红//继续往上处理 -- 将祖父当成新增节点,循环往上处理cur = grandfather;parent = cur->_parent;}//情况二+三,uncle不存在 + 存在且为黑else{//新增在左边:右单旋+变色//      g            p//   p     u  --> c     g //c                         uif (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}//新增在右边:左单旋 + 右双旋+变色//     g//  p     u   -->   //    celse{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else//父亲在右边,叔叔可能在左边  (parent == grandfather->_right){Node* uncle = grandfather->_left;//情况一if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续往上处理cur = grandfather;parent = cur->_parent;}else //情况二+三,uncle不存在 + 存在且为黑{//新增在右边:左单旋+变色//      g//   u     p//            cif (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}//新增在左边:左右双旋+变色//     g//  u     p//       celse{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}//循环结束_root->_col = BLACK;return true;}void InOrder(){_InOrder(_root);cout << endl;}bool IsBalance() //根据规则来检查{if (_root && _root->_col == RED){cout << "根节点不是黑色" << endl;return false;}//定义基准值,用来判断每条路径的黑色节点数量是否相同int benchmark = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){++benchmark;}cur = cur->_left;}//检查连续红色节点return _Check(_root,0,benchmark);}
private:bool _Check(Node* root,int blackNum,int benchmark){if (root == nullptr){if (benchmark != blackNum){cout << "某条路径黑色节点数量不相等";return false;}return true;}if (root->_col == BLACK){++blackNum;}if (root->_col == RED && root->_parent && root->_parent->_col == RED){cout << "存在连续红节点" << endl;return false;}return _Check(root->_left,blackNum,benchmark) && _Check(root->_right,blackNum,benchmark);}//左旋转函数void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;//subRL可能是空if (subRL){subRL->_parent = parent;}//记录一下要旋转的parent节点的_parent,用于当parent是子树根时的调整Node* ppNode = parent->_parent;subR->_left = parent;parent->_parent = subR;//parent是整棵树的根if (_root == parent){_root = subR;subR->_parent = nullptr;}else//parent是子树的根{if (ppNode->_left == parent){ppNode->_left = subR;}else{ppNode->_right = subR;}subR->_parent = ppNode;}}//右旋转函数void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;//subLR可能是空if (subLR){subLR->_parent = parent;}//记录一下要旋转的parent节点的_parent,用于当parent是子树根时的调整Node* ppNode = parent->_parent;subL->_right = parent;parent->_parent = subL;//parent是整颗树的根if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}}//打印子函数void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);}void _Destroy(Node* root){if (root == nullptr){return;}_Destroy(root->_left);_Destroy(root->_right);delete root;root == nullptr;}
private:Node* _root = nullptr;
};

测试

#include"RBTree.h"void TestRBTree1()
{int a[] = { 4,2,6,1,3,5,15,7,16,14 };RBTree<int, int> t1;for (auto e : a){t1.Insert(make_pair(e, e));}cout << "IsBalance:" << t1.IsBalance() << endl;
}void TestRBTree2()
{size_t N = 10000;srand(time(0));RBTree<int, int> t1;for (size_t i = 0; i < N; ++i){int x = rand();cout << "Insert:" << x << ":" << i << endl;t1.Insert(make_pair(x, i));}cout << "IsBalance:" << t1.IsBalance() << endl;
}int main()
{TestRBTree2();cout << endl;TestRBTree1();return 0;
}

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

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

相关文章

(2)SpringBoot学习——芋道源码

Spring Boot 的自动配置 1.概述 EmbeddedWebServerFactoryCustomizerAutoConfiguration 类 Configuration // <1.1> ConditionalOnWebApplication // <2.1> EnableConfigurationProperties(ServerProperties.class) // <3.1> public class EmbeddedWebSe…

推荐一个好用的旧版本软件安装包下载地址

最近要下载旧版本的mysql和postman&#xff0c;发现官网和其他博客里边提供的地址&#xff0c;要不都非常难找到相应的下载链接&#xff0c;要不就是提供的从别的地方复制过来的垃圾教程&#xff0c;甚至有的下载还要积分&#xff0c;反正是最后都没下载成功&#xff0c;偶然发…

nodejs基于vue奖学金助学金申请系统08ktb

高校奖助学金系统的目的是让使用者可以更方便的将人、设备和场景更立体的连接在一起。能让用户以更科幻的方式使用产品&#xff0c;体验高科技时代带给人们的方便&#xff0c;同时也能让用户体会到与以往常规产品不同的体验风格。 与安卓&#xff0c;iOS相比较起来&#xff0c;…

期末成绩群发给家长

每当学期结束&#xff0c;老师们的邮箱和手机便会被成绩报告单填满。那么&#xff0c;如何高效地将成绩群发给家长呢&#xff1f; 一、邮件还是短信&#xff1f; 首先&#xff0c;选择一个合适的通讯方式是关键。邮件正式且便于附件&#xff0c;但短信更快捷。考虑到大多数家长…

windows平台使用tensorRT部署yolov5详细介绍,整个流程思路以及细节。

目录 Windows平台上使用tensorRT部署yolov5 前言&#xff1a; 环境&#xff1a; 1.为什么要部署&#xff1f; 2.那为什么部署可以解决这个问题&#xff1f;&#xff08;基于tensorRT&#xff09; 3.怎么部署&#xff08;只讨论tensorRT&#xff09; 3.0部署的流程 3.1怎…

UE4学习笔记 FPS游戏制作2 制作第一人称控制器

文章目录 章节目标前置概念Rotator与Vector&#xff1a;roll与yaw与pitch 添加按键输入蓝图结构区域1区域2区域3区域4 章节目标 本章节将实现FPS基础移动 前置概念 Rotator与Vector&#xff1a; Vector是用向量表示方向&#xff0c;UE中玩家的正前方是本地坐标系的(1,0,0)&…

【Linux】信号量

信号量 一、POSIX信号量1、信号量的原理2、信号量的概念&#xff08;1&#xff09;PV操作必须是原子操作&#xff08;2&#xff09;申请信号量失败被挂起等待 3、信号量函数4、销毁信号量5、等待信号量&#xff08;申请信号量&#xff09;6、发布信号量&#xff08;释放信号量&…

20240131在WIN10下配置whisper

20240131在WIN10下配置whisper 2024/1/31 18:25 首先你要有一张NVIDIA的显卡&#xff0c;比如我用的PDD拼多多的二手GTX1080显卡。【并且极其可能是矿卡&#xff01;】800&#xffe5; 2、请正确安装好NVIDIA最新的545版本的驱动程序和CUDA。 2、安装Torch 3、配置whisper http…

仰暮计划|“从米票、肉票、糖果票到肥皂票、煤票、棉花票等,生活里头的方方面面都能用粮票买到”

口述人&#xff1a;牛翠英(女) 整理人&#xff1a;霍芝冉 口述人基本信息&#xff1a;现68岁&#xff0c;河南省安阳市北关区霍家村人&#xff0c;现居河南安阳市区。 奶奶一生辛劳&#xff0c;操持家务&#xff1b;亲眼见证了时代变迁&#xff0c;社会发展&#xff0c;…

docker笔记整理

Docker 安装 添加yum源 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo 安装docker yum -y install docker-ce docker-ce-cli containerd.io docker-compose-plugin 启动docker systemctl start docker 查看docker状态 s…

09. 异常处理

目录 1、前言 2、常见的异常 3、异常处理try...except...finally 4、异常信息解读 5、raise 6、自定义异常 7、小结 1、前言 在编程中&#xff0c;异常&#xff08;Exception&#xff09;是程序在运行期间检测到的错误或异常状况。当程序执行过程中发生了一些无法继续执…

C languange DGEQRF 示例,link liblapack.a

1.示例源码 #include <stdio.h>int min(int m, int n){ return m<n? m:n;}void print_matrix(double* A, int m, int n, int lda) {for (int i 0; i < m; i){for (int j 0; j < n; j){//printf("%7.4f ", A[i j*lda]);printf("%7.4f, &quo…

创建表与删除表(六)

表的基本操作&#xff08;六&#xff09; 一、创建表 1.1 使用DDL语句创建表 CREATE TABLE 表名(列名 类型,列名 类型......); 示例&#xff1a; 创建一个 employees 表包含雇员 ID &#xff0c;雇员名字&#xff0c;雇员薪水。 create table employees(employee_id int,em…

从创新者到引领者:探索第四范式的AI之旅

大数据产业创新服务媒体 ——聚焦数据 改变商业 如今&#xff0c;人工智能已成为改变世界、驱动各行各业变革的核心源动力。在国内&#xff0c;有一些公司已走在前列&#xff0c;其中就包括北京第四范式智能技术股份有限公司&#xff0c;在AI这个赛道&#xff0c;他是一名创新…

Patch2QL:开源供应链漏洞挖掘和检测的新方向

背景 开源生态的上下游中&#xff0c;漏洞可能存在多种成因有渊源的其它缺陷&#xff0c;统称为“同源漏洞”&#xff0c;典型如&#xff1a; 上游代码复用缺陷。开源贡献者在实现功能相似的模块时&#xff0c;常复用已有模块代码或逻辑&#xff1b;当其中某个模块发现漏洞后…

java:java反编译工具--jd-gui

JD-GUI是一款反编译软件&#xff0c;JD分为JD-GUI、JD-Eclipse两种运行方式&#xff0c;JD-GUI是以单独的程序的方式运行&#xff0c;JD-Eclipse则是以一个Eclipse插件的方式运行。 官方下载地址&#xff1a; https://github.com/java-decompiler/jd-gui/releases 我这边下载…

Unity Shader 滚动进度条效果

Unity Shader 滚动进度条效果 前言项目场景布置导入图片修改场景设置修改图片尺寸即可调整进度 ASE连线 前言 UI要实现一个滚动进度&#xff0c;于是使用Shader制作一个。 项目 场景布置 导入图片 修改一下导入图片的格式&#xff0c;这样才能循环起来 WrapMode改为Repea…

正点原子--STM32中断系统学习笔记(1)

1、什么是中断&#xff1f; 原子哥给出的概念是这样的&#xff1a;打断CPU正常执行的程序&#xff0c;转而处理紧急程序&#xff0c;然后返回原暂停的程序继续运行&#xff0c;就叫中断。 当发生中断时&#xff0c;当前执行的程序会被暂时中止&#xff0c;进而进入中断处理函…

Node: opensslErrorStack: [ ‘error:03000086:digital envelope routines::initialization error‘ ]异常处理

目录 一、问题描述二、问题分析三、解决方案方案一&#xff1a;你可以按照以下步骤来删除 NODE_OPTIONS 环境变量中的 --openssl-legacy-provider 选项&#xff1a;方案二&#xff1a;在package.json更改scripts方案三&#xff1a;降级 Node.js 版本 在进行前端项目开发时&…

Linux+服务器后台运行程序

在Linux服务器直接运行程序&#xff0c;程序运行的时间较长&#xff0c;程序经常会因为网络连接问题异常终止&#xff0c;一直盯着程序运行又费时费力&#xff0c;这时后台运行程序是更好的解决方式。But&#xff0c;如果服务器重启了&#xff0c;那所有进程都断掉了&#xff0…