高级数据结构:线段树入门(c++实现 + 详解)

news2024/9/20 9:22:17

文章目录

  • 线段树概念
    • 区间最值问题
      • 辅助函数
      • 创建线段树
      • 查询
      • 更新
    • 懒惰修改(查询)问题
      • tag标记设计
      • 改进的更新函数
  • 关于本节的全部源代码

线段树概念

线段树是一种高级数据结构,与树状数组一样,被用来处理区间查询,修改问题,并且线段树的最大优点是对动态数据的处理十分高效。

关于树状数组,可以看我这篇博文的介绍:
高级数据结构:树状数组详解

来看看线段树能处理的问题:

  • 求区间的最值。给你一个区间,让你查询区间的最大值与最小值。如果用普通的数组,加上m次询问,则时间复杂度将会达到O(mn)阶,是非常低效的。
  • 区间和问题,查询,修改区间的元素,求和等等。使用普通数组对指定的区间求和,加之m次询问,则时间复杂度也会达到O(mn),也可以使用前缀和求区间和,但是前缀和虽然高效,但是远没有线段树灵活,线段树能够处理的问题是非常多的。
  • 线段树对于以上两种问题求解都具有O(nlogm)的时间复杂度,是非常高效的。

线段树是具有以下形态的二叉树,其中树上的每个节点都是一个线段区间

看图可以发现线段树的几个特征:

  1. 这颗二叉树是采用分治法来划分区间,并且构建子树的,左右子树各一半。

  2. 这颗二叉树的每个节点都是一个线段区间非叶子节点的线段区间是一段不相等的区间,叶子节点的线段区间的只包含一个元素。

  3. 这颗二叉树是一颗完全二叉树(不是满二叉树),第k层有2^(k-1)个节点。
    在这里插入图片描述

线段树是一颗完全二叉树,这便是线段树做区间问题高效的原因,做查询修改问题只需要访问二叉树的指定节点,而访问节点的复杂度是O(logn)


区间最值问题

我们用区间 [1,4,5,8,6,2,3,9,10,7] 为例,来表示一颗用于查询最小值的线段树

如图所示:

每个节点都表示一个区间,同时每一个区间的前面都记录了一个最小值
在这里插入图片描述
我们对一颗线段树提供几种操作:

  1. 辅助函数
  2. 创建线段树:build
  3. 查询线段树:query
  4. 更新线段树:update

辅助函数

在创建线段树时,我们首先要明白树的创建可以使用链式结构也可以使用顺序结构,那么我们该如何选择呢? 可以注意到我们的线段树会经常用到查询的操作,即经常在每个节点之间进行查询,那么我们就可以通过顺序结构来更快的进行查询,同时,我们还注意到对于普通的二叉树结构满足一个重要的性质:已知当前节点编号为i,则其父节点编号为 i/2,其左右孩子节点编号分别为 2*i 和 2*i+1

所以我们的线段树准备工作如下:

  1. 使用顺序结构存储数据(数组形式)
  2. 提供访问左子树与右子树的函数(我们会经常递归,利用二叉树的编号i 的性质)
  3. 向上递归时我们需要得到孩子节点的最小值来赋值给根节点一个值,我们也可以创建一个函数来完成这个功能,
const int N=100005;
int tree[N*4];		//存储线段树的数据,注意要开四倍区间,防止越界,空间浪费对于线段树是不可避免的。
inline ls(int i)
{
	return i<<1;	//访问左孩子: 2*i
}
inline rs(int i)
{
	return i<<1|1;	//访问右孩子:2*i+1
}
void push_up(int i)
{
	tree[i]=min(tree[(ls(i)],tree[rs(i)]);	//给子树的根节点值等于孩子节点的最小值
}

创建线段树

递归创建这样一颗二叉树应该是很简单的,只需要递归到根节点然后向上回溯赋值父节点即可,代码如下,我们使用build函数来创建一颗线段树。

假设我们有一个输入数组nums,保存原始的数据

void build(int i,int pl,int pr)
{
	if (pl==pr)
	{	
		tree[i]=nums[pl];
		return;
	}
	int mid=(pl+pr)>>1;	//取中间值,分治
	build(ls(i),pl,mid);	//递归左孩子
	build(rs(i),mid+1,pr);	//递归右孩子
	push_up(i);			//自底向上,给子树根节点赋值
	
}

查询

我们进入查询操作:

我们很容易想到,如果我们要查询 [1,3] 的最小值,则可以直接递归找到此节点,此节点记录的最小值为1,因此得到答案为1,查询其他节点也是类似。

但是如果我们要找的区间覆盖多个子区间该如何查询呢?

例如,我们需要查询 [ 4,9] 的最小值,但是我们发现,线段树中并没有 表示[4,9]这个区间的节点,这时我们就需要用到线段树的第一个重要性质: 分治法

我们把这个区间通过递归分治成三个子区间,分别为 [4,5] [6,8] [9,9],我们的线段树中存在这三个子区间的节点,因此返回这三个区间记录的值,然后再取一个最小值,这就是我们所要的答案,即返回这三个区间的最小值

如何通过递归来实现到达每个节点呢,并且得到判断这个到达的这个区间节点是不是我们所需要的区间的子区间,如果是则直接返回此节点记录的最小值,如果不是,则根据我们给出的查询区间与节点区间做比较,然后判断往左子树还是往右子树递归。

关于查询:

  1. 我们所要查询的区间规定为 [4,9] == [L,R] ,所以在整个查询函数中, L,R始终为4,9(这一点非常重要)
  2. mid表示树的某个节点的 (pl+pr)/2,是改变的。
  3. 何时递归到左孩子? 当L 小于等于mid时,分治左区间,即递归以 pl 和 mid 作为子区间继续查询,递归到左孩子。
  4. 何时递归到右孩子? 当 R大于mid时,分治右区间,即递归 mid+1 和 pr 作为子区间继续查询,递归到右孩子。
  5. 何时结束递归? 当pl和pr被L和R完全覆盖的时候,此时对于L和R的最小值的查询就等于pl和pr的区间节点的值,如子图:注意完全覆盖指的是: L<=pl && pr<=R,注意不能是两个等于号,例如查询 [3,4],虽然最后会进入 [3,3]这个区间,但是L和R始终 是3和4,所以只要是pl和pr被覆盖了,所以就得到了[3,3]这个区间的最值。(注意,请认真理解这个过程)
    在这里插入图片描述

在这里插入图片描述
query查询代码实现:

int query(int i,int pl,int pr,int L,int R)
{
 	int res=-inf;
	if (L<=pl && pr<=R)
	{
		//注意《线段区间》 要完全覆盖于L,R查询区间里,此时则可以返回这个节点值,至于为什么不是双==号,看上面(5)的解释
		return tree[i];
	}
	int mid=(pl+pr)>>1;
	if (L<=mid) res=max(res,query(ls(i),pl,mid,L,R));
	if (R>mid) res=max(res,query(rs(i),mid+1,pr,L,R));
	return res;

query函数的使用:n表示原始数组的大小,对于 [1,n]的线段是从根节点1开始,查询[l,r]的最小值

  • 查询模板:query(1,1,n,l,r)

更新

关于此题使用的是简单形式的update函数,只能用于增加一个节点或者修改一个节点
在此例中我们只需求得区间最小值,无需修改区间值,因此我们可以不使用更新函数。

update函数在之后讲解区间和问题在来详细说明,update函数实际上在修改区间与查询区间的问题上非常有用,在一会我们再详细解释。

简单形式的update:

void update(int i, int pl, int pr, int L, int R, int d)
{
	if (L <= pl && pr <= R)
	{
		//找到了被完全覆盖的子区间,修改线段节点的值
		tree[i] = d;
		return;
	}
	//同build构建线段树的原理时候
	int mid = (pl + pr) >> 1;
	if (L <= mid) update(ls(i), pl, mid, L, R, d);
	if (R > mid) update(rs(i), mid + 1, pr, L, R, d);
	push_up(i);
}

update的使用

  • 更新区间最值:update(1,1,n,l,r,6)
  • 添加新元素到末尾: update(1,1,n,cnt,cnt,6),使用cnt记录元素的个数,cnt=n+1…则两个区间表示同一个值,意味着增加一个新的叶子节点

懒惰修改(查询)问题

前面我们的线段树只适用于区间的求极值问题,给你一个区间,求出这个区间的极大值或极小值,但是如果需要做整个区间的修改问题该怎么办呢 ?

这就需要用到我们前面的update函数,前面的update函数只能用于修改一个节点(因为如果修改一个区间的话,上面的update函数会出错),因此如果要修改整个区间的值,每个节点加上d值,如果使用我们最简单的update函数的话,修改m个点只能是一个一个节点最简单的修改,则需要O(mnlongn)这显然是低效的,因此我们使用懒惰标记技术1,这项技术使得对整个区间的修改m次也能降低到O(mlogn)


线段树的节点tree[i]记录了这个区间的值,可以再定义一个tag[i]用来统一记录i这个区间的修改,而不是一个一个节点的修改,这个办法就叫做懒惰标记

当修改的是一个线段区间时,就只对这个线段区间做整体上的修改,其内部的每个孩子节点都不做修改,只有当线段区间的一致性被破坏时,才把变化值传递给其孩子节点。

修改区间的update函数的实现如图所示:

  • 蓝色代表首先递归到的线段节点
  • 红色代表由蓝色节点的修改引起的push_up修改
  • 每个节点的绿色数字代表节点编号,一个数字表示节点的区间和,[x,x]表示这个区间
    在这里插入图片描述

对区间[4,9]的每个值加上3的操作详解:

  1. 首先左子树递归到 [4,5]这个区间(如果你还不知道怎么递归,则看上面的区间最大值的线段树的解释),这个线段区间编号为5,表示tag[5]=3,由于其有两个孩子节点,所以更新tree[5]=14(原始数据)+2*3=20(仔细看,能看懂)
  2. 左子树递归返回,修改线段区间为编号2的节点,更新其值为两个孩子的和,tree[2]=tree[4]+tree[5]=10+20=30
  3. 返回,进入右子树递归,到达 6节点[6,8]区间,标记tag[6]=3,更新tree[6]的值。
  4. 返回,进入右子树的左子树,到达 14节点[9,9]区间,标记tag[14]=3,更新tree[14]返回,更新tree[7]=tree[14]+tree[15]=20,再次返回,更新tree[3]=tree[6]+tree[7]=43,最后递归返回更新tree[1]

我们修改区间的过程就是这样的,但是如果出现了两次更新同一个区间该怎么办?? 例如第一次更新[4,9],第二次更新[5,8],但是他们都会影响到编号为5的节点 [4,5],第一次修改时标记tag[5],然后第二次修改也会进行到11节点 [5,5]的修改,进而破坏了tag[5]的标记,此时原tag[5]记录的统一区间修改就只能往它的孩子节点传递,然后把tag[5]清零


总结的说:对于多个重复区间的修改,我们使用一个push_down函数来解决区间修改的冲突问题。

  • 首先检查p节点的tag[p]看看有没有标记,如果有则说明是之前某次修改过的区间,则需要将tag[p]下移到孩子节点,然后把原始tag[p]清零

tag标记设计

int tag[N << 2];
void addtag(ll i, ll pl, ll pr, ll d)
{
	tag[i] += d;	//此节点加上tag
	tree[i] += (pr - pl + 1) * d;	//更新tree
}

push_down 函数

inline void push_down(int i,int pl,int pr)
{
	if (tag[i])
	{	//如果被标记过
		int mid=(pl+pr)>>1;
		//下移标记
		addtag(ls(i),pl,mid);//左孩子
		addtag(rs(i),mid+1,pr);//右孩子
		tag[i]=0;//原始标记清零
	}
}

改进的更新函数

void update(int i, int pl, int pr, int L, int R, int d)
{
	if (L <= pl && pr <= R)
	{
		addtag(i, pl, pr, d);	//给节点打上标记,等到下一次区间修改到i这个节点时会用到
		return;
	}
	push_down(i, pl, pr);	//如果不能覆盖,递归到每一个节点时,检查是否有没有被标记过,把tag传递给子树
	int mid = (pl + pr) >> 1;
	if (L <= mid) update(ls(i), pl, mid, L, R, d);
	if (R > mid) update(rs(i), mid + 1, pr, L, R, d);
	push_up(i);
}

查询函数:注意查询的时候也会使用push_down

int query(int i, int pl, int pr, int L, int R)
{
	int res = 0;
	if (L <= pl && pr <= R)
	{
		return tree[i];	//完全覆盖,直接返回
	}
	push_down(i, pl, pr);	//不能覆盖,递归子树
	int mid = (pl + pr) >> 1;
	if (L <= mid) res +=  query(ls(i), pl, mid, L, R);			//区间求和
	if (R > mid) res +=  query(rs(i), mid + 1, pr, L, R);		//区间求和
	return res;
}

关于本节的全部源代码

//线段树:区间最大值
namespace test45
{
	const int N = 5e+5;
	int n, q;
	const int inf = 0x3f3f3f3f;
	int nums[N], tree[N];
	inline int ls(int i)//左孩子
	{
		return i << 1;
	}
	inline int rs(int i)//右孩子
	{
		return i << 1 | 1;
	}
	inline void push_up(int i)
	{
		tree[i] = max(tree[ls(i)], tree[rs(i)]);
	}
	void build(int i, int pl, int pr)
	{
		if (pl == pr)
		{
			tree[i] = nums[pl];
			return;
		}
		int mid = (pl + pr) >> 1;
		build(ls(i), pl, mid);
		build(rs(i), mid + 1, pr);
		push_up(i);	//自底向上传递区间值
	}
	void update(int i, int pl, int pr, int L, int R, int d)
	{
		if (L <= pl && pr <= R)
		{
			tree[i] = d;
			return;
		}
		int mid = (pl + pr) >> 1;
		if (L <= mid) update(ls(i), pl, mid, L, R, d);
		if (R > mid) update(rs(i), mid + 1, pr, L, R, d);
		push_up(i);
	}
	int query(int i, int pl, int pr, int L, int R)
	{
		int res = -inf;
		if (L <= pl && pr <= R)
		{
			return tree[i];
		}
		int mid = (pl + pr) >> 1;
		if (L <= mid) res = max(res, query(ls(i), pl, mid, L, R));
		if (R > mid) res=max(res,query(rs(i), mid + 1, pr, L, R));
		return res;
	}
	void test()
	{
		cin >> n >> q;
		int cnt = 0;
		for (int i = 1; i <= n; i++)
		{
			cin >> nums[i];
		}
		build(1, 1, n);
		for (int i = 1; i <= q; i++)
		{
			int l, r;
			cin >> l >> r;
			cnt = n + 1;
			update(1, 1, n + 1, n + 1, n + 1,99);
			//cout << query(1, 1, n, l, r) << endl;
		}
	}
}

//线段树:懒惰标记,区间修改查询
namespace test46
{
	const int N = 5e+5;
	int n, q;
	const int inf = 0x3f3f3f3f;
	int nums[N], tree[N << 2];
	int tag[N << 2];
	void addtag(ll i, ll pl, ll pr, ll d)
	{
		tag[i] += d;	//此节点加上tag
		tree[i] += (pr - pl + 1) * d;	//更新tree
	}
	inline int ls(int i)//左孩子
	{
		return i << 1;
	}
	inline int rs(int i)//右孩子
	{
		return i << 1 | 1;
	}
	inline void push_up(int i)
	{
		tree[i] = tree[ls(i)]+ tree[rs(i)];	//求区间和
	}
	inline void push_down(int i,int pl,int pr)
	{
		if (tag[i])
		{
			//如果被标记过,下移标记
			int mid = (pl + pr) >> 1;
			addtag(ls(i), pl, mid, tag[i]);
			addtag(rs(i), mid + 1, pr, tag[i]);
			tag[i] = 0;	
		}
	}
	void build(int i, int pl, int pr)
	{
		tag[i] = 0;	//懒惰标记赋值为0
		if (pl == pr)
		{
			tree[i] = nums[pl];	//叶子节点赋值
			return;
		}
		int mid = (pl + pr) >> 1;	//分治
		build(ls(i), pl, mid);
		build(rs(i), mid + 1, pr);
		push_up(i);	//自底向上传递区间值
	}
	void update(int i, int pl, int pr, int L, int R, int d)
	{
		if (L <= pl && pr <= R)
		{
			addtag(i, pl, pr, d);	//给节点打上标记,等到下一次区间修改到i这个节点时会用到
			return;
		}
		push_down(i, pl, pr);	//如果不能覆盖,递归到每一个节点时,检查是否有没有被标记过,把tag传递给子树
		int mid = (pl + pr) >> 1;
		if (L <= mid) update(ls(i), pl, mid, L, R, d);
		if (R > mid) update(rs(i), mid + 1, pr, L, R, d);
		push_up(i);
	}
	int query(int i, int pl, int pr, int L, int R)
	{
		int res = 0;
		if (L <= pl && pr <= R)
		{
			return tree[i];	//完全覆盖,直接返回
		}
		push_down(i, pl, pr);	//不能覆盖,递归子树
		int mid = (pl + pr) >> 1;
		if (L <= mid) res +=  query(ls(i), pl, mid, L, R);		//区间求和
		if (R > mid) res +=  query(rs(i), mid + 1, pr, L, R);		//区间求和
		return res;
	}
	void test()
	{
		cin >> n >> q;
		int cnt = 0;
		for (int i = 1; i <= n; i++)
		{
			cin >> nums[i];
		}
		build(1, 1, n);
		for (int i = 1; i <= q; i++)
		{
			int q, l, r, d;
			cin >> q;
			if (q == 1)
			{
				cin >> l >> r >> d;
				update(1, 1, n, l, r, d);
			}
			else
			{
				cin >> l >> r;
				cout << query(1, 1, n, l, r);
			}
		}
	}
}
int main()
{
	using namespace test46;
	test46::test();
	return 0;
}

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

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

相关文章

DDOS渗透与攻防(一)之拒绝服务攻击概念介绍

DDOS渗透与攻防 前言 DOS(Denial of Service&#xff0c;拒绝服务攻击)&#xff0c;它的原理很简单&#xff0c;就是用我们手里的机器去给服务器发请求&#xff0c;如果我们手头的服务器各方面性能都比服务器的主机的性能好&#xff0c;那么当我们发送大量请求给服务器&#…

Day871.行锁 -MySQL实战

行锁 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于行锁的内容。 MySQL 的 行锁 是在引擎层由各个引擎自己实现的。 但并不是所有的引擎都支持行锁&#xff0c;比如 MyISAM 引擎就不支持行锁。 不支持行锁意味着并发控制只能使用表锁&#xff0c;对于这种引擎的…

普通话学习

一、认识韵母是有四个声调的发错的字和音总结R&#xff1a;日、热、肉L&#xff1a;漏、乐参考&#xff1a;https://zhuanlan.zhihu.com/p/34571271身体&#xff08;shen-1&#xff09;森&#xff08;sen-1&#xff09; 生日&#xff08;sheng-1&#xff09;琴&#xff08;qin-…

k8s对接smb/cifs存储

之前文章提到&#xff0c;k8s官方是支持nfs存储的&#xff0c;那么在windows常见的文件共享协议是否也支持呢。答案是肯定的。不过支持的方式是通过CSI接口进行支持的。官方提供的项目是csi-driver-smb官网&#xff1a;https://github.com/kubernetes-csi/csi-driver-smb安装cu…

《MFC编程》:MFC程序的分类

《MFC编程》&#xff1a;MFC程序的分类《MFC编程》&#xff1a;MFC程序的分类MFC的控制台程序MFC的库程序使用MFC库制作自己的静态库程序使用MFC库制作自己的动态库程序使用MFC库制作自己的拓展库程序MFC的窗口程序单文档视图架构程序多文档视图架构程序对话框架构程序《MFC编程…

Windows server——部署DNS服务(2)

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.配置DNS服务 1.安装步骤教程 &#xff08;1&#xff09;安装必要条件 &a…

【链表】链表内指定区间反转

BM2链表内指定区间反转 描述 将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转&#xff0c;要求时间复杂度 O(n)O(n)&#xff0c;空间复杂度 O(1)O(1)。 例如&#xff1a; 给出的链表为1→2→3→4→5→NULL, m2,n4m2,n4, 返回1→4→3→2→5→NULL. 数据范围&#xf…

初识 Canvas(使用 Canvas 绘制直线图形)

初识 Canvas&#xff08;使用 Canvas 绘制直线图形&#xff09;参考描述CanvasCanvas 与 SVGCanvas 元素替补元素使用检测绘制直线图形直线同时绘制多条直线指定起点首尾相接多彩的直线矩形描边矩形填充矩形结合体清除矩形区域多边形参考 描述 Canvas 你可以使用 JavaScript…

3 梯度下降算法

文章目录问题方法穷举法分治法梯度下降算法梯度梯度下降算法课程代码随机梯度下降算法批量梯度下降&#xff08;mini-batch&#xff09;课程来源&#xff1a; 链接其他觉得别人总结的比自己好&#xff0c;采用之的&#xff1a; 链接以及 BirandaのBlog&#xff01;问题方法 穷…

飞速的网格【Steema】:TeeGrid for .NET 2023.1.23 Crack

TeeGrid for .NET 的数据网格控件为您的 Visual Studio 项目提供了一个非常快速的网格。 快速浏览 功能齐全的数据网格控件 适用于 WinForms、ASP.NET Core MVC 的易于使用的数据网格 TeeGrid for .NET 是一个原生的 c# 网格控件&#xff0c;目前可用于 WinForms 项目和 NET …

优先级队列(堆)

1.优先级队列1.1概念前面介绍过队列&#xff0c;队列是一种先进先出&#xff08;FIFO&#xff09;的数据结构&#xff0c;但是有些情况下&#xff0c;操作的数据可能带有优先级时&#xff0c;可能需要优先级高的元素先入队列&#xff0c;该场景中&#xff0c;使用队列显然不合适…

7、数据类型转换

目录 一、隐式类型转换 二、显示类型转换 类型转换是将一个值从一种类型更改为另一种类型的过程。例如&#xff0c;可以将String类型的数据“457”转换为数值型&#xff0c;也可以将任意类型的数据转换为String类型。 一、隐式类型转换 从低级类型向高级类型的转换&#xf…

一起自学SLAM算法:10.1 RTABMAP算法

连载文章&#xff0c;长期更新&#xff0c;欢迎关注&#xff1a; 同前面介绍过的大多数算法一样&#xff0c;RTABMAP也采用基于优化的方法来求解SLAM问题&#xff0c;系统框架同样遵循前端里程计、后端优化和闭环检测的三段式范式。这里重点讨论RTABMAP两大亮点&#xff0c;一个…

python实现问卷星自动填写(可以跳过智能验证)

最近有一个社会实践需要做问卷调查&#xff0c;突发奇想搞一个自动化脚本&#xff0c;省事省米 1 下载依赖selenium selenium是一款网页爬虫重要的工具。 2 安装chrome驱动 这里需要准备chrome浏览器以及对应的驱动。需要注意的是驱动的版本需要和chrome保持一致。 chrome…

Mybatis框架介绍及使用

文章目录1. 概述1.1什么是框架1.2 Mybatis是什么1.3 Mybatis流程分析2. 参数的使用2.1 保存时获取插入id2.2 #{}与${}的区别2.3 parameterType 的使用2.4 SqlMapConfig.xml 中配置的内容3. 动态SQL3.1 <if>标签3.2 <where>标签3.3 <foreach > 标签3.4 抽取重…

ipv6内网穿透,有ipv6地址外网无法访问

问题描述 光猫已经开启ipv6&#xff0c;并且电脑/服务器已经有ipv6地址&#xff0c;只能通过ipv6局域网访问&#xff0c;外网/手机流量访问设备 原因分析&#xff1a; 光猫没有关闭防火墙路由器入站防护 解决方案&#xff1a; 1.光猫没有关闭防火墙 这里以中国移动的光猫为…

(17)目标检测算法之 YOLOv8 算法改进详细解析

目标检测算法之 YOLOv8 算法改进详细解析 1.YOLO的一些发展历史 YOLOv1&#xff1a;2015年Joseph Redmon和 Ali Farhadi等 人&#xff08;华盛顿大学&#xff09; YOLOv2&#xff1a;2016年Joseph Redmon和**Ali Farhadi等人*&#xff08;华盛顿大学&#xff09;* YOLOv3&am…

【JavaEE】多线程之线程安全(volatile篇),wait和notify

目录 内存可见性问题 volatile关键字 从JMM的角度来看内存可见性 wait和notify wait notify-notifyAll 内存可见性问题 首先运行一段代码&#xff0c;线程t1 用 Mycount.flag 作为标志符&#xff0c;当不为0的时候就跳出循环&#xff0c;线程t2 通过输入来改变 Mycount.f…

springboot 入门

springboot是什么 传统的开发模式下&#xff0c;无论是基于xml或注解&#xff0c;都要做许多配置&#xff0c;如果项目中集成越多的其他框架&#xff0c;配置内容也会越多。为了让开发人员以最少的配置去开发应用&#xff0c;springboot诞生了。springboot的原则是约定大于配置…

VSCode中4个Settings(JSON)的区别与联系

目录 &#x1f525; 前言 1. Preferences: Open Default Settings(JSON) 2. Preferences: Open User Settings 3. Preferences: Open Settings(JSON) 4. Preferences: Open Workspace Settings(JSON) &#x1f525; 总结 &#x1f525; 前言 在VSCode中输入快捷键ctrlsh…