【数据结构进阶】手撕红黑树

news2025/5/24 17:08:06

在这里插入图片描述

🔥个人主页: Forcible Bug Maker
🔥专栏: C++ || 数据结构

目录

  • 🌈前言
  • 🔥红黑树的概念
  • 🔥手撕红黑树
    • 红黑树结点的定义
    • 红黑树主体需要实现的成员函数
      • ==红黑树的插入==
      • ==find==
      • ==Empty和Size==
      • ==拷贝构造==
      • ==析构函数和clear==
      • ==检测是否为合法红黑树==
      • ==Begin和End==
    • 红黑树的迭代器接口
      • ==* 解引用和 -> 访问==
      • ==operator++()==
      • ==operator- - ()==
      • ==迭代器比较相等==
  • 🌈结语

🌈前言

本篇博客主要内容:红黑树的介绍,以及底层代码逻辑和实现

刚刚接触编程的时候就听说有的大厂HR会让手撕红黑树。心中一直对这个数据结构保持着敬畏和向往,今天在将其撕出来后,用本篇博客加以记录,望周知。

🔥红黑树的概念

红黑树,也是一种二叉搜索树,但再每个结点上增加一个存储位置表示结点的颜色,可以是RED(红)或BLACK(黑)。通过对任何一条根到叶子的路径上各个结点的着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。
在这里插入图片描述
一颗红黑树,是具有如下性质的二叉搜索树:

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

其中,3和4是最重要的两点。

🔥手撕红黑树

红黑树结点的定义

红黑树的结点包含四个成员变量,模板类型T:可以存储K或者pair<K,V>类型,便于后期封装;三个指针:分别为指向左孩子结点的指针,指向右孩子结点的指针,指向父结点的指针;最后变量_col:枚举类型,可以存REDBLACK

enum Color
{
	RED,
	BLACK
};

template<class T>
struct RBTreeNode
{
	RBTreeNode<T>(const T& t)
		: _data(t)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _col(RED)
	{}
	T _data;
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	Color _col;
};

红黑树主体需要实现的成员函数

// T: 可能是键值对<key,value>
//    可能是一个key
// 不论节点中存储的是<key, value> || key, 都是按照key来进行比较的
// KeyOfValue: 提取data中的Key
template<class K, class T, class KeyOfValue>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	typedef RBTreeIterator<T, T&, T*> iterator;
	typedef RBTreeIterator<T, const T&, const T*> const_iterator;
public:
	RBTree() = default;

	RBTree(const RBTree<K, T, KeyOfValue>& t);

	// 插入值为data的节点
	// 返回值含义:iterator代表新插入节点   bool:代表释放插入成功
	std::pair<iterator, bool> Insert(const T& data);
	
	// Begin和End迭代器
	iterator Begin();
	iterator End();

	// 红黑树是否为空,是返回true,否则返回false
	bool Empty()const;
	
	// 返回红黑树中有效节点的个数
	size_t Size()const;
	
	// 将红黑树中的有效节点删除
	void Clear();

	// 检测红黑树是否为有效的红黑树,注意:其内部主要依靠_IsValidRBTRee函数检测
	bool IsValidRBTRee()
	
	// 在红黑树中查找data,存在赶回该节点对应的迭代器,否则返回End()
	iterator Find(const T& data);

	~RBTree();
private:
	Node* _root;
};

红黑树的插入

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

  1. 首先是按照二叉搜索树的方式插入结点
  2. 检测插入结点后,红黑树的性质是否遭到破坏
    因为新结点的默认颜色是红色,因此:如果父结点的颜色是黑色,就没有违反性质,不需调整;担当插入结点的父节点也是红色时,就违反了性质3(不能有连在一起的红结点),此时需要对红黑树分情况讨论调整:

约定:cur为当前结点,p为父节点,g为祖父结点,u为叔叔结点

  • 情况一:cur为红,p为红,g为黑,u存在且为红

在这里插入图片描述
解决方式:将p,u变成黑色,g变成红色,然后把g改成cur,继续向上调整

  • 情况二:cur为红,p为红,g为黑,u不存在/u存在且为黑(这里需要做的其实就和AVL树中的单旋很像了,我们需要把u结点旋转下来,以维持平衡)

在这里插入图片描述

p为g的左孩子,cur为p的左孩子,进行右单旋;相反,p为g的右孩子,cur为p的右孩子,则进行左单旋。p变黑色,g变红色

  • 情况三(情况二的变体):cur为红,p为红,g为黑,u不存在/u存在且为黑(其实就是双旋,除了不用调整平衡因子,其他的和AVL树的双旋并无差别)

在这里插入图片描述

p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;相反,p为g的右孩子,cur为p的左孩子,则针对p做右单旋转。然后就转换成了情况2

针对每种情况进行相应的处理即可:

// 插入值为data的节点
// 返回值含义:iterator代表新插入节点   bool:代表释放插入成功
std::pair<iterator, bool> Insert(const T& data)
{
	if (_root == nullptr) {
		_root = new Node(data);
		_root->_col = BLACK;
		return std::make_pair(iterator(_root, _root), true);
	}

	KeyOfValue kov;

	Node* parent = nullptr;
	Node* cur = _root;
	while (cur) {
		if (kov(cur->_data) < kov(data)) {
			parent = cur;
			cur = cur->_right;
		}
		else if (kov(cur->_data) > kov(data)) {
			parent = cur;
			cur = cur->_left;
		}
		else return std::make_pair(iterator(cur, _root), false);
	}

	cur = new Node(data);
	Node* InsertNode = cur;
	if (kov(parent->_data) < kov(data)) {
		parent->_right = cur;
		cur->_parent = parent;
	}
	else {
		parent->_left = cur;
		cur->_parent = parent;
	}

	while (parent && parent->_col == RED && cur->_col == RED) {
		Node* grandParent = parent->_parent;
		Node* uncle = nullptr;
		//   g
		// p   u
		if (grandParent->_left == parent) {
			uncle = grandParent->_right;
			if (uncle == nullptr || uncle->_col == BLACK) {
				if (parent->_left == cur) {
					RotateR(grandParent);
					parent->_col = BLACK;
					grandParent->_col = RED;
				}
				else {
					RotateL(parent);
					RotateR(grandParent);
					cur->_col = BLACK;
					grandParent->_col = RED;
				}
				break;
			}
			else {
				parent->_col = BLACK;
				grandParent->_col = RED;
				uncle->_col = BLACK;
				cur = grandParent;
				parent = grandParent->_parent;
			}
		}
		//   g
		// u   p
		else {
			uncle = grandParent->_left;
			if (uncle == nullptr || uncle->_col == BLACK) {
				if (parent->_right == cur) {
					RotateL(grandParent);
					parent->_col = BLACK;
					grandParent->_col = RED;
				}
				else {
					RotateR(parent);
					RotateL(grandParent);
					cur->_col = BLACK;
					grandParent->_col = RED;
				}
				break;
			}
			else {
				parent->_col = BLACK;
				grandParent->_col = RED;
				uncle->_col = BLACK;
				cur = grandParent;
				parent = grandParent->_parent;
			}
		}
	}
	_root->_col = BLACK;
	return std::make_pair(iterator(InsertNode, _root), true);
}

在红黑树中,由于不用调节平衡因子,双旋的复杂度大大降低,直接使用单旋并在插入过程中调整结点颜色即可。
旋转的具体内容在AVL树中(【数据结构进阶】AVL树)讲解过,这里就不赘述了。

// 左单旋
void RotateL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	Node* parentParent = parent->_parent;

	if (subRL)
		subRL->_parent = parent;
	subR->_left = parent;
	subR->_parent = parentParent;
	parent->_right = subRL;
	parent->_parent = subR;

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

// 右单旋
void RotateR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	Node* parentParent = parent->_parent;

	if (subLR)
		subLR->_parent = parent;
	subL->_right = parent;
	subL->_parent = parentParent;
	parent->_left = subLR;
	parent->_parent = subL;

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

find

和二叉搜索树的查找规则相同。

// 在红黑树中查找data,存在赶回该节点对应的迭代器,否则返回End()
iterator Find(const T& data)
{
	KeyOfValue kov;
	Node* cur = _root;
	while (cur) {
		if (kov(cur->_data) < kov(data))
			cur = cur->_right;
		else if (kov(cur->_data) > kov(data))
			cur = cur->_left;
		else return iterator(cur, _root);
	}
	return iterator(nullptr, _root);
}

Empty和Size

Empty接口函数用来判断树是否为空;Size用来计算返回树结点的个数。

// 红黑树是否为空,是返回true,否则返回false
bool Empty()const
{
	return _root == nullptr;
}
// 返回红黑树中有效节点的个数
size_t Size()const
{
	return _Size(_root);
}

size_t _Size(Node* root)
{
	return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
}

拷贝构造

RBTree(const RBTree<K, T, KeyOfValue>& t)
{
	_root = _Copy(t._root);
}

Node* _Copy(Node* root)
{
	if (root == nullptr)return nullptr;
	Node* newRoot = new Node(root->_data);
	newRoot->_left = _Copy(root->_left);
	newRoot->_right = _Copy(root->_right);
	return newRoot;
}

析构函数和clear

// 将红黑树中的有效节点删除
void Clear()
{
	_Destroy(_root);
	_root = nullptr;
}

~RBTree()
{
	_Destroy(_root);
	_root = nullptr;
}

void _Destroy(Node* root)
{
	if (root == nullptr)return;
	_Destroy(root->_left);
	_Destroy(root->_right);
	delete root;
}

检测是否为合法红黑树

IsValidRBTRee中,首先算出一条路径上的黑结点个数digit_black,然后在每条路径递归到空结点时判断黑结点个数是否相等,即可验证性质4(所有路径上黑结点个数相等);递归的过程中,判断当前结点和父节点的颜色是否同时为红,即可验证性质3(没有连续的红结点)

// 检测红黑树是否为有效的红黑树,注意:其内部主要依靠_IsValidRBTRee函数检测
bool IsValidRBTRee()
{
	if (_root == nullptr)
		return true;
	Node* cur = _root;
	size_t digit_black = 0;
	while (cur) {
		if (cur->_col == BLACK)
			++digit_black;
		cur = cur->_left;
	}
	return _IsValidRBTRee(_root, digit_black, 0);
}

bool _IsValidRBTRee(Node* pRoot, size_t blackCount, size_t pathBlack)
{
	if (pRoot == nullptr) {
		if (blackCount == pathBlack)
			return true;
		else return false;
	}
	if (pRoot->_col == RED && pRoot->_parent->_col == RED) {
		return false;
	}
	if (pRoot->_col == BLACK) {
		return _IsValidRBTRee(pRoot->_left, blackCount, pathBlack + 1)
			&& _IsValidRBTRee(pRoot->_right, blackCount, pathBlack + 1);
	}
	else {
		return _IsValidRBTRee(pRoot->_left, blackCount, pathBlack)
			&& _IsValidRBTRee(pRoot->_right, blackCount, pathBlack);
	}
}

Begin和End

这两个函数用于获取红黑树的头对象和尾迭代器。

// Begin和End迭代器
iterator Begin() 
{
	return iterator(_LeftMost(), _root);
}
iterator End()
{
	return iterator(nullptr, _root);
}

// 获取红黑树最左侧节点
Node* _LeftMost()
{
	if (_root == nullptr)
		return nullptr;
	Node* parent = _root;
	Node* cur = parent->_left;
	while (cur) {
		parent = cur;
		cur = cur->_left;
	}
	return parent;
}

红黑树的迭代器接口

迭代器的好处是可以方便遍历,使数据结构的底层实现变的透明,从而降低代码编写的复杂程度。

template<class T, class Ref, class Ptr>
struct RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef RBTreeIterator<T, Ref, Ptr> Self;

	RBTreeIterator(Node* pNode,Node* root)
		: _pNode(pNode)
		,_root(root)
	{}

	// 让迭代器具有类似指针的行为
	Ref operator*();
	Ptr operator->();

	// 然迭代器可以移动:前置/后置++  
	Self& operator++();
	Self operator++(int);
	
	// 然迭代器可以移动:前置/后置-- 
	Self& operator--();
	Self operator--(int);

	// 让迭代器可以比较
	bool operator!=(const Self& s)const;
	bool operator==(const Self& s)const;

private:
	Node* _pNode;
	Node* _root;
};

* 解引用和 -> 访问

// 让迭代器具有类似指针的行为
Ref operator*()
{
	return _pNode->_data;
}
Ptr operator->()
{
	return &(_pNode->_data);
}

operator++()

二叉树的中序遍历并不难实现,但是要实现从任意一个结点按中序遍历跑到下一个结点,这就有相当难度了。
具体逻辑为:

  1. 右子树不为空,访问右子树的最左结点。
  2. 右子树为空(代表当前结点所在子树访问完了),沿着到根节点的路线,孩子是父亲左的那个祖先结点,就是下一个要访问的结点。
// 迭代器可以移动:前置/后置++  
Self& operator++()
{
	if (_pNode->_right) {
		_pNode = _pNode->_right;
		while (_pNode->_left) 
			_pNode = _pNode->_left;
	}
	else {
		Node* cur = _pNode;
		Node* parent = cur->_parent;
		while (parent && cur == parent->_right) {
			cur = parent;
			parent = parent->_parent;
		}
		_pNode = parent;
	}
	return *this;
}

Self operator++(int)
{
	Node* rem = _pNode;
	if (_pNode->_right) {
		_pNode = _pNode->_right;
		while (_pNode->_left)
			_pNode = _pNode->_left;
	}
	else {
		Node* cur = _pNode;
		Node* parent = cur->_parent;
		while (parent && cur == parent->_right) {
			cur = parent;
			parent = parent->_parent;
		}
		_pNode = parent;
	}
	return Self(rem);
}

operator- - ()

这时候要判断当前迭代器是否指向尾End(),同时判断树是否为空,这就要用到传入迭代器对象中的_root了。在找到End()的前一个结点之后,按照和operator++()相反的逻辑即可实现operator--()
具体逻辑为:

  1. 左子树不为空,访问左子树的最右结点。
  2. 左子树为空(代表当前结点所在子树访问完了),沿着到根结点的路线,孩子是父亲右的那个祖先结点,就是下一个要访问的结点。
// 迭代器可以移动:前置/后置-- 
Self& operator--()
{
	if (_pNode == nullptr) {
		if (_root == nullptr)
			assert(false);
		Node* parent = _root;
		Node* cur = parent->_right;
		while (cur) {
			parent = cur;
			cur = cur->_right;
		}
		_pNode = parent;
		return *this;
	}
	if (_pNode->_left) {
		_pNode = _pNode->_left;
		while (_pNode->_right)
			_pNode = _pNode->_right;
	}
	else {
		Node* cur = _pNode;
		Node* parent = cur->_parent;
		while (parent && cur == parent->_left) {
			cur = parent;
			parent->_parent;
		}
		_pNode = parent;
	}
	return *this;
}

Self operator--(int)
{
	if (_pNode == nullptr) {
		if (_root == nullptr)
			assert(false);
		Node* parent = _root;
		Node* cur = parent->_right;
		while (cur) {
			parent = cur;
			cur = cur->_right;
		}
		_pNode = parent;
		return Self(nullptr);
	}
	Node* rem = _pNode;
	if (_pNode->_left) {
		_pNode = _pNode->_left;
		while (_pNode->_right)
			_pNode = _pNode->_right;
	}
	else {
		Node* cur = _pNode;
		Node* parent = cur->_parent;
		while (parent && cur == parent->_left) {
			cur = parent;
			parent->_parent;
		}
		_pNode = parent;
	}
	return Self(rem);
}

迭代器比较相等

底层就是用指针作比较。

// 让迭代器可以比较
bool operator!=(const Self& s)const
{
	return _pNode != s._pNode;
}

bool operator==(const Self& s)const
{
	return _pNode == s._pNode;
}

🌈结语

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( l o g 2 N log_2 N log2N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。
红黑树的底层实现到这里就要结束了,本篇的数据结构较为复杂,模板的使用也有很多容易出错的点,需要多加体会。感谢大家的支持♥

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

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

相关文章

CANFD报文 位时间 理解

&#x1f345; 我是蚂蚁小兵&#xff0c;专注于车载诊断领域&#xff0c;尤其擅长于对CANoe工具的使用&#x1f345; 寻找组织 &#xff0c;答疑解惑&#xff0c;摸鱼聊天&#xff0c;博客源码&#xff0c;点击加入&#x1f449;【相亲相爱一家人】&#x1f345; 玩转CANoe&…

PCB设计——51单片机核心板布线以及原理图

首先是最小系统板&#xff0c;包括晶振电路&#xff0c;电源电路&#xff0c;复位电路 对应pcb板图

HTTP协议:网络通信的基石

一、引言 HTTP&#xff08;HyperText Transfer Protocol&#xff09;&#xff0c;即超文本传输协议&#xff0c;是当今互联网世界中最为重要的协议之一。它是客户端和服务器之间进行通信的规则和标准&#xff0c;使得我们能够在浏览器中浏览网页、下载文件、提交表单等各种操作…

AT32F403A/421 SVPWM驱动无刷电机开环速度测试

AT32F403A/421 SVPWM驱动无刷电机开环速度测试 &#x1f4cc;相关篇《HAL STM32F4 ARM DSP库跑SVPWM开环速度测试》 ✨本测试工程基于上面的运行例程移植而来。主要用来测试驱动无刷电机性能方面的差异。 &#x1f516;工程基于AT32_Work_Bench创建。 &#x1f530;AT32F403A和…

卷积神经网络随记

1.问题描述&#xff1a;一般而言&#xff0c;几个小滤波器卷积层的组合比一个大滤波器卷积层要好&#xff0c;比如层层堆叠了3个3x3的卷积层&#xff0c;中间含有非线性激活层&#xff0c;在这种排列下面&#xff0c;第一个卷积层中每个神经元对输入数据的感受野是3x3&#xff…

Verilog语言和C语言的本质不同点是什么?

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「c语言的资料从专业入门到高级教程」&#xff0c;点个关注在评论区回复“666”之后私信回复“666”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01; 在c语言中&#xff0c;如果你…

7.Redis的Hash类型

Hash类型&#xff0c;也叫散列&#xff0c;其value是一个无序字典&#xff0c;类似于HashMap结构。 问题 String结构是将对象序列化为json字符串后存储&#xff0c;当需要修改对象某个字段是不是很方便。 key value…

【计算机遥感方向】SCI期刊推荐!水刊、顶刊齐聚在此,速投!

本期将为您带来五本计算机SCI 妥妥毕业神刊&#xff01; IEEE TRANSACTIONS ON GEOSCIENCE AND REMOTE SENSING International Journal of Applied Earth Observation and Geoinformation INTERNATIONAL JOURNAL OF REMOTE SENSING Geocarto International RADIO SCIEN…

蔚来智驾的大模型之路:自研芯片 + 世界模型 + 群体智能

作者 |德新 编辑 |王博 7月27日上周末&#xff0c;蔚来举办第二届NIO IN。 李斌说&#xff0c;2023年的第一届NIO IN像是一个大纲&#xff0c;第一次对外完整展示了蔚来布局的12大技术领域。 而这届&#xff0c;更像第一个交付的章节。它重点展示了5项阶段性的进展&#xff…

智能电池管理,soc、soh、comsol锂电池仿真

锂离子电池&#xff0c;作为能源转型与电动车市场崛起的基石&#xff0c;正迎来研发与应用的飞跃。面对繁杂设计参数与实验盲点&#xff0c;电池仿真技术&#xff0c;尤以COMSOL为代表的多物理场仿真&#xff0c;精准解析电池内部机理&#xff0c;从微观行为到宏观性能&#xf…

LoRA:大模型的轻量级高效微调方法

文章目录 1. 模型微调的两种方式2. LoRA 实现 LoRA是一种轻量化且效果非常突出的大模型微调方法&#xff0c;与使用Adam微调的GPT-3 175B相比&#xff0c;LoRA可以将可训练参数的数量减少10000倍&#xff0c;并将GPU内存需求减少3倍。 paper&#xff1a;LoRA: Low-Rank Adapta…

二维码门楼牌管理应用平台建设:流程优化与全面考量

文章目录 前言一、工作流程优化&#xff1a;移动端采集与实时更新二、数据完整性与准确性保障三、效率提升与成本节约四、扩展性与未来发展五、数据安全与隐私保护六、用户培训与技术支持 前言 随着智慧城市建设的不断深入&#xff0c;二维码门楼牌管理应用平台作为城市管理的…

电脑浏览器缓存怎么清除 Mac电脑如何清理浏览器缓存数据 macbookpro浏览器怎么清理

浏览器已经成为我们日常生活中不可或缺的工具。然而&#xff0c;随着时间的推移&#xff0c;浏览器缓存的积累可能会逐渐影响我们的上网体验&#xff0c;导致网页加载速度变慢、浏览器运行卡顿等问题。因此&#xff0c;定期清理浏览器缓存变得尤为重要。那么Mac怎么清除浏览器缓…

Springboot学习-day16

Springboot学习-day16 Springboot是spring家族中的一个全新框架&#xff0c;用来简化spring程序的创建和开发过程。在以往我们通过SpringMVCSpringMybatis框架进行开发的时候&#xff0c;我们需要配置web.xml&#xff0c;spring配置&#xff0c;mybatis配置&#xff0c;然后整…

layui+jsp框架下实现对pdf或图片预览功能

功能 对上传的文件实现预览功能&#xff0c;文件类型为图片或pdf。 效果展示 实现 引入 jQuery&#xff1a; <script src"https://code.jquery.com/jquery-3.5.1.min.js"></script>引入 Bootstrap 的 CSS 和 JavaScript&#xff1a; <link href&quo…

Java面试必看!知己知彼才能百战百胜,如何做好面试前的准备?

随着 Java 这个赛道的不断内卷&#xff0c;这两年&#xff0c;Java 程序员的面试&#xff0c;从原来的常规八股文&#xff08;有 标准答案&#xff09;到现在&#xff0c;以项目、场景问题、技术深度思考为主&#xff0c;逐步转变成没有标准答案&#xff0c; 需要大家基于自己的…

【大厂笔试】翻转、平衡、对称二叉树,最大深度、判断两棵树是否相等、另一棵树的子树

检查两棵树是否相同 100. 相同的树 - 力扣&#xff08;LeetCode&#xff09; 思路解透 两个根节点一个为空一个不为空的话&#xff0c;这两棵树就一定不一样了若两个跟节点都为空&#xff0c;则这两棵树一样当两个节点都不为空时&#xff1a; 若两个根节点的值不相同&#xff…

【时时三省】(C语言基础)函数的嵌套调用和链式访问

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ——csdn时时三省 嵌套调用 每一个函数都只能在大括号的外面独立存在 不能在一个函数的里面还有一个函数 这样是不行的 函数是不能嵌套定义的 但是函数可以嵌套调用 比如在外面建立函数1&函数 然后在mai…

小区房布置超五类网线,怎么网络只有100Mbps?

前言 最近有粉丝找到小白&#xff0c;说家里的网络怎么一直都是100Mbps&#xff0c;宽带明明是1000Mbps的&#xff0c;只用了十分之一。 一开始小白以为是家里的网络使用的是两对双绞线的那种网线&#xff08;一共四芯&#xff09;。 随即她说水晶头接的都是8根&#xff0c;…

JSP分页写法

一、写界面框架&#xff1a; <html> <head><title>学生管理系统</title><style>body { font-family: 微软雅黑; background-color: #e0f7fa; margin: 0; padding: 0; display: flex; justify-content: center; align-items: center; margin-top…