【算法】Splay详解

news2024/9/22 15:36:46

Splay

      • 引入
  • Splay
      • 旋转操作
      • splay操作
      • 插入操作
      • 查询x排名
      • 查询排名为x
      • 删除操作
      • 查询前驱/后继
      • 模板
      • Splay时间复杂度分析
    • 进阶操作
      • 截取区间
      • 区间加,区间赋值,区间查询,区间最值
      • 区间翻转
      • 原序列整体插入
      • 指定位置插入
      • 整体插入末尾
      • 区间最大子段和
    • 一些好题
    • 参考文献

引入

首先我们要知道一个东西叫二叉搜索树。
其定义如下:

  1. 空树是二叉搜索树。
  2. 若二叉搜索树的左子树不为空,则其左子树上所有点的附加权值均小于其根节点的值。
  3. 若二叉搜索树的右子树不为空,则其右子树上所有点的附加权值均大于其根节点的值。
  4. 二叉搜索树的左右子树均为二叉搜索树。

二叉搜索树上的基本操作所花费的时间与这棵树的高度成正比。对于一个有 n n n 个结点的二叉搜索树中,这些操作的最优时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn)

如图就是一颗典型的 BST(二叉查找树)
在这里插入图片描述
可是我们发现,如果树退化成一条链,那么时间复杂度将退化为 O ( n ) O(n) O(n),这是我们不能接受的,于是平衡树孕育而生,其核心就是维护一颗相对平衡的 BST。
本文将介绍Splay,虽然它并不能保证树一直是"平衡"的,但对于Splay的一系列操作,我们可以证明其每步操作的平摊复杂度都是 O ( log ⁡ n ) O(\log n) O(logn)。所以从某种意义上说,Splay也是一种平衡的二叉查找树。

Splay

旋转操作

下面参考 OI-WIKI的介绍。
在这里插入图片描述
注意,左右旋指的是向左或右旋转。
左旋为ZAG,右旋为ZIG
以下是一次标准旋转操作:
在这里插入图片描述
我们可以知道,旋转流程如下:
在这里插入图片描述

于是我们便可以写出 ZIG和ZAG函数,参考下列代码:
在这里插入图片描述
在这里插入图片描述
不过有时候为了方便表示,我们可以把两个旋转操作合并起来。
就成了 rotate(旋转)函数,以下是参考代码:

void rotate(int x){
		int y=fa[x],z=fa[y],id=son_(x);
		ch[y][id]=ch[x][id^1];
		if(ch[x][id^1])
			fa[ch[x][id^1]]=y;
		ch[x][id^1]=y;
		fa[y]=x;
		fa[x]=z;
		if(z)
			ch[z][y==ch[z][1]]=x;
		pushup(y);
		pushup(x);
	}

其中 son_( x x x)是判断 x x x 为父节点的左儿子还是右儿子,pushup为由下往上更新。

splay操作

这个操作可以说是Splay的核心操作之一,可以理解为把某个点通过旋转操作旋转到根节点。
那么如何将一个节点旋转到根节点呢?
首先有 6 6 6 种基本情况,见下图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
那么我们只需要不断重复执行旋转操作,即可旋转到根节点。
以下是参考代码:

void splay(int x) {
  for (int f = fa[x]; f = fa[x], f; rotate(x))
    if (fa[f]) 
    	rotate(get(x) == get(f) ? f : x);
  rt = x;
}

一些进阶
由于后面某些操作需要用到,所以我们对splay函数进行一些修改。
具体而言,我们引入一个参数 y y y,让splay把 x x x 旋转到 y y y 的儿子上。(当 y = 0 y=0 y=0 时将 x x x 旋转到根节点)
其实也没什么改动,见参考代码:

void splay(int x,int y){
	while(fa[x]!=y){
		if(fa[fa[x]]!=y){
			if(son_(fa[x])==son_(x))
				rotate(fa[x]);
			else
				rotate(x);
		}
		rotate(x);
	}
	if(!y)
		rt=x;
}

插入操作

在这里插入图片描述

解释一下:
二叉树的性质使得插入操作变得非常简易,具体而言,只要值比当前节点大,就往右子树找,小就往左子树找,一样就让计数器+1,如果找不到匹配的值就直接新建一个节点。
参考代码:

	void add(int k){
		if(!rt){
			rt=++idx;
			cnt[rt]++,val[rt]=k;
			pushup(rt);
			return ;
		}
		int x=rt,y=0;
		while(1){
			if(val[x]==k){
				cnt[x]++;
				pushup(x),pushup(y);
				splay(x,0);
				break;
			}
			y=x;
			x=ch[x][val[x]<k];
			if(!x){
				cnt[++idx]++,val[idx]=k;
				fa[idx]=y;
				ch[y][val[y]<k]=idx;
				pushup(idx);
				pushup(y);
				splay(idx,0);
				break;
			}
		}
	}

查询x排名

这个跟插入差不多,从根节点不断往下找,每次向右子树找时加上左子树的size+1,因为左子树和根的值一定比查询值小(BST的性质)。
具体详见代码:

	int x_rank(int k){
		int rk=0,x=rt;
		while(1){
			if(k<val[x])
				x=ch[x][0];
			else{
				rk+=sz[ch[x][0]];
				if(!x)
					return rk+1;
				if(k==val[x]){
					splay(x,0);
					return rk+1;
				}
				rk+=cnt[x];
				x=ch[x][1];
			}
		}
	}

查询排名为x

这个跟上面两个操作都差不多,不断往下找就行了。
看着代码,画画图也就能理解了。

	int kth(int k){
		int x=rt;
		while(1){
			if(ch[x][0]&&k<=sz[ch[x][0]])
				x=ch[x][0];
			else{
				k-=sz[ch[x][0]];
				if(k<=cnt[x]){
					splay(x,0);
					return val[x];
				}
				k-=cnt[x];
				x=ch[x][1];
			}
		}
	}

删除操作

在这里插入图片描述
这个就感性理解一下。
参考代码:

	void del(int k){
		x_rank(k);
		int x=rt,y=0;
		if(cnt[rt]>1)
			cnt[rt]--,pushup(rt);
		else if(!ch[rt][0]&&!ch[rt][1])
			clean(rt),rt=0;
		else if(!ch[rt][0]){
			rt=ch[rt][1];
			fa[rt]=0;
			clean(x);
		}
		else if(!ch[rt][1]){
			rt=ch[rt][0];
			fa[rt]=0;
			clean(x);
		}
		else{
			pre();
			fa[ch[x][1]]=rt;
			ch[rt][1]=ch[x][1];
			clean(x),pushup(rt);
		}
	}

或者还有一种方式,我们把 x x x 的前驱旋转到根节点,再把 x x x 的后继旋转到根节点的右子树上,这样根节点的右子树的左儿子即为目标节点,直接断开联系即为删除。
参考代码:

void del(int x){
	int l=kth(x-1),r=kth(r+1);
	splay(l,0),splay(r,l);
	fa[ch[r][0]]=0,ch[r][0]=0;
	pushup(r);
	pushup(l);
}

查询前驱/后继

这个可以先将这个节点插入,此时它在根节点,那么前驱就是它左子树中最右的点,后继就是它右子树中最左的点。
查询完我们在删除这个点即可。
参考代码:

	int pre(){
		int z=ch[rt][0];
		while(ch[z][1])
			z=ch[z][1];
		splay(z,0);
		return z;
	}
	int nxt(){
		int z=ch[rt][1];
		while(ch[z][0])
			z=ch[z][0];
		splay(z,0);
		return z;
	}

模板

综合上述操作,我们即可A掉洛谷模版题。
P3369 【模板】普通平衡树

题目概述:
在这里插入图片描述
参考代码:

struct Tr_splay{
	int fa[N],ch[N][2],sz[N],val[N],cnt[N];
	void pushup(int x){
		sz[x]=sz[ch[x][0]]+sz[ch[x][1]]+cnt[x];
	}
	void clean(int x){
		fa[x]=sz[x]=cnt[x]=val[x]=ch[x][0]=ch[x][1]=0;
	}
	bool son_(int x){
		return x==ch[fa[x]][1];
	}
	void rotate(int x){
		int y=fa[x],z=fa[y],id=son_(x);
		ch[y][id]=ch[x][id^1];
		if(ch[x][id^1])
			fa[ch[x][id^1]]=y;
		ch[x][id^1]=y;
		fa[y]=x;
		fa[x]=z;
		if(z)
			ch[z][y==ch[z][1]]=x;
		pushup(y);
		pushup(x);
	}
	void splay(int x,int y){
		while(fa[x]!=y){
			if(fa[fa[x]]!=y){
				if(son_(fa[x])==son_(x))
					rotate(fa[x]);
				else
					rotate(x);
			}
			rotate(x);
		}
		if(!y)
			rt=x;
	}
	int pre(){
		int z=ch[rt][0];
		while(ch[z][1])
			z=ch[z][1];
		splay(z,0);
		return z;
	}
	int nxt(){
		int z=ch[rt][1];
		while(ch[z][0])
			z=ch[z][0];
		splay(z,0);
		return z;
	}
	void add(int k){
		if(!rt){
			rt=++idx;
			cnt[rt]++,val[rt]=k;
			pushup(rt);
			return ;
		}
		int x=rt,y=0;
		while(1){
			if(val[x]==k){
				cnt[x]++;
				pushup(x),pushup(y);
				splay(x,0);
				break;
			}
			y=x;
			x=ch[x][val[x]<k];
			if(!x){
				cnt[++idx]++,val[idx]=k;
				fa[idx]=y;
				ch[y][val[y]<k]=idx;
				pushup(idx);
				pushup(y);
				splay(idx,0);
				break;
			}
		}
	}
	int x_rank(int k){
		int rk=0,x=rt;
		while(1){
			if(k<val[x])
				x=ch[x][0];
			else{
				rk+=sz[ch[x][0]];
				if(!x)
					return rk+1;
				if(k==val[x]){
					splay(x,0);
					return rk+1;
				}
				rk+=cnt[x];
				x=ch[x][1];
			}
		}
	}
	int kth(int k){
		int x=rt;
		while(1){
			if(ch[x][0]&&k<=sz[ch[x][0]])
				x=ch[x][0];
			else{
				k-=sz[ch[x][0]];
				if(k<=cnt[x]){
					splay(x,0);
					return val[x];
				}
				k-=cnt[x];
				x=ch[x][1];
			}
		}
	}
	void del(int k){
		x_rank(k);
		int x=rt,y=0;
		if(cnt[rt]>1)
			cnt[rt]--,pushup(rt);
		else if(!ch[rt][0]&&!ch[rt][1])
			clean(rt),rt=0;
		else if(!ch[rt][0]){
			rt=ch[rt][1];
			fa[rt]=0;
			clean(x);
		}
		else if(!ch[rt][1]){
			rt=ch[rt][0];
			fa[rt]=0;
			clean(x);
		}
		else{
			pre();
			fa[ch[x][1]]=rt;
			ch[rt][1]=ch[x][1];
			clean(x),pushup(rt);
		}
	}
}tree;
signed main(){
	IOS;
	cin>>m;
	while(m--){
		int x,y;
		cin>>x>>y;
		if(x==1)tree.add(y);
		if(x==2)tree.del(y);
		if(x==3)tree.add(y),cout<<tree.x_rank(y)<<"\n",tree.del(y);
		if(x==4)cout<<tree.kth(y)<<"\n";
		if(x==5)tree.add(y),cout<<tree.val[tree.pre()]<<"\n",tree.del(y);
		if(x==6)tree.add(y),cout<<tree.val[tree.nxt()]<<"\n",tree.del(y);
	}
	return 0;
}

Splay时间复杂度分析

这个蒟蒻不会,但可以参考 OI-WIKI的证明:
证明

进阶操作

截取区间

Splay还可应用到序列操作中,具体而言,如果我们需要对区间 [ l , r ] [l,r] [l,r]进行操作,我们只需要先将 l − 1 l-1 l1 弄到根节点,再把 r + 1 r+1 r+1 弄到根节点的右儿子上,那么它的左子树就是区间 [ l , r ] [l,r] [l,r]了。
参考代码:

	int split(int l,int r){
		l=kth(l-1),r=kth(r+1);
		splay(l,0);
		splay(r,l);
		return ch[r][0];
	}
	//返回区间[l,r]对应的子树的根节点

区间加,区间赋值,区间查询,区间最值

这个类似线段树,我们相应的维护标记,并写好pushdown即可。
区间加参考:

void pushadd(int x,int k){
	val[x]+=k;
	sum[x]+=k*sz[x];
	add[x]+=k;
}
void modify1(int l,int r,int k){
	int _=split(l,r);
	pushadd(_,0,k);
	pushup(r);
	pushup(l);
}

区间赋值参考:

void pushcov(int x,int k){
	val[x]=k;
	sum[x]=sz[x]*k;
	add[x]=0;
	cov[x]=1;
}
void modify(int l,int r,int k){
	int _=split(l,r);
	pushcov(_,k);
	pushup(r);
	pushup(l);
}

区间查询参考:

void ask_sum(int l,int r){
	int _=split(l,r);
	cout<<sum[_]<<"\n";
}

区间翻转

这个呢我们还是搞一个懒标记然后下传,注意各个标记之间的先后顺序。
参考代码:

	void change(int x){
		swap(ch[x][0],ch[x][1]);
		lazy[x]^=1;
	}
	void reverse(int l,int r){
		l=kth(l),r=kth(r+2);
		splay(l,0);
		splay(r,l);
		change(ch[ch[l][1]][0]);
	}

原序列整体插入

有时候题目会直接给我们一个初始序列,一个个插入过于麻烦,于是我们可以类似线段树直接建树。
参考代码:

	int create(int k){
		int x=top?rb[top--]:++ID;
		ch[x][0]=ch[x][1]=fa[x]=rev[x]=cov[x]=0;
		sz[x]=1;
		val[x]=mx[x]=sum[x]=k;
		lx[x]=rx[x]=max(0ll,k);
		return x;
	}
	一些毒瘤题卡空间,这样回收可以节省空间。
	int build(int l,int r,int *a){
		if(l>r)
			return 0;
		if(l==r)
			return create(a[l]);
		int mid=(l+r)>>1,x=create(a[mid]);
		ch[x][0]=build(l,mid-1,a);
		ch[x][1]=build(mid+1,r,a);
		fa[ch[x][0]]=fa[ch[x][1]]=x;
		pushup(x);
		return x;
	}
rt=build(1,n,a);

指定位置插入

这个可以参考查询排名为x的操作。
能看到这里说明你已经是大佬了,看着代码画画图即可理解吧。

	void add(int pos,int k){
		kth(pos);
		pushdown(rt);
		fa[ch[rt][0]]=++ID,ch[ID][0]=ch[rt][0];
		ch[rt][0]=ID,fa[ID]=rt;
		sz[ID]=1;
		val[ID]=sum[ID]=k;
		pushup(ID);
		pushup(rt);
	}

整体插入末尾

这个也比较抽象,类似于建一棵新的splay,然后合并。

	void insert(int pos,int len,int *a){
		int _=build(1,len,a);
		int y=kth(pos),x=kth(pos+1);
		splay(y,0);
		splay(x,y);
		ch[x][0]=_,fa[_]=x;
		pushup(x);
		pushup(y);
	}

区间最大子段和

参考线段树,我们维护3个标记:
lx:从左起的最大子段和
mx:整个区间的最大子段和
rx:从右起的最大子段和
参考代码:(由于同时维护区间赋值和区间翻转,代码比较抽象)

	void pushup(int x){
		sz[x]=sz[ch[x][0]]+sz[ch[x][1]]+1;
		sum[x]=sum[ch[x][0]]+sum[ch[x][1]]+val[x];
		lx[x]=max(lx[ch[x][0]],sum[ch[x][0]]+val[x]+lx[ch[x][1]]);
		rx[x]=max(rx[ch[x][1]],sum[ch[x][1]]+val[x]+rx[ch[x][0]]);
		mx[x]=max(max(mx[ch[x][0]],mx[ch[x][1]]),rx[ch[x][0]]+val[x]+lx[ch[x][1]]);
	}
	void pushdown(int x){
		if(cov[x]){
			if(ch[x][0])val[ch[x][0]]=val[x],cov[ch[x][0]]=1,sum[ch[x][0]]=val[x]*sz[ch[x][0]];
			if(ch[x][1])val[ch[x][1]]=val[x],cov[ch[x][1]]=1,sum[ch[x][1]]=val[x]*sz[ch[x][1]];
			if(val[x]>0){
				if(ch[x][0])lx[ch[x][0]]=rx[ch[x][0]]=mx[ch[x][0]]=sum[ch[x][0]];
				if(ch[x][1])lx[ch[x][1]]=rx[ch[x][1]]=mx[ch[x][1]]=sum[ch[x][1]];
			}
			else{
				if(ch[x][0])lx[ch[x][0]]=rx[ch[x][0]]=0,mx[ch[x][0]]=val[x];
				if(ch[x][1])lx[ch[x][1]]=rx[ch[x][1]]=0,mx[ch[x][1]]=val[x];
			}
			cov[x]=0;
		}
		if(rev[x]){
			if(ch[x][0])
				rev[ch[x][0]]^=1,swap(ch[ch[x][0]][0],ch[ch[x][0]][1]),swap(lx[ch[x][0]],rx[ch[x][0]]);
			if(ch[x][1])
				rev[ch[x][1]]^=1,swap(ch[ch[x][1]][0],ch[ch[x][1]][1]),swap(lx[ch[x][1]],rx[ch[x][1]]);
			rev[x]=0;
		}
	}
	void ask_max_sum(){
		cout<<mx[rt]<<"\n";
	}

一些好题

P2042
P4008
P6707

参考文献

  1. OI-WIKI
  2. 伸展树的基本操作和应用——杨思雨
  3. 各位大佬的博客和题解

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

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

相关文章

【windows|014】TCP协议详解

&#x1f341;博主简介&#xff1a; &#x1f3c5;云计算领域优质创作者 &#x1f3c5;2022年CSDN新星计划python赛道第一名 &#x1f3c5;2022年CSDN原力计划优质作者 ​ &#x1f3c5;阿里云ACE认证高级工程师 ​ &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社…

LLM用于时序预测真的不行,连推理能力都没用到

语言模型真的能用于时序预测吗&#xff1f;根据贝特里奇头条定律&#xff08;任何以问号结尾的新闻标题&#xff0c;都能够用「不」来回答&#xff09;&#xff0c;答案应该是否定的。事实似乎也果然如此&#xff1a;强大如斯的 LLM 并不能很好地处理时序数据。 时序&#xff0…

Prometheus监控主机进程

前言 客户端安装及配置 Premetheus服务端配置 模板导入 grafana效果图 前言 此场景主要是利用process-export监控主机的进程存活、资源占用率&#xff0c;防止进程挂掉导致服务崩溃 gitlab地址&#xff1a;GitHub - ncabatoff/process-exporter: Prometheus exporter that…

开发体育直播平台:高并发问题解决手段及架构设计思路

在追求极致观赛体验的体育直播领域&#xff0c;高并发处理能力成为了衡量系统性能与稳定性的关键标尺。东莞梦幻网络科技技术团队&#xff0c;凭借其在互联网领域的深厚积累与前瞻视野&#xff0c;成功打造了一套高效、稳定的体育赛事直播系统&#xff0c;有效解决了高并发带来…

【Python】sklearn教程

1. sklearn库介绍 sklearn是 Python 中一个非常重要的机器学习库&#xff0c;全称为scikit-learn。它是基于Python语言的机器学习工具&#xff0c;提供了一系列简单高效的机器学习算法。sklearn库通常与NumPy和SciPy库一起使用&#xff0c;用于数据预处理、特征选择、模型训练…

Centos 使用nfs配置共享目录使docker集群所有容器日志统一主机访问

Centos 使用nfs配置共享目录&#xff0c;使docker集群所有容器日志统一存放在主机一个共享目录下&#xff0c;供开发人员访问查看 准备两台或以上Centos服务器 192.168.0.1 nfs服务器 192.168.0.2 nfs客户端 以root用户登录192.168.0.1服务器&#xff0c;执行以下操作 注意先…

【JavaScript】解决 JavaScript 语言报错:Uncaught SyntaxError: Unexpected identifier

文章目录 一、背景介绍常见场景 二、报错信息解析三、常见原因分析1. 缺少必要的标点符号2. 使用了不正确的标识符3. 关键词拼写错误4. 变量名与保留字冲突 四、解决方案与预防措施1. 检查和添加必要的标点符号2. 使用正确的标识符3. 检查关键词拼写4. 避免使用保留字作为变量名…

ReentrantLock的源码实现和原理介绍

目录 一、概述 二、ReentrantLock的整体结构 三、ReentrantLock 和Synchronized相比 四、ReentrantLock 公平锁和非公平锁实现 4.1 ReentrantLock 源码解读 4.1.1 ReentrantLock 类源码解读 4.1.1.1 Lock接口 4.1.1.2 Sync抽象类 4.1.1.3 NonfairSync()和FairSync() 4…

《0基础》学习Python——第十讲

小知识点补充 一、json大字符串 JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;它以易于阅读和编写的方式为基础&#xff0c;同时也易于解析和生成。因为其简洁性和可读性&#xff0c;JSON已成为数据交换的首选格式。 大字符…

超大模型加载转换Trick

在深度学习领域&#xff0c;大模型的训练和推理通常需要消耗大量的计算和内存。如何高效地加载和使用大模型是一个相当关键的问题。在这篇博客中&#xff0c;我将分享一些关于更快加载大模型和减少内存的技巧。 1.问题分析 假设现在我们有一个236B 超大模型的原始权重的 check…

jmeter-beanshell学习9-放弃beanshell

写这篇时候道心不稳了&#xff0c;前面写了好几篇benashell元件&#xff0c;突然发现应该放弃。想回去改前面的文章&#xff0c;看了看无从下手&#xff0c;反正已经这样了&#xff0c;我淋了雨&#xff0c;那就希望别人也没有伞吧&#xff0c;哈哈哈哈&#xff0c;放在第九篇送…

DHCP原理及配置

目录 一、DHCP原理 DHCP介绍 DHCP工作原理 DHCP分配方式 工作原理 DHCP重新登录 DHCP优点 二、DHCP配置 一、DHCP原理 1 DHCP介绍 大家都知道&#xff0c;现在出门很多地方基本上都有WIFI&#xff0c;那么有没有想过这样一个问题&#xff0c;平时在家里都是“固定”的…

互联网十万个为什么之什么是专有网络VPC?

专有网络VPC有什么优势&#xff1f; 专有网络VPC具有安全可靠、灵活可控、简单易用的特性和较强的可扩展性。 安全可靠 每个VPC都有一个独立的隧道号&#xff0c;一个隧道号对应着一个虚拟化网络。VPC之间通过隧道号进行隔离&#xff1a; 由于VPC内部存在交换机和路由器&#…

PyTorch人脸识别

新书速览|PyTorch深度学习与企业级项目实战-CSDN博客 一套基本的人脸识别系统主要包含三部分&#xff1a;检测器、识别器和分类器&#xff0c;流程架构如图11-3所示&#xff1a; 图11-5 检测器负责检测图片中的人脸&#xff0c;再将检测出来的人脸感兴趣区域&#xff08;Reg…

如何在单片机外部Flash存储器上部署高效文件系统:从原理到实现

目录 1.Littlefs文件系统 1.1文件系统简介 2 Littlefs文件系统移植到单片机上 2.1 添加源代码 2.2 编辑接口函数 2.3 测试代码 1.Littlefs文件系统 1.1文件系统简介 littlefs文件系统源码下载地址&#xff1a;littlefs-project/littlefs: A little fail-safe filesystem…

Unity Shader学习笔记

Shader类型 类型详情Standard Surface Shader标准表面着色器&#xff0c;基于物理的着色系统&#xff0c;用于模拟各种材质效果&#xff0c;如石头、木材、玻璃、塑料和金属等。Unlit Shader最简单的着色器&#xff0c;不包含光照但包含雾效&#xff0c;只由最基础的Vertex Sh…

Pytorch使用Dataset加载数据

1、前言&#xff1a; 在阅读之前&#xff0c;需要配置好对应pytorch版本。 对于一般学习&#xff0c;使用cpu版本的即可。参考教程点我 导入pytorch包&#xff0c;使用如下命令即可。 import torch # 注意虽然叫pytorch&#xff0c;但是在引用时是引用torch2、神经网络获取…

【C++】—— 初识C++

【C】—— 初识C 一、什么是 C二、C 的发展历史三、C 版本更新四、C 的重要性五、C 在工作领域中的运用六、C 书籍推荐&#xff1a; 一、什么是 C C语言 是结构化和模块化的语言&#xff0c;适合处理较小规模的程序。对于复杂的问题&#xff0c;规模较大的程序&#xff0c;需要…

六、STM32F4+标准库+LWIP2.1.2移植+无操作系统

最快最简单的移植LWIP协议栈&#xff0c;可改可不改的东西统一不修改。后期学会了有能力了再回过头来修改&#xff0c;操作复杂理论复杂&#xff0c;同时讲解对新手不是很友好&#xff0c;故此此文档只讲操作无任何理论讲解。 零、所需文件及环境 1、第四章建立好的串…

51单片机11(蜂鸣器硬件设计和软件设计)

一、蜂鸣器硬件设计 1、 2、上面两张图&#xff0c;是针对不同产品的电路图。像左边这一块&#xff0c;是我们的A2&#xff0c;A3&#xff0c;A4的一个产品对应的一个封闭器的硬件电路。而右边的这一块是对应的A5到A7的一个硬件电路。因为A5到A7的一个产品&#xff0c;它的各…