C++(搜索二叉树)

目录

前言:

 1.二叉搜索树

1.1二叉搜索树的定义 

1.2二叉搜索树的特点 

2.二叉搜索树的实现 

2.1框架

2.2查找 

2.3插入 

 2.4删除

1.右子树为空 

 2.左子树为空

 3.左右都不为空

3.递归版本

3.1前序遍历

3.2中序遍历 

3.3后续遍历 

3.4查找(递归版) 

3.5插入(递归版) 

3.6删除(递归版)

4.内部函数补充 

 4.1销毁

4.2拷贝构造和赋值重载

5.应用场景

 5.1单key场景

 5.2key-value场景

6 面试经典题



前言:

二叉树在前面 数据结构阶段已经讲过,本节取名二叉树进阶是因为:
1. map set 特性需要 先铺垫二叉搜索树,而二叉搜索树也是一种树形结构
2. 二叉搜索树的特性了解,有助于更好的理解 map set 的特性
3. 二叉树中部分面试题稍微有点难度 ,在最后对常见的面试题进行复盘
4. 有些 OJ 题使用 C 语言方式实现比较麻烦,比如有些地方要返回动态开辟的二维数组,非常麻
烦。
因此本节借二叉树搜索树,对二叉树部分进行收尾总结

 1.二叉搜索树

1.1二叉搜索树的定义 

 二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树

        对于搜索二叉树,对数据进行查找时,理想情况下时间复杂度为logN(二分查找),搜索查找还是很快的。

1.2二叉搜索树的特点 

         对于搜索二叉树,对该树进行中序遍历,得到的就是升序结果。比如一颗二叉搜索树如下图所示,对其进行中序遍历得到的结果为【1,3,4,6,7,8,10,13,14】

2.二叉搜索树的实现 

         我们想对一颗二叉搜索树进行增删查改,我们就要种一棵树,这棵树上的果子就是节点。

2.1框架

         利用学过的类和对象、泛型编程,对搜索二叉树的框架进行搭建

namespace Cmx //创建一个属于自己的域
{
	template <class T>
	struct BSTreeNode
	{
	    BSTreeNode<T>* _left;
		BSTreeNode<T>* _right;
		T _val;
		BSTreeNode(const T&val)
			:_left(nullptr)
			,_right(nullptr)
			,_val(val)
		{}
	};
	template <class T>
	class BSTree
	{
		typedef BSTreeNode<T> Node;
     public:
        //需要后续实现的函数 增删改查,前后中遍历,递归版本
     private:
         Node* _root;
    };
}

 二叉搜索树的节点类需要写出构造函数,因为后面创建新节点时会用到;二叉搜索树的根可以给个缺省值 nullptr,确保后续判断不会出错.

2.2查找 

 因为是对确定的值进行查找,所以需要有比较的过程:        

        a、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
        b、最多查找高度次,走到到空,还没找到,这个值不存在。
bool find(const T& key)
		{
			Node* cur = _root;
			while (cur)
			{
				if (cur->_val < key)
				{
					cur = cur->_right;
				}
				else if (cur->_val > key)
				{
					cur = cur->_left;
				}
				else
				{
					return true;
				}
			}

			return false;
		}

 当查找的值存在时

当查找的值不存在时 

2.3插入 

插入的具体过程如下:(非重复版)

        a. 树为空,则直接新增节点,赋值给root 指针
        b. 树不空,按二叉搜索树性质查找插入位置,插入新节点

bool Insert(const T& key)
		{
			if (_root == nullptr)
			{
				_root = new Node(key);
				return true;
			}

			Node* parent = nullptr;
			Node* cur = _root;
			while (cur)
			{
				if (cur->_val < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_val > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					return false;
				}
			}

			cur = new Node(key);
			if (parent->_val < key)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}

			return true;
		}

 2.4删除

        删除需要注意的地方很多,也是面试常考的地方,删除逻辑:

首先查找元素是否在二叉搜索树中,如果不存在,则返回 , 否则要删除的结点可能分下面四种情
况:
a. 要删除的结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点
看起来有待删除节点有 4 中情况,实际情况 a 可以与情况 b 或者 c 合并起来,因此真正的删除过程
如下:
情况 b :删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点 -- 直接删除
情况 c :删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点 -- 直接删除
情况 d :在它的右子树中寻找中序下的第一个结点 ( 关键码最小 ) ,用它的值填补到被删除节点
中,再来处理该结点的删除问题 -- 替换法删除

1.右子树为空 

 右子树为空时,只 需要将其左子树与父节点进行判断链接即可,无论其左子树是否为空,都可以链接,链接完成后,删除目标节点

 

 2.左子树为空

 操作同上,转换托孤方向。

 3.左右都不为空

         当左右都不为空时,就有点麻烦了,需要找到一个合适的值(即 > 左子树所有节点的值,又 < 右子树所有节点的值),确保符合二叉搜索树的基本特点

        符合条件的值有:左子树的最右节点(左子树中最大的)、右子树的最左节点(右子树中最小的),将这两个值中的任意一个覆盖待删除节点的值,都能确保符合要求

        这里找的是待删除节点 左子树的最右节点

为什么找 左子树的最右节点或右子树的最左节点的值覆盖 可以符合要求?

因为左子树的最右节点是左子树中最大的值,> 左子树所有节点(除了自己),< 右子树所有节点
右子树的最左节点也是如此,都能符合要求
通俗理解:需要找待删除节点的值的兄弟来镇住这个位置,而它的兄弟自然就是 左子树最右节


		bool Erase(const T& key)
		{
			Node* parent = nullptr;
			Node* cur = _root;
			while (cur)
			{
				if (cur->_val < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_val > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					//找到该值
					//1.该节点左为空
					if (cur->_left == nullptr)
					{
						if (cur == _root)
						{
							_root = cur->_right;
						}
						else
						{
							if (parent->_left = cur)
							{
								parent->_left = cur->_right;
							}
							else
							{
								parent->_right = cur->_right;
							}

						}

					}
					else if (cur->_right == nullptr)
					{
						if (cur == _root)
						{
							_root = cur->_left;
						}
						else
						{
							if (parent->_left = cur)
							{
								parent->_left = cur->_left;
							}
							else 
							{
								parent->_right = cur->_left;
							}

						}

					}
					else  //左右都不为空
					{
						Node* parent = cur;
						Node* leftMax = parent->_left;
						while (leftMax->_right)
						{
							parent = leftMax;
							leftMax = leftMax->_right;
						}
						swap(cur->_val, leftMax->_val);
						if (parent->_left = leftMax)
						{
							parent->_left = leftMax->_left;
						}
						else
						{
							parent->_right = leftMax->_left;
						}
						cur = leftMax;
					}
					delete cur;
					return true;
				}
			}
			return false;
		}

点 和 右子树最左节点,配合中序遍历结果可以确认

注意:

涉及更改链接关系的操作,都需要保存父节点的信息
右子树为空、左子树为空时,包含了删除 根节点 的情况,此时 parent 为空,不必更改父节点链接关系,更新根节点信息后,删除目标节点即可,因此需要对这种情况特殊处理
右子树、左子树都为空的节点,包含于 右子树为空 的情况中,自然会处理到
左右子树都不为空的场景中,parent 要初始化为 cur,避免后面的野指针问题


3.递归版本

我将二叉搜索树树的前序后序中序遍历放在这里,因为对于不同序的访问,我是利用递归实现的。

包括之前的插入,删除,查找我都要用递归实现。 

3.1前序遍历

前序:根 -> 左 -> 右

在递归遍历时,先打印当前节点值(根),再递归左子树(左),最后递归右子树(右)

因为这里是一个被封装的类,所以面临着一个尴尬的问题:二叉搜索树的根是私有,外部无法直接获取

解决方案:

公有化(不安全,也不推荐)
通过函数获取(安全,但用着很别扭)
将这种需要用到根的函数再封装(完美解决方案)
这里主要来说说方案3:类中的函数可以直接通过 this 指针访问成员变量,但外部可没有 this 指针,于是可以先写一个外壳(不需要传参的函数),在这个外壳函数中调用真正的函数即可,因为这个外壳函数在类中,所以此时可以通过 this 指针访问根 _root

具体操作如下:

	//===遍历===
	void PrevOrder()
	{
		_PrevOrder(_root);
	}

protected:
	void _PrevOrder(const Node* root)
	{
		if (root == nullptr)
			return;

		//前序:根左右
		cout << root->_key << " ";
		_PrevOrder(root->_left);
		_PrevOrder(root->_right);
	}

  

3.2中序遍历 

中序:左 -> 根 -> 右

在递归遍历时,先递归左子树(),再打印当前节点值(),最后递归右子树(

中序遍历也需要用到根,同样对其进行再封装

		void InOrder()
		{
			_InOrder(_root);
		}

protected:
		void _InOrder(const Node* root)
		{
			if (root == nullptr)
				return;

			//中序:左根右
			_InOrder(root->_left);
			cout << root->_key << " ";
			_InOrder(root->_right);
		}

3.3后续遍历 

后序:左 -> 右-> 根

在递归遍历时,先递归左子树(),再递归右子树(),最后打印当前节点值(

一样需要进行再封装

		void PostOrder()
		{
			_PostOrder(_root);
		}
protected:
		void _PostOrder(const Node* root)
		{
			if (root == nullptr)
				return;

			//后序:左右根
			_PrevOrder(root->_left);
			_PrevOrder(root->_right);
			cout << root->_key << " ";
		}

3.4查找(递归版) 

递归查找逻辑:如果当前根的值 < 目标值,递归至右树查找;如果当前根的值 > 目标值,递归至左树查找;否则就是找到了,返回 true

因为此时也用到了根 _root,所以也需要进行再封装

		//===递归实现===
		bool FindR(const K& key) const
		{
			return _FindR(_root, key);
		}
protected:
		//递归实现
		bool _FindR(Node* root, const K& key) const
		{
			//递归至叶子节点也没找到
			if (root == nullptr)
				return false;
			
			//递归至右树
			if (root->_key < key)
				return _FindR(root->_right, key);
			//递归至左树
			else if (root->_key > key)
				return _FindR(root->_left, key);
			//找到了
			else
				return true;
		}

3.5插入(递归版) 

基于递归查找的逻辑,实现递归插入

此时用到了一个很nb的东西:引用,实际插入时,甚至都不需要改链接关系,直接赋值即可

		bool InsertR(const K& key)
		{
			return _InsertR(_root, key);
		}
protected:
		bool _InsertR(Node*& root, const K& key)
		{
			if (root == nullptr)
			{
				//得益于引用,可以对不同栈帧中的值进行修改
				root = new Node(key);
				return true;
			}

			//递归至右树
			if (root->_key < key)
				return _InsertR(root->_right, key);
			//递归至左树
			else if (root->_key > key)
				return _InsertR(root->_left, key);
			//冗余了,无法插入
			else
				return false;
		}

 因为此时的参数是 节点指针的引用,所以在 保持原有链接属性的前提下,改变当前节点,即插入节点

3.6删除(递归版)

递归删除时也使用了引用,这样可以做到 在不同的栈帧中,删除同一个节点,而非临时变量

同时递归删除还用到了一种思想:转换问题的量级

比如原本删除的是根节点,但根节点之下还有很多节点,直接删除势必会造成大量的链接调整,于是可以找到 “保姆”(左子树的最右节点或右子树的最左节点),将 “保姆” 的值与待删除节点的值交换,此时递归至保姆所在的子树进行删除

因为保姆必然只带一个子树或没有子树,所以删除起来很简单

		bool EraseR(const K& key)
		{
			return _EraseR(_root, key);
		}
protected:
		bool _EraseR(Node*& root, const K& key)
		{
			if (root == nullptr)
				return false;

			if (root->_key < key)
				return _EraseR(root->_right, key);
			else if(root->_key > key)
				return _EraseR(root->_left, key);
			else
			{
				Node* del = root;	//需要保存一下待删除的节点信息

				//如果右树为空,则直接将左树覆盖上来
				if (root->_right == nullptr)
					root = root->_left;
				//如果左树为空,则直接将右树覆盖上来
				else if (root->_left == nullptr)
					root = root->_right;
				else
				{
					//递归为小问题去处理
					Node* maxLeft = root->_left;
					while (maxLeft->_right)
						maxLeft = maxLeft->_right;

					//注意:需要交换
					std::swap(root->_key, maxLeft->_key);

					//注意:当前找的是左子树的最右节点,所以递归从左子树开始
					return _EraseR(root->_left, key);
				}

				delete del;	//释放节点
				return true;
			}
		}

注意:

再次递归时,需要传递 root->_left 而非 maxLeft,因为此时的 maxLeft 是临时变量,而函数参数为引用
传递 root->_left 的原因:找的保姆出自左子树的最右节点,所以要求左子树中找,不能只传递 root,这样会导致查找失败 -> 删除失败
要使用 swap 交换 maxLeft->_key 与 key,然后递归时,找的就是 key;如果不使用交换而去使用赋值,那么递归查找的仍是 maxLeft->_key,类似于迭代删除时,将多余的节点删除

4.内部函数补充 

 4.1销毁

创建节点时,使用了 new 申请堆空间,根据动态内存管理原则,需要使用 delete 释放申请的堆空间,但二叉搜索树是一棵树,不能直接释放,需要 递归式的遍历每一个节点,挨个释放

释放思路:后序遍历思想,先将左右子树递归完后,才释放节点

		~BSTree()
		{
			destory(_root);
		}
protected:
		//细节问题
		void destory(Node*& root)
		{
			if (root == nullptr)
				return;

			//后序销毁
			destory(root->_left);
			destory(root->_right);

			delete root;
			root = nullptr;
		}

4.2拷贝构造和赋值重载

        

	BSTree(const BSTree<T>& t)
		{
			_root = (t._root);
		}
		BSTree<T>& operator=(BSTree<T> t)
		{
			swap(_root, t._root);
			return *this;
		}

5.应用场景

 5.1单key场景

key 模型的应用场景:在不在

  • 门禁系统
  • 车库系统
  • 检查文章中单词拼写是否正确

这些都是可以利用 key 模型解决,其实我们上面写的就是 key 模型,下面通过一段演示代码,展示 key 模型实现 单词查找系统

void BSTreeWordFind()
{
	vector<string> word = { "apple", "banana", "milk", "harmony" };
	Yohifo::BSTree<string> table;

	for (auto e : word)
		table.Insert(e);

	string str;
	while (cin >> str)
	{
		if (table.Find(str))
			cout << "当前单词 " << str << " 存在于词库中" << endl;
		else
			cout << "当前单词 " << str << " 没有在词库中找到" << endl;
	}
}

 5.2key-value场景

key / value 的模型:应用搜索场景

中英文互译字典
电话号码查询快递信息
电话号码 + 验证码
key / value 模型比 key 模型 多一个 value,即 kv 模型除了可以用来查找外,还可以再带一个值用于统计,这其实就是哈希的思想(建立映射关系)

kv 模型需要将代码改一下,新增一个模板参数 class value,插入时新增一个参数,同时操作也会有轻微改动,查找时返回的不再是 bool ,而是指向当前节点的指针,其他操作可以不用变

注:即使是 kv 模型,也只是将 key 作为查找、插入、删除的依据,基本逻辑与 value 没有任何关系,value 仅仅起到一个存储额外值的作用

将代码进行小改动,具体可查看源码

实现一个简单的中英词典

void BSTreeDictionary()
{
	vector<pair<string, string>> word = { make_pair("apple", "苹果"), make_pair("banana", "香蕉"), make_pair("milk", "牛奶"), make_pair("harmony", "鸿蒙")};
	Yohifo::BSTreeKV<string, string> table;

	for (auto e : word)
		table.InsertR(e.first, e.second);

	string str;
	while (cin >> str)
	{
		Yohifo::BSTreeNodeKV<string,string>* ret = table.FindR(str);

		if (ret)
			cout << "当前单词 " << str << " 存在于词库中,翻译为 " << ret->_value << endl;
		else
			cout << "当前单词 " << str << " 没有在词库中找到" << endl;
	}
}

6 面试经典题

1. 二叉树创建字符串。 OJ 链接
2. 二叉树的分层遍历 1 OJ 链接
3. 二叉树的分层遍历 2 OJ 链接
4. 给定一个二叉树 , 找到该树中两个指定节点的最近公共祖先 。 OJ 链接
5. 二叉树搜索树转换成排序双向链表。 OJ 链接
6. 根据一棵树的前序遍历与中序遍历构造二叉树。 OJ 链接
7. 根据一棵树的中序遍历与后序遍历构造二叉树。 OJ 链接
8. 二叉树的前序遍历,非递归迭代实现 。 OJ 链接
9. 二叉树中序遍历 ,非递归迭代实现。 OJ 链接
10. 二叉树的后序遍历 ,非递归迭代实现。 OJ 链接

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SPA单页面的讲解(超级详细)

目录 一、什么是SPA 二、SPA和MPA的区别 单页应用与多页应用的区别 单页应用优缺点 三、实现一个SPA 原理 实现 hash 模式 history模式 四、题外话&#xff1a;如何给SPA做SEO SSR服务端渲染 静态化 使用Phantomjs针对爬虫处理 一、什么是SPA SPA&#xff08;sin…

Windows打开IE浏览器命令最简单的方法

问题场景&#xff1a; 许多插件或特定版本的系统需要使用ie浏览器来访问&#xff0c;window默认的ie浏览器是被禁用的如何快速打开ie浏览器解决问题 目录 问题场景&#xff1a; 测试环境&#xff1a; 检查环境是否支持&#xff1a; 问题解决&#xff1a; 方法一 方法二 方法…

支持IPv4与IPv6双协议栈的串口服务器,IPv6串口服务器

物联网是啥玩意儿&#xff1f;这是首先要搞明白的。按照百度百科的说法&#xff0c;是将各种信息传感设备&#xff0c;如射频识别&#xff08;RFID&#xff09;装置、红外感应器、全球定位系统、激光扫描器等种种装置与互联网结合起来而形成的一个巨大网络。这个说法有些复杂&a…

【JaveWeb教程】(33)SpringBootWeb案例之《智能学习辅助系统》的详细实现步骤与代码示例(6)修改员工的实现

目录 SpringBootWeb案例063. 修改员工3.1 查询回显3.1.1 接口文档3.1.2 实现思路3.1.3 代码实现3.1.4 postman测试 3.2 修改员工3.2.1 接口文档3.2.2 实现思路3.2.3 代码实现3.2.4 postman测试3.2.5 前后端联调测试 SpringBootWeb案例06 前面我们已经实现了员工信息的条件分页…

linux jenkins相关命令

1.jenkins启动命令 [rootlocalhost /]#service jenkins start 2.jenkins停止命令 [rootlocalhost /]#service jenkins stop 3.查询jenkins状态命令 [rootlocalhost /]#service jenkins status 4.重启jenkins命令 [rootlocalhost /]#service jenkins restart Jenkins默认的端口号…

牵手国际企业,OpenAI计划自己制造AI芯片

据外媒报道称&#xff0c;近日&#xff0c;OpenAI的首席执行官萨姆奥特曼正在积极洽谈一项规模达数十亿美元的投资项目&#xff0c;计划与多家顶级芯片制造商合作&#xff0c;建设一个覆盖全球的人工智能芯片生产网络。 奥特曼曾多次反馈目前的芯片已不能满足OpenAI公司的AI研发…

九、Kotlin 注解

1. 什么是注解 注解是对程序的附件信息说明。 注解可以作用在类、函数、函数参数、属性等上面。 注解的信息可用于源码级、编译期、运行时。 2. 注解类的定义 使用元注解 Retention 声明注解类的作用时期。 使用元注解 Target 声明注解类的作用对象。 定义注解类时可以声…

Linux详细笔记大全

第0章 Linux基础入门 什么是计算机 计算机的组成: 控制器,是整个计算机的中枢神经,根据程序要求进行控制,协调计算机各部分工作及内存与外设的访问等。 运算器,功能是对数据进行各种算术运算和逻辑运算。 存储器,功能是存储程序、数据和各种信号、命令等信息。 输入设备…

vue3-element-admin的组件el-time-picker设置只能选择上午或下午

上午&#xff1a; <el-time-picker style"width: 80%;" :disabled"!top_status" is-range v-model"top_time"range-separator"至" start-placeholder"开始时间" end-placeholder"结束时间" placeholder"…

Spring Security 存储密码之 JDBC

Spring Security的JdbcDaoImpl实现了UserDetailsService接口,通过使用JDBC提供支持基于用户名和密码的身份验证。 JdbcUserDetailsManager扩展了JdbcDaoImpl,通过UserDetailsManager接口提供UserDetails的管理功能。 当Spring Security配置为接受用户名/密码进行身份验证时,…

59.螺旋矩阵II(力扣LeetCode)

59.螺旋矩阵II 题目描述 给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[[1,2,3],[8,9,4],[7,6,5]] 示例 2&#xff1a; 输…

Arduino开发实例-DRV8833电机驱动器控制直流电机

DRV8833电机驱动器控制直流电机 文章目录 DRV8833电机驱动器控制直流电机1、DRV8833电机驱动器介绍2、硬件接线图3、代码实现DRV8833 使用 MOSFET,而不是 BJT。 MOSFET 的压降几乎可以忽略不计,这意味着几乎所有来自电源的电压都会传递到电机。 这就是为什么 DRV8833 不仅比基…

php项目下微信小程序对接实战问题与解决方案

一.实战问题与方案总结 1.SQL查询条件是一组数&#xff0c;传参却是一个字符串导致报错&#xff0c;如下 SQLSTATE[HY093]: Invalid parameter number (SQL: select count(*) as aggregate from car_video where province_id in (1492) and city_id in (1493) and county_id …

GEM5 Garnet Standalone 命令行与stats.txt结果分析

简介 展示了不同的命令行与结果,作为初步的了解. 命令行 sim-cycles要大,不然没结果 ./build/NULL/gem5.debug configs/example/garnet_synth_traffic.py –num-cpus16 –num-dirs16 –networkgarnet –topologyMesh_XY –mesh-rows4 –sim-cycles1000000 --inj-vnet…

php+Layui开发的网站信息探针查询源码

信息探针是一款基于layui开发的专业查询好友个人信息的程序。 自定义设置探针页面&#xff0c;探针功能&#xff0c;QQ分享&#xff0c;通知邮箱等功能。 生成页面链接好友点击会出现好友ip 位置信息&#xff0c;手机型号ua头浏览器等信息 gps需要注册百度地图开发者才可以使用…

找不到msvcp110.dll怎么办,msvcp110.dll丢失修复方法分享

当计算机系统中无法找到msvcp110.dll这个特定的动态链接库文件时&#xff0c;可能会引发一系列运行问题和功能受限的情况。msvcp110.dll是Microsoft Visual C Redistributable Package的一部分&#xff0c;对于许多基于Windows的应用程序来说&#xff0c;它是至关重要的运行组件…

数据结构——用链表实现Map

目录 一、映射&#xff08;Map&#xff09; 二、代码实现 1.建立接口 2.方法实现 &#xff08;1&#xff09;映射的建立 键&#xff08;key&#xff09;和值&#xff08;val&#xff09;的建立 重写toString方法 &#xff08;2&#xff09;构造方法 &#xff08;3&…

Springfox Swagger3从入门案例

首先&#xff0c;在pom.xml中添加依赖&#xff1a; <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>io…

【从零到一】跑通CATR(一):并行超算云的环境配置

从零到一配环境篇 由于今年要展开大量的编程工作&#xff0c;实验室在用的云计算平台是并行超算云&#xff0c;因此打算在寒假期间先熟悉一下超算云的环境&#xff0c;并从配套的文档和网上的教程开始&#xff0c;从零到一先跑通一个用于音视频分割的模型CATR。 以blog的形式…

vue项目打包部署到服务器并使用cdn加速

配置 vue.config.js文件 const isProd process.env.NODE_ENV production module.exports {// 其他配置chainWebpack: config > {// 生产环境下使用CDNif (isProd) {config.plugin(html).tap(args > {args[0].cdn assetsCDNreturn args})}},// 生产环境下替换路径为c…