【数据结构(C++)】树型查找——二叉搜索树

news2024/11/23 12:29:25

目录

1. 二叉搜索树

1.1 二叉搜索树的概念

1.2 二叉搜索树类模板

1.3 二叉搜索树的操作

1.3.1 查找

1.3.2 插入

1.3.3 删除

1.4 二叉搜索树的性能分析

2. 平衡二叉树

2.1 平衡二叉树的概念

2.2 平衡二叉树类模板

2.3 二叉搜索树的插入

3. 红黑树

3.1 红黑树的概念

3.2 红黑树类模板


1. 二叉搜索树

1.1 二叉搜索树的概念

二叉搜索树(Binary Search Tree)又称二叉查找树、二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

  • 若左子树非空,则左子树上所有结点的值都小于根结点的值
  • 若右子树非空,则右子树上所有结点的值都大于根结点的值
  • 它的左右子树也分别为二叉搜索树

根据二叉排序树的定义,左子树结点值<根结点值<右子树结点值,因此对二叉排序树进行中序遍历,可以得到一个递增的有序序列。

1.2 二叉搜索树类模板

//二叉搜索树结点
template<class K>
struct BSTreeNode
{
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
	K _key;

	BSTreeNode(const K& key)
		:_left(nullptr)
		, _right(nullptr)
		, _key(key)
	{}
};

//二叉搜索树
template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
    //查找
	bool Find(const K& key)
	{
		//……
	}

    //插入
	bool Insert(const K& key)
	{
		//……
	}

    //删除
	bool Erase(const K& key)
	{
		//……
	}

private:
	Node* _root = nullptr;
};

1.3 二叉搜索树的操作

1.3.1 查找

  1. 从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
  2. 最多查找高度次,走到到空,还没找到,这个值不存在。
bool Find(const K& key)
{
	Node* cur = _root;
	while (cur)
	{
		if (cur->_key < key)
		{
			cur = cur->_right;
		}
		else if (cur->_key > key)
		{
			cur = cur->_left;
		}
		else
		{
			return true;
		}
	}
	return false;
}

1.3.2 插入

  • 树为空,则直接新增结点,赋值给root指针
  • 树不空,按二叉搜索树性质查找插入位置,插入新结点
bool Insert(const K& key)
{
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}

	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}

	cur = new Node(key);
	//链接
	if (parent->_key < key)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}

	return true;
}

1.3.3 删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回; 否则要删除的结点可能分下面四种情
况:

  1. 要删除的结点是叶子结点——直接删除
  2. 要删除的结点没有右孩子——令Node的子树成为Node双亲结点的子树,替代Node的位置
  3. 要删除的结点没有左孩子——令Node的子树成为Node双亲结点的子树,替代Node的位置
  4. 要删除的结点有左、右孩子——令Node的直接后继(或直接前驱)替代Node,然后从二叉搜索树中删除这个直接后继(或直接前驱),这样就转换成了情况2或情况3。

Node的直接后继:在中序遍历下,Node的后一个结点。在二叉搜索树中,Node的直接后继就是在它的右子树中关键码最小的结点。

Node的直接前驱:在中序遍历下,Node的前一个结点。在二叉搜索树中,Node的直接前驱就是在它的左子树中关键码最大的结点。

实际情况1可以与情况2或者3合并起来,删除结点就分为3种情况。

bool Erase(const K& key)
{
	Node* parent = nullptr;
	Node* cur = _root;

	while (cur)
	{
		if (cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			//1、左为空
			if (cur->_left == nullptr)
			{
				if (cur == _root)
				{
					_root = cur->_right;
				}
				else
				{
					if (parent->_left == cur)
					{
						parent->_left = cur->_right;
					}
					else
					{
						parent->_right = cur->_right;
					}
				}
				delete cur;

			}
			//2、右为空
			else if (cur->_right == nullptr)
			{
				if (cur == _root)
				{
					_root = cur->_left;
				}
				else
				{
					if (parent->_left == cur)
					{
						parent->_left = cur->_left;
					}
					else
					{
						parent->_right = cur->_left;
					}
				}
				delete cur;
			}
			//3、左、右都不为空
			else
			{
				//找右子树最小结点替代,也可以是左树最大结点替代
				Node* pminRight = cur;
				Node* minRight = cur->_right;
				while (minRight->_left)
				{
					pminRight = minRight;
					minRight = minRight->_left;
				}

				cur->_key = minRight->_key;

				if (pminRight->_left == minRight)
				{
					pminRight->_left = minRight->_right;
				}
				else
				{
					pminRight->_right = minRight->_right;
				}

				delete minRight;
			}

			return true;
		}
	}

	return false;
}

1.4 二叉搜索树的性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能,查找效率,主要取决于树的高度。

对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

最好情况下,二叉搜索树的左、右子树的高度之差的绝对值不超过1(平衡二叉树),时间复杂度为O(logn)。

最坏情况下,二叉搜索树是一个只有右(左)孩子的单支树(类似于有序的单链表),时间复杂度为O(n)。

2. 平衡二叉树

2.1 平衡二叉树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)

2.2 平衡二叉树类模板

//AVL树结点
template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	int _bf;//balance factor

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

//AVL树
template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
    //插入
	bool Insert(const pair<K, V>& kv)
	{
		//……
	}

    //中序遍历
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

    //是否平衡
	bool IsBalance()
	{
		return _IsBalance(_root);
	}

    //高度
	int Height()
	{
		return _Height(_root);
	}

private:
    //高度
	int _Height(Node* root)
	{
		if (root == NULL)
			return 0;

		int leftH = _Height(root->_left);
		int rightH = _Height(root->_right);

		return leftH > rightH ? leftH + 1 : rightH + 1;
	}

    //是否平衡
	bool _IsBalance(Node* root)
	{
		if (root == NULL)
		{
			return true;
		}

		int leftH = _Height(root->_left);
		int rightH = _Height(root->_right);

		if (rightH - leftH != root->_bf)
		{
			cout << root->_kv.first << "结点平衡因子异常" << endl;
			return false;
		}

		return abs(leftH - rightH) < 2
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);
	}

    //左单旋转
	void RotateL(Node* parent)
	{
		//……
	}

    //右单旋转
	void RotateR(Node* parent)
	{
		//……
	}

    //左右双旋转
	void RotateLR(Node* parent)
	{
		//……
	}

    //右左双旋转
	void RotateRL(Node* parent)
	{
		//……
	}

    //中序遍历
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}

    //成员变量
	Node* _root = nullptr;
};

2.3 二叉搜索树的插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么
AVL树的插入过程可以分为两步:

  1. 按照二叉搜索树的方式插入新结点
  2. 调整结点的平衡因子

如果在一棵原本是平衡的AVL树中插入一个新结点,可能造成不平衡,此时必须调整树的结构,
使之平衡化。根据结点插入位置的不同,AVL树的旋转分为四种:

1. 新结点插入较高左子树的左侧——左左:右单旋

上图在插入前,AVL树是平衡的,新结点插入到30的左子树(注意:此处不是左孩子)中,30左
子树增加了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60左子树的高度减少一层,右子树增加一层,即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的右子树,而如果30有右子树,右子树根的值一定大于30,小于60,只能将其放在60的左子树,旋转完成后,更新结点的平衡因子即可。在旋转过程中,有以下几种情况需要考虑:

  1. 30结点的右孩子可能存在,也可能不存在
  2. 60可能是根结点,也可能是子树(如果是根结点,旋转完成后,要更新根结点;如果是子树,可能是某个结点的左子树,也可能是右子树)
void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

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

		Node* ppnode = parent->_parent;

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

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

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

2. 新结点插入较高右子树的右侧——右右:左单旋

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

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

		Node* ppnode = parent->_parent;

		subR->_left = parent;
		parent->_parent = subR;

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

			subR->_parent = ppnode;
		}

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

3. 新结点插入较高左子树的右侧——左右:先左单旋再右单旋

先对30进行左单旋,然后再对90进行右单旋。

新结点究竟是插入b还是插入c还是60本身就是新结点,不影响旋转过程。

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

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

		if (bf == 1)
		{
			parent->_bf = 0;
			subLR->_bf = 0;
			subL->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
			subLR->_bf = 0;
			subL->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subLR->_bf = 0;
			subL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

4. 新结点插入较高右子树的左侧——右左:先右单旋再左单旋

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

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

		if (bf == 1)
		{
			subR->_bf = 0;
			parent->_bf = -1;
			subRL->_bf = 0;
		}
		else if (bf == -1)
		{
			subR->_bf = 1;
			parent->_bf = 0;
			subRL->_bf = 0;
		}
		else if (bf == 0)
		{
			subR->_bf = 0;
			parent->_bf = 0;
			subRL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

总结:

假如以parent为根的子树不平衡,即parent的平衡因子为2或者-2,分以下情况考虑:

1. parent的平衡因子为2,说明parent的右子树高,设parent的右子树的根为subR:

当subR的平衡因子为1时,执行左单旋;

当subR的平衡因子为-1时,执行右左双旋。

2. parent的平衡因子为-2,说明parent的左子树高,设parent的左子树的根为subL:

当subL的平衡因子为-1时,执行右单旋;

当subL的平衡因子为1时,执行左右双旋。

旋转完成后,原parent为根的子树个高度降低,已经平衡,不需要再向上更新。

bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		Node* parent = nullptr;
		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->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		// 更新平衡因子
		while (parent)
		{
			if (cur == parent->_right)
			{
				parent->_bf++;
			}
			else
			{
				parent->_bf--;
			}

			if (parent->_bf == 1 || parent->_bf == -1)
			{
				// 继续更新
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if (parent->_bf == 0)
			{
				break;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				// 需要旋转处理 -- 1、让这颗子树平衡 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)
				{
					RotateLR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}
				else
				{
					assert(false);
				}

				break;
			}
			else
			{
				assert(false);
			}
		}

		return true;
	}

3. 红黑树

3.1 红黑树的概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或
Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路
径会比其他路径长出俩倍,因而是接近平衡的。

红黑树有如下性质:

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

3.2 红黑树类模板

enum Colour
{
	RED,
	BLACK,
};

template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	T _data;
	Colour _col;

	RBTreeNode(const T& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _col(RED)
	{}
};

template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __RBTreeIterator<T, Ref, Ptr> Self;
	Node* _node;

	__RBTreeIterator(Node* node)
		:_node(node)
	{}

	Ref operator*()
	{
		return _node->_data;
	}

	Ptr operator->()
	{
		return &_node->_data;
	}

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

	Self& operator++()
	{
		if (_node->_right)
		{
			// 1、右不为空,下一个就是右子树的最左节点
			Node* subLeft = _node->_right;
			while (subLeft->_left)
			{
				subLeft = subLeft->_left;
			}

			_node = subLeft;
		}

		return *this;
	}
};

// 仿函数
template<class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	~RBTree()
	{
		_Destroy(_root);
		_root = nullptr;
	}
public:
	typedef __RBTreeIterator<T, T&, T*> itertaor;
	typedef __RBTreeIterator<T, const T&, const T*> const_itertaor;

	itertaor begin()
	{
		Node* cur = _root;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}

		return itertaor(cur);
	}

	itertaor end()
	{
		return itertaor(nullptr);
	}


	Node* Find(const K& key)
	{
		Node* cur = _root;
		KeyOfT kot;
		while (cur)
		{
			if (kot(cur->_data) < key)
			{
				cur = cur->_right;
			}
			else if (kot(cur->_data) > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}

		return nullptr;
	}

	bool Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return true;
		}

		KeyOfT kot;
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (kot(cur->_data) < kot(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kot(cur->_data) > kot(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(data);
		if (kot(parent->_data) > kot(data))
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				// 情况1:u存在且为红,变色处理,并继续往上处理
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else // 情况2+3:u不存在/u存在且为黑,旋转+变色
				{
					//     g
					//   p   u
					// c 
					if (cur == parent->_left)
					{
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//     g
						//   p   u
						//     c
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						//parent->_col = RED;
						grandfather->_col = RED;
					}

					break;
				}
			}
			else // (grandfather->_right == parent)
			{
				//    g
				//  u   p
				//        c
				Node* uncle = grandfather->_left;
				// 情况1:u存在且为红,变色处理,并继续往上处理
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else // 情况2+3:u不存在/u存在且为黑,旋转+变色
				{
					//    g
					//  u   p
					//        c
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						//    g
						//  u   p
						//    c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
		}

		_root->_col = BLACK;

		return true;
	}

	bool IsBalance()
	{
		if (_root && _root->_col == RED)
		{
			cout << "根节点颜色是红色" << endl;
			return false;
		}

		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
				++benchmark;
			cur = cur->_left;
		}

		// 连续红色节点
		return _Check(_root, 0, benchmark);
	}

	int Height()
	{
		return _Height(_root);
	}

private:
	void _Destroy(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
	}

	int _Height(Node* root)
	{
		if (root == NULL)
			return 0;

		int leftH = _Height(root->_left);
		int rightH = _Height(root->_right);

		return leftH > rightH ? leftH + 1 : rightH + 1;
	}

	bool _Check(Node* root, int blackNum, int benchmark)
	{
		if (root == nullptr)
		{
			if (benchmark != blackNum)
			{
				cout << "某条路径黑色节点的数量不相等" << endl;
				return false;
			}

			return true;
		}

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

		if (root->_col == RED 
			&& root->_parent 
			&& root->_parent->_col == RED)
		{
			cout << "存在连续的红色节点" << endl;
			return false;
		}

		return _Check(root->_left, blackNum, benchmark)
			&& _Check(root->_right, blackNum, benchmark);
	}

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

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

		Node* ppnode = parent->_parent;

		subR->_left = parent;
		parent->_parent = subR;

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

			subR->_parent = ppnode;
		}
	}

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

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

		Node* ppnode = parent->_parent;

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

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

private:
	Node* _root = nullptr;
};

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

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

相关文章

合宙Air724UG Cat.1模块硬件设计指南--Camera接口

Camera接口 简介 CAT.1模块支持一路摄像头接口&#xff0c;可以用于扫码&#xff0c;拍照应用。 特性 仅支持SPI接口实现 最高像素30W像素 支持数据格式YUV422, Y420, RAW8, RAW10 集成GC0310驱动 管脚功能 CAM_PWDN。关闭Camera&#xff0c;上电状态默认下拉输入。 CAM_RST。…

pg 绑定变量源码解析

pg 绑定变量相关源码解析 下面以callstmt 为例 说明一下绑定参数的执行流程&#xff08;基于pg13&#xff09; 整体架构 1. exec_parse_message 解析变量。 --> parse_analyze_varparams 解析绑定参数$id--->parse_variable_parameters &#xff0c; 把返回值paramTyp…

《Java黑皮书基础篇第10版》 第18章【习题】

Java语言程序设计 习题第十八章 18.2章节习题 18.1 什么是递归方法?什么是无限递归? 递归方法可以拆解为递和归。在Java中&#xff0c;大多数方法的执行都需要调用栈&#xff0c;来跟踪方法的调用和返回。在递的过程中&#xff0c;递归方法调用自身&#xff0c;把新的调用添…

Vue3 开发语法使用总结(超详细、超基础)

前言 最近开源了一套后台管理模板Wocwin-Admin&#xff0c;是基于 Vue3.2、TypeScript、Vite4、Pinia、Element-Plus、Qiankun(微前端) 技术栈&#xff0c;借此归纳一下Vue3.2的新语法。 一、全局注册(属性/方法) 1、main.ts注册 import { createApp } from "vue";…

Ubuntu22 2023最新版安装教程

Ubuntu22安装教程 2023全网最新版 前置资源准备 如果选择使用虚拟机安装&#xff0c;那么需要准备VmwareWorkstation 在官网进行下载安装 VmwareWorkstation官网:https://www.vmware.com/products/workstation-pro/workstation-pro-evaluation.html 前置资源准备好后就可以…

【SpringCloud config分布式配置中心】—— 每天一点小知识

&#x1f4a7; S p r i n g C l o u d c o n f i g 分布式配置中心 \color{#FF1493}{SpringCloud config分布式配置中心} SpringCloudconfig分布式配置中心&#x1f4a7; &#x1f337; 仰望天空&#xff0c;妳我亦是行人.✨ &#x1f984; 个人主页——微风撞见云的…

okcc呼叫系统运营商的重点功能有什么

一、资费套餐模块 资费套餐&#xff0c;即客户进行业务时使用的资费标准。填写资费套餐名称&#xff0c;选择计费规则方式&#xff0c;点击“确认”按钮即可创建一条资费套餐&#xff0c;如下图所示。 计费规则即计费所遵循的规则。OKCC系统目前设计了以下三种计费方式(后续还…

Caretta 利用 eBPF 实现 Kubernetes 应用网络拓扑

介绍 Caretta 是一种轻量级的独立工具&#xff0c;快速展示集群中运行的服务可视化网络图。 Caretta 利用 eBPF 有效地展示 K8s 集群中的服务网络交互图&#xff0c;并利用 Grafana 查询和可视化收集的数据。科学家们早就知道&#xff0c;海龟和许多动物一样&#xff0c;通过…

【瑞萨RA_FSP】AGT——低功耗定时器

文章目录 一、AGT简介二、AGT的框图分析1. 16位计数器2. 16位重装载寄存器3. 计数时钟源4. 比较匹配功能5. 比较匹配输出引脚6. 输出引脚7. 下溢事件信号/测量完成事件信号输出 三、AGT工作模式详解四、实验&#xff1a;比较匹配功能——PWM输出1. 硬件设计2. 文件结构3. FSP配…

基础篇:新手使用vs code新建go项目(从0开始到运行)

学习新语言&#xff0c;搭建新环境。在网上找了一些教程&#xff0c;感觉还是写一个比较详细的方便以后自己使用。其实vs code没有新建项目这个功能&#xff0c;具体怎么运行go语言的项目请看下文。 一、下载GO安装包 1.点击go安装包下载链接下载相应的版本&#xff08;本次下…

【计算机网络自顶向下】简答题习题总结(三)

文章目录 第三章 传输层UDP用户数据报协议可靠数据传输原理面向连接传输TCP流量控制可靠数据传输机制 题目 第三章 传输层 传输层服务&#xff1a;在两个不同的主机的运行应用程序之间提供逻辑通信 在接收主机多路分解 将接收到的数据段传递给正确的套接字【多路分解】 在发送…

线程与轻进程(OS)

目录 1、进程的引入 2、线程的概念 3、线程的结构 3、线程控制块 5、线程的实现 &#xff08;1&#xff09;用户级别线程 &#xff08;2&#xff09;核心级别线程 &#xff08;3&#xff09;混合线程 6、线程的应用 1、进程的引入 进程切换 上下文涉及内容多&#xf…

软件测试面试,从简历到面试常问,不学几招怎么跳槽?

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 软件测试面试环节…

关于Java多线程不安全的问题简析

在了解多线程不安全的问题之前 让我们先来看如下代码 public class demo18 {public static int count 0;public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(()->{for (int i 0; i < 10000; i) {count;}});Thread t2 new …

软件测试报告需要做哪些测试内容?软件测试外包服务公司靠谱吗?

在软件开发领域中&#xff0c;测试是最为重要的环节之一&#xff0c;它在确保软件质量方面有着至关重要的作用。软件测试是一种检验软件代码是否符合设计和用户期望的过程。软件测试的主要目的是发现缺陷并确保软件在实际使用中的可靠性&#xff0c;安全性&#xff0c;以及稳定…

Linux中centos修改系统时间并写到硬件,Linux中centos设置定时自动同步网络时间

文章目录 前言一、centos修改系统时间并写到硬件1.1查看当前的系统时间1.2修改系统时间1.3查看硬件时间1.4同步系统时间和硬件时间1.5本地时间写入硬件时间 二、centos设置定时自动同步网络时间2.1安装ntpdate工具2.2CentOS安装/操纵crontab2.3启动crontab并查看状态2.4写一个c…

Ubuntu系统安装Mysql服务并设置远程连接-Navicat连接Mysql-物联网系统

目录 一、前言 二、Mysql的安装 三、Mysql服务管理 四、配置Mysql远程连接 五、修改登录限制 六、修改Root密码 七、Navicat连接Mysql 一、前言 在我们购买服务器后&#xff0c;常需要在服务器上部署数据库以存储我们所需要的数据&#xff0c;因此我们本文将在Ubuntu系统…

LeetCode - #81 搜索旋转排序数组 II

文章目录 前言1. 描述2. 示例3. 答案关于我们 前言 我们社区陆续会将顾毅&#xff08;Netflix 增长黑客&#xff0c;《iOS 面试之道》作者&#xff0c;ACE 职业健身教练。&#xff09;的 Swift 算法题题解整理为文字版以方便大家学习与阅读。 LeetCode 算法到目前我们已经更新…

解决阿里云服务器被植入挖矿脚本过程

文章目录 前言一、服务器为什么会被告警挖矿&#xff1f;二、怎么解决&#xff1a;1.top 命令查看进程cpu 占用情况&#xff1a;2.通过pid进程号&#xff0c;查找改程序所在的目录&#xff1a;3. 强制删除脚本文件&#xff1a;4. 强制杀死进程&#xff1a;5. 检查是否有脚本的定…

three.js物体纹理及其常用属性介绍

一、Three中的纹理和材质介绍 THREE中的纹理和材质是用来渲染3D场景中的物体表面的。纹理贴图定义物体表面的颜色和外观&#xff0c;而材质则定义物体表面如何反射光线。 纹理可以使用多种类型的图像文件&#xff0c;包括JPEG、PNG、GIF等。纹理可以是简单的颜色、图案或者是复…