数据结构:红黑树的原理和实现

news2024/11/17 17:37:22

文章目录

  • 红黑树的概念
  • 红黑树的性质
  • 红黑树的模拟实现
    • 红黑树的平衡问题
  • 整体实现和测试

本篇用于进行红黑树的拆解和模拟实现,为之后的map和set的封装奠定基础

红黑树的概念

红黑树也是一种二叉搜索树,但是在每一个节点的内部新增了一个用以表示该节点颜色的值,有黑色和红色两种,通过对任何一条从根到叶子的路径上的各个节点着色方式的限制,红黑树可以保证没有一条路径可以比其他路径长出两倍,因此是平衡的

红黑树的基本模式如下图所示
在这里插入图片描述

红黑树的性质

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

为什么红黑树具有最长路径中节点的个数不超过最短路径个数的2倍?

其实原因在于红黑树的性质,在红黑树中可以存在两个相同黑色节点连在一起,但是绝对不会存在两个连在一起的红色节点,并且每个路径上的黑色节点数量是相同的,基于这两点原因,在红黑树中最长的路径不过是一个红节点穿插一个黑节点…而最短的路径就是所有黑节点是一个接着一个,基于这样的原因就可以保证上面的性质了

红黑树的模拟实现

基本的定义

enum Color
{
	RED,
	BLEAK
};

template<class K, class V>
struct BSTreeNode
{
	BSTreeNode<K, V>* _left;
	BSTreeNode<K, V>* _right;
	BSTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Color _col;

	BSTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(RED)
	{}
};

为什么这里在定义信息的时候,默认值使用的是RED?

由于红黑树的性质可以知道,一条路径中的黑节点的数量是确定的,当插入的是一个红色节点时,最多会影响的是当前路径的信息,但是如果插入的是一个黑色节点,那么势必会引起整个树中所有完整的路径中的异常,会破坏红黑树中的平衡

红黑树的平衡问题

在插入新节点后,红黑树的平衡可能会受到破坏,下面分情况来进行讨论

定义:当前节点为cur,父亲节点为parent,祖父节点为grandparent,叔叔节点为uncle,而红黑树的插入问题重点看叔叔

1. 如果双亲节点是黑色

在这里插入图片描述
最简单的一种情况,不需要做任何处理,只需要插入即可

2. cur为红色,parent为红色,grandfather为黑色,uncle存在并且是红色

在这里插入图片描述
此时,出现了两个红色节点相继出现的情况,这种情况是不被允许的,因此要做出调整:把parent和uncle都改成黑色,同时将grandfather改成红色

此时需要继续进行情况讨论,如果grandfather是根节点,那么就意味着此时调整已经完毕了,不需要再进行调整,因此把根节点置为黑色,而如果grandfather不为根节点,并且上面一个节点还是红色,那么此时又有两个红色节点相继出现了,此时就需要继续进行调整,把grandfather当成cur,然后进行调整即可

在这里插入图片描述
3. cur为红色,parent为红色,grandfather为黑色,uncle不存在或者是黑色

根据uncle的情况来进行分析:

  1. 如果uncle节点不存在,那么就说明cur一定是新插入的节点,这是因为路径下的黑色节点必定要相同,此时又有两种情况,可能插入在parent的左右两边

在这里插入图片描述

  1. 如果uncle节点存在,并且是黑色,那么就意味着cur节点一定是黑的,现在体现为红色是因为cur子树在调整的过程中把cur的节点变成红色了,如果cur是新插入节点,那么红黑树原来就是错的,因为下面的场景不存在
    在这里插入图片描述
    所以一定是这样的情景:

在这里插入图片描述

而此时cur并不是新插入的节点,新插入节点是cur的左右子树中的一个,现在体现为红色是因为下面子树的调整把cur变成红色了,它原来是黑色的

那么此时就要进行旋转了:令grandparent右旋即可完成降高度的效果,再进行变色即可

在这里插入图片描述
因此将上述的过程都综合起来,就可以完成代码的实现了

	bool insert(const pair<K, V>& kv)
	{
		Node* cur = _root;
		Node* parent = nullptr;
		// 根据搜索二叉树的基本逻辑完成
		if (_root == nullptr)
		{
			_root = new Node(kv);
		}
		else
		{
			// 插入数据
			while (cur)
			{
				if (cur->_kv.second > kv.second)
				{
					// 插入元素小于当前节点元素,插入到左边
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_kv.second < kv.second)
				{
					// 插入元素大于当前节点元素,插入到右边
					parent = cur;
					cur = cur->_right;
				}
				else
				{
					return false;
				}
			}
			// 此时parent指向最后一个节点,cur为空
			cur = new Node(kv);
			if (parent->_kv.second > cur->_kv.second)
			{
				// 如果插入节点小于它的父亲,就插入到左边
				parent->_left = cur;
				cur->_parent = parent;
			}
			else if (parent->_kv.second < cur->_kv.second)
			{
				// 如果插入节点大于它的父亲,就插入到右边
				parent->_right = cur;
				cur->_parent = parent;
			}
			else
			{
				return false;
			}
		}

		// 至此,普通二叉搜索树的插入已经完成,该进行红黑树的高度调整了
		// 终止条件是parent为空,或者parent已经是黑色了,就意味着不需要调整了
		// parent是红色,意味着grandparent一定存在
		while (parent && parent->_col == RED)
		{
			// 更变的核心是舅舅,因此要先找到舅舅
			// 整体来说还有两种情况,parent可能是grandparent的左或者右,舅舅就是另外一边
			Node* grandparent = parent->_parent;
			if (parent == grandparent->_left)
			{
				Node* uncle = grandparent->_right;
				// 1. 舅舅存在,并且是红色
				if (uncle && uncle->_col == RED)
				{
					//     g
					//   p   u
					// c
					
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandparent->_col = RED;
				
					// 向上处理
					cur = grandparent;
					parent = cur->_parent;
				}

				// 2. 舅舅不存在
				else
				{
					// 如果cur是左孩子
					if (cur == parent->_left)
					{
						//     g
						//   p
						// c
						
						// 对grandparent进行右旋
						RotateR(grandparent);
						// 变色
						cur->_col = grandparent->_col = RED;
						parent->_col = BLACK;
					}
					// 如果cur是右孩子
					else
					{
						//     g               g
						//  p       -->     c         -->    c
						//    c           p                p   g
						
						// 对parent左旋,对grandparent右旋
						RotateL(parent);
						RotateR(grandparent);
						// 变色
						cur->_col = BLACK;
						parent->_col = grandparent->_col = RED;
					}

					// 更新之后parent和grandparent顺序乱了,而且也不需要继续调整了,直接break
					break;
				}
			}
			
			// parent是grandparent的右孩子,相同的逻辑再进行一次
			else
			{
				Node* uncle = grandparent->_left;
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandparent->_col = RED;

					// 继续往上处理
					cur = grandparent;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_right)
					{
						//   g
						//      p
						//         c
						RotateL(grandparent);
						parent->_col = BLACK;
						grandparent->_col = RED;
					}
					else
					{
						//     g
						//       p 
						//     c

						RotateR(parent);
						RotateL(grandparent);
						cur->_col = BLACK;
						grandparent->_col = RED;
					}

					break;
				}
			}
		}
		// 不管上面怎么变都无所谓,只需要保证根节点是黑的就可以了
		_root->_col = BLACK;

		return true;
	}

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		subR->_left = parent;

		Node* parentParent = parent->_parent;

		parent->_parent = subR;
		if (subRL)
			subRL->_parent = parent;

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

			subR->_parent = parentParent;
		}
	}

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		Node* parentParent = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

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

			subL->_parent = parentParent;
		}
	}

整体实现和测试

enum Color
{
	RED,
	BLACK
};

template<class K, class V>
struct RBTreeNode
{
	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(RED)
	{}

	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Color _col;
};

template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	RBTree()
		:_root(nullptr)
	{}

	bool insert(const pair<K, V>& kv)
	{
		Node* cur = _root;
		Node* parent = nullptr;
		// 根据搜索二叉树的基本逻辑完成
		if (_root == nullptr)
		{
			_root = new Node(kv);
		}
		else
		{
			// 插入数据
			while (cur)
			{
				if (cur->_kv.second > kv.second)
				{
					// 插入元素小于当前节点元素,插入到左边
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_kv.second < kv.second)
				{
					// 插入元素大于当前节点元素,插入到右边
					parent = cur;
					cur = cur->_right;
				}
				else
				{
					return false;
				}
			}
			// 此时parent指向最后一个节点,cur为空
			cur = new Node(kv);
			if (parent->_kv.second > cur->_kv.second)
			{
				// 如果插入节点小于它的父亲,就插入到左边
				parent->_left = cur;
				cur->_parent = parent;
			}
			else if (parent->_kv.second < cur->_kv.second)
			{
				// 如果插入节点大于它的父亲,就插入到右边
				parent->_right = cur;
				cur->_parent = parent;
			}
			else
			{
				return false;
			}
		}

		// 至此,普通二叉搜索树的插入已经完成,该进行红黑树的高度调整了
		// 终止条件是parent为空,或者parent已经是黑色了,就意味着不需要调整了
		// parent是红色,意味着grandparent一定存在
		while (parent && parent->_col == RED)
		{
			// 更变的核心是舅舅,因此要先找到舅舅
			// 整体来说还有两种情况,parent可能是grandparent的左或者右,舅舅就是另外一边
			Node* grandparent = parent->_parent;
			if (parent == grandparent->_left)
			{
				Node* uncle = grandparent->_right;
				// 1. 舅舅存在,并且是红色
				if (uncle && uncle->_col == RED)
				{
					//     g
					//   p   u
					// c
					
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandparent->_col = RED;
				
					// 向上处理
					cur = grandparent;
					parent = cur->_parent;
				}

				// 2. 舅舅不存在
				else
				{
					// 如果cur是左孩子
					if (cur == parent->_left)
					{
						//     g
						//   p
						// c
						
						// 对grandparent进行右旋
						RotateR(grandparent);
						// 变色
						cur->_col = grandparent->_col = RED;
						parent->_col = BLACK;
					}
					// 如果cur是右孩子
					else
					{
						//     g               g
						//  p       -->     c         -->    c
						//    c           p                p   g
						
						// 对parent左旋,对grandparent右旋
						RotateL(parent);
						RotateR(grandparent);
						// 变色
						cur->_col = BLACK;
						parent->_col = grandparent->_col = RED;
					}

					// 更新之后parent和grandparent顺序乱了,而且也不需要继续调整了,直接break
					break;
				}
			}
			
			// parent是grandparent的右孩子,相同的逻辑再进行一次
			else
			{
				Node* uncle = grandparent->_left;
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandparent->_col = RED;

					// 继续往上处理
					cur = grandparent;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_right)
					{
						//   g
						//      p
						//         c
						RotateL(grandparent);
						parent->_col = BLACK;
						grandparent->_col = RED;
					}
					else
					{
						//     g
						//       p 
						//     c

						RotateR(parent);
						RotateL(grandparent);
						cur->_col = BLACK;
						grandparent->_col = RED;
					}

					break;
				}
			}
		}
		// 不管上面怎么变都无所谓,只需要保证根节点是黑的就可以了
		_root->_col = BLACK;

		return true;
	}

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		subR->_left = parent;

		Node* parentParent = parent->_parent;

		parent->_parent = subR;
		if (subRL)
			subRL->_parent = parent;

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

			subR->_parent = parentParent;
		}
	}

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		Node* parentParent = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

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

			subL->_parent = parentParent;
		}
	}

	void inorder()
	{
		_inorder(_root);
		cout << endl;
	}

	bool isbalance()
	{
		return _isbalance(_root);
	}

private:
	bool _check(Node* root, int blacknum, const int RefVal)
	{
		// 判断黑色路径数量是否相等
		if (root == nullptr)
		{
			if (blacknum != RefVal)
			{
				cout << "黑色节点数量不相等" << endl;
				return false;
			}
			return true;
		}

		// 判断是否有连续的红色节点
		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << "有连续的红色节点" << endl;
			return false;
		}

		if (root->_col == BLACK)
		{
			blacknum++;
		}

		return _check(root->_left, blacknum, RefVal)
			&& _check(root->_right, blacknum, RefVal);
	}

	// 判断红黑树是否平衡
	bool _isbalance(Node* root)
	{
		// 如果根节点是红的,说明错了
		if (root->_col == RED)
		{
			cout << "根节点是红色" << endl;
			return false;
		}

		// 统计一下路径中有多少黑色节点
		int RefVal = 0;
		Node* cur = root;
		while (cur)
		{
			if (cur->_col == BLACK)
				RefVal++;
			cur = cur->_left;
		}

		// 判断路径中的黑色节点是否相等
		return _check(root, 0, RefVal);
	}

	void _inorder(Node* root)
	{
		if (root == nullptr)
			return;
		_inorder(root->_left);
		cout << root->_kv.second << " ";
		_inorder(root->_right);
	}

	Node* _root = nullptr;
};
int main()
{
	const int N = 100000;
	vector<int> v;
	v.reserve(N);
	srand(time(0));

	for (size_t i = 0; i < N; i++)
	{
		v.push_back(rand() + i);
	}

	RBTree<int, int> t;
	for (auto e : v)
	{
		cout << "Insert:" << e << "->";
		t.insert(make_pair(e, e));
		cout << t.isbalance() << endl;
	}

	cout << t.isbalance() << endl;

	return 0;
}

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

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

相关文章

计算机毕业设计选题推荐-公共浴池微信小程序/安卓APP-项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

ubuntu操作系统的docker更换存储目录

前言 要将Docker的存储目录更改为/home/docker&#xff0c;你需要进行以下步骤&#xff1a; 目录 前言1、停止Docker服务2、创建新的存储目录3、编辑Docker配置文件4、启动Docker服务5、验证更改 1、停止Docker服务 首先停止Docker守护进程&#xff0c;可以使用以下命令&…

asp.net图书管理系统

asp.net图书管理系统 基本操作图书管理 读者管理 借书 修改资料 修改密码 说明文档 运行前附加数据库.mdf&#xff08;或sql生成数据库&#xff09; 主要技术&#xff1a; 基于C#winform架构和sql server数据库 功能模块&#xff1a; 图书管理 读者管理 借书 修改资料 修改…

【Vue】过滤器Filters

hello&#xff0c;我是小索奇&#xff0c;精心制作的Vue系列持续发放&#xff0c;涵盖大量的经验和示例&#xff0c;如对您有用&#xff0c;可以点赞收藏哈 过滤器 filters过滤器已从Vue 3.0中删除&#xff0c;不再支持了&#xff0c;这里可以作为了解进行学习 vue3要精简代码&…

算法笔记——递归(1)

这里写目录标题 递归的思想序列求最大值翻转字符串斐波那契数列数塔回文字符串上楼汉诺塔棋盘覆盖问题数字螺旋矩阵盒分形 递归的思想 子问题须与原始问题为同样的事&#xff0c;且更为简单。 不能无限制地调用本身&#xff0c;须有个出口&#xff0c;化简为非递归状况处理 序…

SQL学习之增删改查

文章目录 数据库数据类型建表create table插入数据insert into查询数据select from修改数据update set删除数据delete from备份ctas结果插入iis截断表 truncate table修改表结构alter table添加注释 注&#xff1a;本文的SQL语法是基于Oracle数据库操作的&#xff0c;但是基本的…

官宣合作|Space and Time的SQL证明使zkLogin更强大

为了实现Web3的承诺&#xff0c;必须有一种方法能够提供当前Web基础设施的所有功能&#xff0c;同时不会破坏区块链基础设施的零信任模型。能够在无需依赖第三方中介的情况下运行是一种基本哲学&#xff0c;也是区块链技术的优点&#xff0c;即提高安全性和效率的同时降低成本。…

跨境电商源码:多语言支持与扩展的终极解决方案

随着全球电商市场的不断扩大&#xff0c;跨境电商源码的需求日益增长。对于想要拓展国际业务的电商企业来说&#xff0c;多语言支持显得尤为重要。在这方面&#xff0c;我们的跨境电商源码产品具备显著优势&#xff0c;不仅全面支持多语言&#xff0c;还方便扩展多个语言的CSDN…

瑞萨e2studio(29)----SPI速率解析

瑞萨e2studio.29--SPI速率解析 概述视频教学时钟配置解析RA4M2的BRR值时钟速率7.5M下寄存器值3K下寄存器值 概述 在嵌入式系统的设计中&#xff0c;串行外设接口&#xff08;SPI&#xff09;的通信速率是一个关键参数&#xff0c;它直接影响到系统的性能和稳定性。瑞萨电子的…

windows安装composer并更换国内镜像

第一步、官网下载 下载地址 Composer安装https://getcomposer.org/Composer-Setup.exe第二步、双击安装即可 第三步选择 php安装路径并配置path 第四步、 composer -v查看安装是否成功&#xff0c;出现成功界面 第五步、查看镜像地址并更换&#xff08;composer国内可能较慢…

外汇天眼:3家平台牌照失效,远离以下平台!

监管信息早知道&#xff01;外汇天眼将每周定期公布监管牌照状态发生变化的交易商&#xff0c;以供投资者参考&#xff0c;规避投资风险。如果平台天眼评分过高&#xff0c;建议投资者谨慎选择&#xff0c;因为在外汇天眼评分高不代表平台没问题&#xff01; 以下是监管牌照发生…

DefaultListableBeanFactory

DefaultListableBeanFactory 是一个完整的、功能成熟的 IoC 容器&#xff0c;如果你的需求很简单&#xff0c;甚至可以直接使用 DefaultListableBeanFactory&#xff0c;如果你的需求比较复杂&#xff0c;那么通过扩展 DefaultListableBeanFactory 的功能也可以达到&#xff0c…

【教3妹学编程-算法题】逃离火灾

3妹&#xff1a;2哥&#xff0c;今日都立冬了&#xff0c; 可是天气一点都不冷。 2哥 : 立冬了&#xff0c;晚上要不要一起出去吃饺子&#xff1f;&#x1f95f; 3妹&#xff1a;好呀好呀&#xff0c;2哥请吃饺子喽 2哥 : 歪歪&#xff0c;我说的是一起出去吃&#xff0c;没说我…

python用pychart库,实现将经纬度信息在地图上显示

python使用pyecharts对给到的经纬度数据进行位置标注&#xff0c;下面是批量更新。给入数据&#xff0c;将地图生成。实验数据在下面附件。 from pyecharts import options as opts from pyecharts.charts import Geo import osfolder_path F:\\GPS file_names os.listdir(f…

mycat2 读写分离

mycat2 读写分离 mycat2 读写分离1.创建两个主从复制的数据库2.mycat2 读写分离3.mycat2读写分离测试 mycat2 读写分离 1.创建两个主从复制的数据库 参考&#xff1a;mysql主从复制 2.mycat2 读写分离 连接到mycat数据库 1.在mycat中创建数据库mydb1 CREATE DATABASE mydb…

数字化催生低代码开发浪潮,管理系统也能一键生成

近年来&#xff0c;数字经济以无比迅猛的势头崛起&#xff0c;成为引领全球经济变革的重要引擎。在这个数字化趋势日益明显的时代&#xff0c;企业的数字转型已经成为提高竞争力、适应市场需求的迫切需要。 随着科技的飞速发展&#xff0c;数字技术已经渗透到各个领域&#xff…

安防监控EasyCVR视频汇聚平台运维现场无法使用Linux抓包该如何解决?

视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。监控视频平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、云存储、…

安装 Lua 的 HTTP 库

首先&#xff0c;你需要安装 Lua 的 HTTP 库。可以使用 LuaRocks 来安装。以下是安装命令&#xff1a; luarocks install http然后&#xff0c;你可以使用以下代码来爬取网页内容&#xff1a; local http require http-- 设置代理信息 http.set_proxy(jshk.com.cn)-- 网页UR…

社区新零售:改变生活方式的创新商业模式

社区新零售&#xff1a;改变生活方式的创新商业模式 社区新零售&#xff0c;顾名思义&#xff0c;以社区为核心&#xff0c;利用互联网、大数据、人工智能等先进技术&#xff0c;将线上购物和线下体验有机结合&#xff0c;形成一种全新的零售模式。它特别强调地理位置的便利性&…

mysql的主从复制,读写分离

主从复制&#xff1a;主mysql的数据&#xff0c;新增&#xff0c;修改&#xff0c;表里的数据都会同步到从mysql上 主从复制的模式&#xff1a; 1 异步复制 mysql 的最常用的复制&#xff0c;只要执行完&#xff0c;客户端提交事务&#xff0c;主mysql 会立即把结果返回给从…