C++——AVL树

作者:几冬雪来

时间:2023年11月30日

内容:C++板块AVL树讲解

目录

前言: 

AVL树与搜索二叉树之间的关系: 

AVL树概念:

插入结点:

平衡因子:

旋转:

双旋: 

验证AVL树:

代码:

结尾:


前言: 

在上一篇博客中我们完成了对C++中异常的讲解,但异常在C++中并不是一个重点知识,而在更前面的二叉搜索树中我们有提到过,二叉搜索树是一个非常重要的知识板块,它作为基础会引出之后要学习AVL树与红黑树,而今天我们就来讲解二者其中的一棵树——AVL树。

AVL树与搜索二叉树之间的关系: 

在了解AVL树之前,我们先来回顾一下之前说过的AVL树和红黑树是以二叉搜索树为基础改良出来的树

那么AVL树和红黑树是怎么对二叉搜索树进行改良的

在二叉搜索树博客处我们就有说过二叉搜索树的一种特殊情况,就是上图这种二叉搜索树一边过长,另一边只有一个值的这种极端场景

这种极端场景下这棵二叉搜索树就会退化成类似链表的结果。

AVL树和红黑树就是为了解决这种情况诞生的,只不过二者解决这个问题所用的方法有些类似,但是其性质天差地别。 

因此AVL树与红黑树又被称为二叉平衡搜索树。 

AVL树概念:

了解完了AVL树之后,接下来就来讲解一下AVL树的概念。 

如果二叉搜索树插入一个新结点后,要是能保证每个结点的左右子树高度差的绝对值不超过1。这就是AVL树的做法。

而做到控制这棵树的方法有很多。

在这里控制树的方法就是引入了平衡因子,但是这里的平衡因子并不是必须的

而这个地方每个结点都有平衡因子,它的平衡因子有一套计算公式

平衡因子 = 右子树的高度 - 左子树的高度 

而这里的AVL树也是成功的控制了树的效率它的增删查改时间复杂度为O(logN)

这个地方满二叉树结点的个数为2^h-1转换为AVL树的话,这里的-1就要换为-x。与此同时我们也可以计算出x的范围是多少

插入结点:

在书写完了AVL树的基础框架之后,要想让AVL可以去控制它的平衡,在这个基础上需要我们先进行结点的插入操作

在书写完了它的插入代码之后,接下来就是要对它的平衡因子进行修改了,那要怎么去修改该结点的平衡因子呢? 

平衡因子:

在书写完了二叉搜索树的代码之后,接下来就需要去控制它的平衡

在这个地方地方有一颗AVL树,要在AVL树插入一个结点如果这个结点不平衡应该怎么办?又怎么确定它就是不平衡的呢

而为了解决这些问题,就需要对其进行假设。

这里假设出三种新结点的插入方法,然后依次对它们进行分析。

首先这个地方可以确定这个结点cur与它的父结点parent,这里插入的方法也有两种,一种是插入在parent的左边,另一种是插在parent的右边

那么它的平衡因子会发生怎么样的变化?

类似上图,类似情况一:如果父结点原先就有一个右结点,再插入一个结点到父结点左边的话,这个地方父结点的平衡因子就会从1变为0

情况二:原parent左右结点都为空,在这种情况下插入左结点父结点的平衡因子会变为-1,如果插入右结点的话,它的平衡因子会变为1

但是要注意插入的结点只会影响它的父结点与祖先的平衡因子,它其他方向是不会被影 响的

接下来就是其增加结点对父结点或者祖宗结点平衡因子的影响

在这里如果父结点在增加结点前自己原先已有左结点或者右结点,这里向它另一边增加结点崩坏改变高度,只会影响到父结点

类似左边的图不会影响到祖宗,但是右边的图AVL树的高度发生变化,会影响到祖宗

这里还需要知道一个点当parent更新等于0的时候,这里就说明parent所在的子树高度不变,不会影响到祖先,因此没有必要向上更新

相反如果更新后平衡因子等于1或者-1的话,说明它的子树高度变化,要向上进行更新。要是等于2或者-2的话,该子树的高度变化且不平衡,这里这棵树就出问题了不能继续向上更新

这里平衡因子的作用就是用来判断该AVL树是否平衡没有出现问题

既然知道了这里的平衡因子的使用方法,那么下来就要在代码中去实现它。

首先要记录父结点的平衡因子是0或者1和-1等值,这个地方就需要定义一个整形且将这个整形初始化为0

这里首先要知道什么情况下更新要停止一种情况就是parent的平衡因子为0的时候更新结束另一种是更新到parent的平衡因子为2或-2的情况,该树出问题停止更新,最后一种特殊情况那就是当更新到根节点的时候,这里也是要停止的

然后接下来就要书写更新平衡因子的代码。

因为平衡因子的更新有可能更新到根结点,因此这个地方使用parent来作为循环的条件

接下来就是对_bf的++与--,如果插入的结点为parent的左边,那么_bf就进行--操作,反之在右边进行++操作如果_bf为0则证明不用再更新了,这里就break跳出循环

然后就判断_bf为-1与1和2与-2的情况,如果平衡因子为-1或1的话cur与parent都向上移动,如果平衡因子值为2或者-2那就需要处理这种情况,最后为了防止代码出错导致_bf会变为其他值,else语句中药进行断言操作

旋转:

接下来就是平衡因子的处理了,这里如果某个结点的平衡因子为2,那就说明它的左子树高度比右子树少2

反正为-2的话则是右子树高度比左子树少2

如果发生什么两种情况的其中一种的话,那么这里就要对较高子树的那边做一个旋转的操作

但是在旋转时需要注意几个问题

1.旋转后保持它是搜索树

2.让这棵树变为平衡树且降低这棵子树的高度

再者就是旋转的原理图

这里就是旋转的原理。

如同上图,根结点或者某棵区域树的平衡因子为2,这个时候就需要用到旋转

旋转的核心操作就是把cur的左给parent的右,再让parent给cur的左。这样子做可以让这棵树依旧保持是搜索树,同时保证了平衡因子不为2或者-2,也降低了树的高度

如果根结点为2那么这个地方就需要进行左单旋,相反为-2则是进行右单旋,这样的结果都能保证根结点的平衡因子为0

这棵树旋转后相比插入之前高度发生了变化

接下来就是旋转处代码的书写。

这里就是旋转的具体操作,输入核心代码看似只有两句,但是真正写的时候需要对其进行更改的地方有很多

同时旋转的时候要注意的东西也有很多,如果稍微漏了一点则可能会导致旋转代码的失败

这里就是旋转代码的原理图。

首先给两个结点存cur的位置与cur->left的位置,再给一个结点存parent的父结点

接下来将curleft给给parent的right完成第一步连接,接下来处理cur左子树为空的情况, 然后将原parent的父结点指向cur,然后就是判断原parent结点的parent是空函数某个子树的左右结点后将其连接好修改

最后就是对平衡因子的修改,这里就是AVL树它的左单旋

而既然有左单旋,那么同样的AVL树也右单旋,对比左单旋,右单旋的操作就是将左单旋的代码进行一定的修改

这个地方就是右单旋的代码书写,它和左单旋的区别在于一个是指向cur的left,另外一个是指向cur的right

但是即使是这样,它们的实现原理还是相同的,这里的内容就不再去过多的赘述了。

而且在讲解完了左右单旋之后,接下来就要讲到AVL树的左右双旋

双旋: 

AVL树中不仅有单旋操作,同时还有双旋的操作

这里地方单旋有一个特点,那就是一定是单纯的右边高或者左边高

那么为什么只能单纯的一边高,而不是parent结点是右边高,来到cur结点变成左边高呢?这里就需要一张图来带我们对其进行了解。

这里就是只能单纯的一边高的原因,在上边的那棵树就是单纯的右边高,这个地方cur的左给parent的右后再让cur的左指向parent,这样左并不会破坏这棵树的平衡

但是下边这棵树并不是单纯的一边高在parent那里是右子树高,但是到cur处变成了左子树高在这种情况下让cur的左给parent的右,再让cur的左指向parent,这样子做还是会使得cur的平衡因子为2

而为了解决这个问题就需要使用到双旋的操作。

这里就是双旋操作的概念图。

这里在结点值为2的左子树插入一个新结点,要让这棵树变成平衡树的话,这个地方先要以3结点为旋转点进行一次右单旋再然后以1为旋转点再继续一次左单旋,这里就能成功的解决这个问题了。

而要进行的是单旋还是双旋的基本条件也是很好区分的。  

这里如果满足parent的值是2,cur的值为-1。又或者parent的值是-2,cur的值为1,这两种情况都达到了使用双旋的条件

在知道条件之后,接下来就需要书写代码了。

而且这里的双旋的代码就可以套用上面两个单旋的代码,如果parent为2,cur为-1,那么就要进行双旋操作。

首先就是将parent的右为旋转点进行一次右单旋,再以parent为旋转点进行一次左单旋,这样就能达到想要的效果。

旋转完了之后,下来就是对_bf的修改了,只不过在

这里要注意的点是,如上图在值为60的结点左右插入结点都能用双旋使它变为AVL树,但是插入的是左右哪个结点却会影响到树的造型与_bf的值

如图,如果插入在60结点的左边,最后的结果是值为90的结点平衡因子为1,值为30与60结点的平衡因子都为0

但是插入在60结点的右边,最后是60与90结点的平衡因子为0,30结点的平衡因子为-1,这就是需要注意的点。

如果值为60的结点平衡因子为0,那就说明这个结点是新增结点,在双旋之后3者的平衡因子都为0

同样的在这里值为60结点的子树的下一个结点后插入新结点,最终也使得这棵树的parent与它的左右两个结点的平衡因子不同

在上边有提及值为60的结点平衡因子是0的情况我们可以确定60是这个地方的新增结点

但是这个地方值为60的结点必须保证它自己是新增结点才行如果插入结点使得值为60的结点拥有左右子树并且它的平衡因子为0的话,这个地方结点会在更新到60处停止,并不会继续向上更新

因此我们也可以得出一个结论,如上图平衡因子的更新关键看的是值为60的这个结点。 

再接下来就是对其代码的书写,这里我们先写入的是parent的右边,也就是parent为正数时候的处理方式

首先要确定cur与cur的left的位置,再用_bf记录没旋转前cur的左left的平衡因子

旋转完了之后判断平衡因子为0,1与-1三种情况对应的curleftcur与parent各种的平衡因子是什么,这样就能成功的实现双旋的操作。

剩下的再把parent的左边双旋代码也写出来即可

验证AVL树:

在上边我们完成了结点的平衡因子的修改,并且在不同情况下平衡因子对针对不同情况进行修改,但在并不意味着这里的平衡因子就没有问题了

这里依旧会存在插入的值引发平衡因子发生异常的问题

因此就需要来验证这里的AVL树是否正常。

像这个地方的IsBlance就是来验证AVL树的

首先如果根结点为空的话就直接返回true如果不是的话,接下来就是从根结点判断它的左右子树的高度,在判断根结点左右子树的函数中再走判断根结点的左右结点对应的左右子树,用递归的形式

在然后就是判断如果它的左子树的高度-右子树的高度不等于该结点的_bf的话,这里就对代码进行报错

要是没问题的话就返回true

就像上图一样,在.cpp文件中我们创建了一个数组并且使用map的make_pair将组数组进行了排序和平衡因子的修改

但是在打印insert的值和它们对应的平衡因子的时候我们可以看见,在插入结点11的时候树发生的错误,而且这个错误导致了原先平衡因子正常值为7的结点,它的平衡因子出错了

然后就是通过条件断点和监视窗口解决这个问题。

在这里我们在值为11的结点处设置一个条件断点,接下来就是监视窗口的调试和平衡因子产生的变化

这个地方最终可以确定在右旋处有一点代码没有添加

这就是AVL树的验证方法。

代码:

AVLTree.cpp 

#include"AVLTree.h"

int main()
{
	int a[] = { 16,3,7,11,9,26,18,14,15 };
	AVLTree<int, int> t;
	for (auto e : a)
	{
		if (e == 11)
		{
			int x = 0;
		}

		t.Insert(make_pair(e, e));
		cout << "Insert:" << e << "->" << t.IsBalance() << endl;
	}
	return 0;
}

AVLTree.h 

#pragma once

#include <iostream>
#include <assert.h>
using namespace std;

template<class K,class V>
struct AVLTreeNode
{
	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	int _bf;

	AVLTreeNode(const pair<K, V>& kv)
		:_kv(kv),
		_right(nullptr),
		_left(nullptr),
		_parent(nullptr),
		_bf(0)
	{

	}
};

template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
	Node* parent;
public:
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		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;

		while (parent)
		{
			if (cur == parent->_left)
			{
				parent->_bf--;
			}

			else
			{
				parent->_bf++;
			}

			if (parent->_bf == 0)
			{
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				//继续更新
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//需要旋转
				if (parent->_bf == 2 && cur->_bf == 1)
				{
					RotateL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == -1)
				{
					RotateR(parent);
				}
				else if(parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					RotateLR(parent);
				}
				break;
			}
			else
			{
				assert(false);
			}
		}

		return true;
	}

	void RotateRL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curleft = cur->_left;
		int bf = curleft->_bf; 

		RotateR(parent->_right);
		RotateL(parent);
		if (bf == 0)
		{
			cur->_bf = 0;
			curleft->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == 1)
		{
			cur->_bf = 0;
			curleft->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			cur->_bf = 1;
			curleft->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	void RotateLR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;
		int bf = curright->_bf;

		RotateL(parent->_left);
		RotateR(parent);

		if (bf == 0)
		{
			parent->_bf = 0;
			cur->_bf = 0;
			curright->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
			cur->_bf = 0;
			curright->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = 0;
			cur->_bf = -1;
			curright->_bf = 0;
		}
	}

	void RotateL(Node* parent)  
	{ 
		Node* cur = parent->_right;
		Node* curleft = cur->_left;
		Node* ppnode = parent->_parent;

		parent->_right = curleft;
		if (curleft)
		{
			curleft->_parent = parent;
		}
		cur->_left = parent;
		parent->_parent = cur;

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

			cur->_parent = ppnode;
		}

		parent->_bf = cur->_bf = 0; 
	}

	void RotateR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;
		Node* ppnode = parent->_parent;

		parent->_left = cur->_right;
		if (curright)
		{
			curright->_parent = parent;
		}
		cur->_right = parent;
		parent->_parent = cur;
		if (ppnode == nullptr)
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = cur;
			}
			else
			{
				ppnode->_right = cur;
			}
			cur->_parent = ppnode;
		}
		parent->_bf = cur->_bf = 0;
	}

	int Height(Node* root)
	{
		if (root == nullptr)
		{
			return 0;
		}
		int leftHight = Height(root->_left);
		int rightHight = Height(root->_right);

		return leftHight > rightHight ? leftHight + 1 : rightHight + 1;
	}

	bool IsBalance()
	{
		return IsBlance(_root);
	}

	bool IsBlance(Node* root)
	{
		if (root == nullptr)
		{
			return true;
		}
		int leftHight = Height(root->_left);
		int rightHight = Height(root->_right);

		if (rightHight - leftHight != root->_bf)
		{
			cout << "平衡因子出错:" << root->_kv.first << "->" << root->_bf << endl;
			return false;
		}

		return abs(rightHight - leftHight) < 2 && IsBlance(root->_left) && IsBlance(root->_right);
	}

private:
	Node* _root = nullptr;
};

结尾:

到这里我们的AVL树就大概的讲解完毕了,在AVL树里面还会用到删除和查找操作,查找操作十分容易,但是AVL的删除操作相比插入操作有些繁琐。这里不讲解的原因是因为删除操作本质和插入操作没什么区别,只是平衡因子改动变麻烦了,实际上无论是去外应聘或者找工作什么的,都极少要求手撕。而在讲解完了AVL树之后,后面就是要讲另一棵树——红黑树,最后希望这篇博客能给各位带来帮助。

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

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

相关文章

C语言每日一题(42)删除链表的倒数第N个结点

力扣网 19 删除链表的倒数第N个结点 题目描述 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5]示例 2&#xff1a; 输入&#xff1a;head …

基于php的求书网的设计与实现

摘 要 伴随着信息技术的飞速发展&#xff0c;以及百姓生活品质的改善&#xff0c;电商也成为人们日常生活不可或缺的构成要素。网上商城已然成为了电子商务最最普遍的一种形式&#xff0c;已被大家逐渐接受并且去实施。所以本文提出的求书网站开发能够充分适合当今形势&#x…

目标检测——SPPNet算法解读

论文&#xff1a;Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition 作者&#xff1a;Kaiming He, Xiangyu Zhang, Shaoqing Ren, and Jian Sun 链接&#xff1a;https://arxiv.org/abs/1406.4729 目录 1、算法概述2、Deep Networks with Spatia…

11月30日作业

设计一个Per类&#xff0c;类中包含私有成员:姓名、年龄、指针成员身高、体重&#xff0c;再设计一个Stu类&#xff0c;类中包含私有成员:成绩、Per类对象p1&#xff0c;设计这两个类的构造函数、析构函数和拷贝构造函数 #include <iostream>using namespace std;class …

20个Python源码项目下载

20个很不错的Python项目源码&#xff0c;其中包括适合毕业设计的项目。这些资源中涵盖了Django 3版本的项目&#xff1a; DjangoMysqlBulma实现的商场管理系统源码 PythonDjango实现基于人脸识别的门禁管理系统 PythonFlaskMySQL实现的学生培养计划管理系统 Python大熊猫主题人…

qt 5.15.2压缩和解压缩功能

qt 5.15.2压缩和解压缩功能 主要是添加qt项目文件.pro内容&#xff1a; 这里要先下载quazip的c项目先编译后引入到本项目中/zip目录下 INCLUDEPATH ./zip CONFIG(debug, debug|release) {win32:win32-g: PRE_TARGETDEPS $$PWD/zip/libquazipd.awin32:win32-g: LIBS -L$$PWD…

文件格式扩展名转换:将图片png扩展名批量改为jpg的方法

在处理大量图片文件时&#xff0c;可能会遇到需要将文件格式扩展名进行转换的情况。比如&#xff0c;将图片文件从PNG格式转换为JPG格式。这不仅可以节省存储空间&#xff0c;还可以提高图片加载速度&#xff0c;特别是在网页设计中。本文详解如何将PNG图片批量转换为JPG格式的…

2023-11-30 LeetCode每日一题(确定两个字符串是否接近)

2023-11-30每日一题 一、题目编号 1657. 确定两个字符串是否接近二、题目链接 点击跳转到题目位置 三、题目描述 如果可以使用以下操作从一个字符串得到另一个字符串&#xff0c;则认为两个字符串 接近 &#xff1a; 操作 1&#xff1a;交换任意两个 现有 字符。 例如&…

LeetCode Hot100 3.无重复字符的最长子串

题目&#xff1a; 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 代码&#xff1a; class Solution {public int lengthOfLongestSubstring(String s) {char[] arr s.toCharArray(); // 转换成 char[] 加快效率&#xff08;忽略带来的空间…

[操作系统] 文件管理

文章目录 5.1 磁盘调度算法1. 先来先服务算法( First Come First Served, FCFS) 算法2. 最短寻道时间优先算法( Shortest Seek Time First, SSTF) 算法3. 扫描算法( SCAN ) 算法4. 循环扫描算法( Circular Scan, CSCAN ) 算法5. LOOK 与 CLOOK 算法 5.2 进程写文件时&#xff0…

Goby 漏洞发布| CrushFTP as2-to 认证权限绕过漏洞(CVE-2023-43177)

漏洞名称&#xff1a; CrushFTP as2-to 认证权限绕过漏洞&#xff08;CVE-2023-43177&#xff09; English Name&#xff1a;CrushFTP as2-to Authentication Permission bypass Vulnerability (CVE-2023-43177) CVSS core: 9.8 影响资产数&#xff1a; 38695 漏洞描述&…

【Web】UUCTF 2022 新生赛 个人复现

目录 ①websign ②ez_rce ③ez_upload ④ez_unser ⑤ezsql ⑥ezpop ⑦funmd5 ⑧phonecode ⑨ezrce ①websign 右键打不开&#xff0c;直接抓包发包看源码 ②ez_rce “反引号” 在PHP中会被当作SHELL命令执行 ?codeprintf(l\s /); ?codeprintf(ta\c /ffffffffffl…

商家门店小程序怎么做?门店小程序的优势和好处

生活服务类商家在当前数字化时代&#xff0c;越来越认识到门店小程序的重要性。门店小程序不仅为商家提供了一个在线展示的窗口&#xff0c;更为其打造了一个与消费者直接互动的平台。有了门店小程序&#xff0c;商家可以更加便捷地管理商品信息、订单流程&#xff0c;同时还能…

Linux常用命令----history命令

文章目录 在Linux中&#xff0c;history命令是一个极其有用的工具&#xff0c;它可以帮助用户查看和管理之前执行过的命令历史。这个功能对于快速查找和重用之前的命令特别有帮助。下面&#xff0c;我们将通过一些实例&#xff0c;详细介绍history命令的使用方法。 1. 基本使用…

【稳定检索|投稿优惠】2024年经济管理与安全科学国际学术会议(EMSSIC 2024)

2024年经济管理与安全科学国际学术会议(EMSSIC 2024) 2024 International Conference on Economic Management and Security Sciences(EMSSIC 2024) 一、【会议简介】 2024年经济管理与安全科学国际学术会议(EMSSIC 2024)&#xff0c;将于繁华的上海城召开。这次会议的主题是“…

前端:实现div的隐藏与显示

效果 完整代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-widt…

WSL2 docker GUI 界面

在 WSL2 docker 中运行GUI界面。 具体流程和远程显示Ubuntu界面类似&#xff0c;链接, 更简单一点&#xff0c; 少了 ssh 的部分。 安装好wsl2 和 docker wsl2 运行GUI程序&#xff0c;windows 会默认弹出窗口。 可以安装 gedit 测试一下 windows 下载并运行 Xlaunch. 运行 d…

力扣202题 快乐数 双指针算法

快乐数 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。然后重复这个过程直到这个数变为 1&#xff0c;也可能是 无限循环 但始终变不到 1。如果这个过程 结果为 1&#…

LLM 分布式训练框架 | DeepSpeed与Accelerate

&#x1f680; 简单记录下根据网上资料&#xff08;如Reference中所列&#xff09;所学到的一些知识&#xff0c;这里主要介绍的是deepspeed分布式训练框架相关概念。 &#x1f604;小日记&#xff1a;今天太舒服了&#xff0c;早上跑了6km&#xff0c;晚上吃了养生菌菇火锅~ …

亚马逊云科技re:Invent Peter DeSantis演讲,数据规模拓展无极限引领Serverless构建之路

re:lnvent 2023 Peter DeSantis主题演讲&#xff0c;数据规模拓展无极限引领Serverless构建之路&#xff08;Road to Serverless&#xff09;。 Logical Qubit全新发布&#xff1a;量子计算硬件&#xff0c;6倍的量子纠错效率提升。 Amazon全新发布Redshift Serverless&#xf…
最新文章