【C++、数据结构】二叉排序树(二叉查找树、二叉搜索树)(图解+完整代码)

news2024/11/25 16:30:30

目录

[⚽1.什么是二叉排序树]

[🏐2.构建二叉排序树]

[🏀3.二叉排序树的查找操作]

[🥎4.二叉排序树的删除]

[🎱5.完整代码]

⚽1.什么是二叉排序树

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树
    在这里插入图片描述

🏐2.构建二叉排序树

  1. 二叉搜索树的模拟实现

2.1 结点的声明

//描述二分查找树的一个结点
template<class K>
struct BSTNode
{
	K _key;						//数据域
	struct BSTNode* _left;		//指向左子树指针
	struct BSTNode* _right;		//指向右子树指针
	BSTNode(K key)
		:_key(key)
		,_left(nullptr)
		,_right(nullptr)
	{}
};

2.2 基本的几个成员函数

template<class K>

class BSTree
{               
	typedef BSTreeNode<K> Node;
private:
	//没有参数是不能递归的
	void DestroyTree(Node* root)
	{
		if (root == nullptr)
			return;
		DestroyTree(root->_left);
		DestroyTree(root->_right);
		delete root;
	}

	Node* CopyTree(Node* root)
	{
		if (root == nullptr)
			return nullptr;

		Node* copyNode = new Node(root->_key);
		copyNode->_left = CopyTree(root->_left);
		copyNode->_right = CopyTree(root->_right);
		
		return copyNode;
	}
public:
	//强制编译器自己生成构造函数 -- C++11
	BSTree() = default;

	/*BSTree()
		:_root(nullptr)
	{}*/

	//前序遍历递归拷贝
	BSTree(const BSTree<K>& t)
	{
		_root = CopyTree(t._root);
	}

	//t1 = t2; -- 任何赋值重载都可以用现代写法
	BSTree<K>& operator=(BSTree<K> t)
	{swap(_root, t._root);
		return *this;
	}

	~BSTree()
	{
		DestroyTree(_root);
		_root = nullptr;
	}

构造函数

  • 这里我们可以采用传统的方法
  • 直接初始化成员变量
  • 也可以用C++11的语法default
  • 强制编译器自己生成构造函数

拷贝构造

  • 这里我们用了递归的方式进行拷贝
  • 采用根 - 左 - 右 的前序遍历的递归方式对整个二叉树拷贝
  • 最后将跟结点返回

析构函数

  • 析构函数我们这里也是采用递归的方式进行一个一个结点析构
  • 同样的我们再嵌套一个子函数
  • 也是采用类似前序遍历的方法将整个二叉树释放掉

采用递归方式的缺点就是如果数的结点个数足够多的时候,就会有爆栈的风险!!

2.3 插入操作

假设我们有以下数据,我们按从左到右的顺序来构建二叉排序树:

  1. 首先,将8作为根节点
  2. 插入3,由于3小于8,作为8的左子树
  3. 插入10,由于10大于8,作为8的右子树
  4. 插入1,由于1小于8,进入左子树3,1又小于3,则1为3的左子树
  5. 插入6,由于6小于8,进入左子树3,6又大于3,则6为3的右子树
  6. 插入14,由于14大于8,进入右子树10,14又大于10,则14为10的右子树
  7. 插入4,由于4小于8,进入左子树3,4又大于3,进入右子树6,4还小于6,则4为6的左子树
  8. 插入7,由于7小于8,进入左子树3,7又大于3,进入右子树6,7还大于于6,则7为6的右子树
  9. 插入13,由于13大于8,进入右子树10,又13大于10,进入右子树14,13小于14,则13为14的左子树

经过以上的逻辑,这棵二叉排序树构建完成。

我们可以看出:

  • 只要左子树为空,就把小于父节点的数插入作为左子树
  • 只要右子树为空,就把大于父节点的数插入作为右子树
  • 如果不为空,就一直往下去搜索,直到找到合适的插入位置

没错,这棵二叉树中序遍历结果为:

  • 二叉树中序遍历结果为升序,左节点<根节点<右节点

插入思路:

  • 从根结点开始遍历。(不能相等哦,直接结束就好
  • key<遍历结点的值,则遍历其左子树;key>遍历结点的值,则遍历其右子树
  • 直到遍历到某个叶子结点
  • 插入:比叶子节点小,插入左子树,反之,右子树

根据以上思路,我们其实就可以写出代码了,构建的过程其实就是插入的过程:

//插入函数
bool Insert(const K key)
{
	Node* newnode = new Node(key);
	//空树时
	if (_root == NULL)
	{
		_root = newnode;
		return true;
	}
	Node* cur = _root;			//用来遍历
	Node* parent = nullptr;		//记录上一个节点
	while (cur)
	{
		parent = cur;
		//key< 结点值,遍历其左子树
		if (key < cur->_key)
		{
			cur = cur->_left;
		}
		//key> 结点值,遍历其右子树
		else if (key > cur->_key)
		{
			cur = cur->_right;
		}
		//不能插入相同的
		else
		{
			return false;
		}
	}
	if (key < parent->_key)
	{
		parent->_left = newnode;
	}
	if (key > parent->_key)
	{
		parent->_right = newnode;
	}
	return true;
}

🏀3.二叉排序树的查找操作

它既然也叫二叉查找树,那想必会非常方便我们查找吧!它的操作并不是把中序遍历的结果存入数组,然后在有序数组里查找,而是直接在树上查找。

  1. 首先,访问根节点8
  2. 根据性质,7<8访问8的左子树
  3. 访问到了3,7>3,访问3的右子树
  4. 访问到了6,继续访问6的右子树
  5. 访问到了7,刚好找到啦!

显然,它的效率会比在无序数组中挨着查找快多了吧!我们直接上代码。

	//查找
	bool Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			//往左子树找
			if (key<cur->_key)
			{
				cur = cur->_left;
			}
			//往右子树找
			else if (key>cur->_key)
			{
				cur = cur->_right;
			}
			//找到了
			else
			{
				return true;	
			}
		}
		return false;	//没有找到
	}

🥎4.二叉排序树的删除(动图演示)

二叉搜索树的删除函数是最难实现的,若是在二叉树当中没有找到待删除结点,则直接返回 false 表示删除失败即可,但若是找到了待删除结点,此时就有以下三种情况:

  • 待删除结点的左子树为空(因为放第一个,所以包含左子树和无子树)
  • 待删除结点的右子树为空
  • 待删除结点的左右子树均不为空
    下面进行一对一处理:

情况一:左子树为空

(1)待删除结点的左子树为空,即右子树不为空,或者不为空

  • 待删除结点的左子树为空(因为放第一个,所以包含左子树和无子树)
  • 若待删除结点的左子树为空,那么当我们在二叉搜索树当中找到该结点后,只需先让其父结点指向该结点的右孩子结点,然后再将该结点释放便完成了该结点的删除,进行删除操作后仍保持二叉搜索树的特性

注意:如果删除的节点为叶子节点,即待删除节点左右子树均为空,这个情况包含在这种情况里面

动图演示(演示其中一种情况):删除10
在这里插入图片描述

  • 待删除结点不是根结点,此时parent不为nullptr
  • 待删除结点是其父结点的左孩子,父结点的左指针指向待删除结点的右子树即可
  • 反之,父结点的右指针指向待删除结点的右子树即可

下面只是删除部分的代码哦,看下面完整代码

//(1)删除节点只有0-1个孩子的时候(只有右孩子或者没有孩子)
//  这里包括了  cur->_left=nullptr&&cur->_right=nullptr的情况,就是没有孩子的情况
if (cur->_left == nullptr)
{
	//删除的是根节点
	//parent==nullptr;
	if (cur == _root)
	{
		_root = cur->_right;
	}
	else
	{
		//cur是parent的左子树,把cur的右子树托孤给parent的左/右子树
		if (parent->_left == cur)
		{
			parent->_left = cur->_right;
		}
		else
		{
			parent->_right = cur->_right;
		}
		
	}
	delete cur;		//删除节点
	return true;
}

情况二:右子树为空

(2)待删除结点的右子树为空,即左子树不为空

  • 待删除结点的右子树为空,那么当我们在二叉搜索树当中找到该结点后
  • 只需先让其父结点指向该结点的左孩子结点,然后再将该结点释放便完成了该结点的删除,进行删除操作后仍需要保持二叉搜索树的特性

动图演示(演示其中一种情况):删除14
在这里插入图片描述

  • 待删除结点不是根结点,此时parent不为nullptr
  • 待删除结点是其父结点的右孩子,父结点的左指针指向待删除结点的左子树即可
  • 反之,父结点右指针指向待删除结点的左子树即可

下面只是删除部分的代码哦,看下面完整代码

//(2) 删除节点只有左孩子
else if (cur->_right == nullptr)
{
	//删除的是根节点
	//parent==nullptr;
	if (cur == _root)
	{
		_root = cur->_right;
	}
	else
	{
		//cur是parent的左子树,把cur的左子树托孤给parent的左/右子树
		if (parent->_left == cur)
		{
			parent->_left = cur->_left;
		}
		else
		{
			parent->_right = cur->_left;
		}

	}
	delete cur;		//删除节点
	return true;
}

情况三:两个左右子树都不为空

(3)待删除结点的左右子树均不为空

若待删除结点的左右子树均不为空,那么当我们在二叉搜索树当中找到该结点后,可以使用替换法进行删除

有两种替换方法

(1)删除结点左子树当中值最大的结点
(2)删除结点右子树当中值最小的结点

关于两种替换方法
替换方法(1):要删的结点(8)的左子树的最大结点
那就是用7来顶替位置

此时7从叶子结点“升迁”到了根节点(只是刚好要删除的结点为根节点,如果删除3,就替换3的位置)

替换方法(2):要删的结点(8)的右子树的最小结点
那就是用10来顶替位置


下面演示我们用删除结点右子树当中值最小的结点来替换。

动图演示(演示其中一种情况):删除3

显然删除结点右子树当中值最小的结点为4

  • 怎么找到(删除结点右子树当中值最小的结点)
    • 1.用minParent记录minRight的父节点
    • 2.minRight从删除节点的右子树开始
    • 3.一直往左找
//这里找右子树的最小节点替换cur
Node* minParent = cur;
Node* minRight = cur->_right;//从右子树开始找
while (minRight->_left)		//当然一直往左找
{
	minParent = minRight;
	minRight = minRight->_left;
}

下面只是删除部分的代码哦,看下面完整代码

//(3)删除节点左右孩子都有
//替换法,然后删除(找右子树的最小节点或者找到左子树的最大节点)
else
{
	//这里找右子树的最小节点替换cur
	Node* minParent = cur;
	Node* minRight = cur->_right;//从右子树开始找
	while (minRight->_left)		//当然一直往左找
	{
		minParent = minRight;
		minRight = minRight->_left;
	}
	//开始替换
	//1.替换值
	cur->_key = minRight->_key;
	//2.连接minParent和minRight(把minRight右边连上就OK)
	//minRight是minParent的左节点
	if (minRight == minParent->_left)
		minParent->_left = minRight->_right;
	//minRight是minParent的右节点
	else
		minParent->_right = minRight->_right;
	//3.删除节点
	delete minRight;
	return true;
}

删除代码

//删除
bool Erase(const K& key)
{
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		//往左走
		if (key < cur->_key)
		{
			parent = cur;
			cur = cur->_left;
		}
		//往右走
		else if(key>cur->_key)
		{
			parent = cur;
			cur = cur->_right;
		}
		//找到了,开删
		else
		{
			//(1)删除节点只有0-1个孩子的时候(只有右孩子或者没有孩子)
			//  这里包括了  cur->_left=nullptr&&cur->_right=nullptr的情况,就是没有孩子的情况
			if (cur->_left == nullptr)
			{
				//删除的是根节点
				//parent==nullptr;
				if (cur == _root)
				{
					_root = cur->_right;
				}
				else
				{
					//cur是parent的左子树,把cur的右子树托孤给parent的左/右子树
					if (parent->_left == cur)
					{
						parent->_left = cur->_right;
					}
					else
					{
						parent->_right = cur->_right;
					}
					
				}
				delete cur;		//删除节点
				return true;
			}
			//(2) 删除节点只有左孩子
			else if (cur->_right == nullptr)
			{
				//删除的是根节点
				//parent==nullptr;
				if (cur == _root)
				{
					_root = cur->_right;
				}
				else
				{
					//cur是parent的左子树,把cur的左子树托孤给parent的左/右子树
					if (parent->_left == cur)
					{
						parent->_left = cur->_left;
					}
					else
					{
						parent->_right = cur->_left;
					}

				}
				delete cur;		//删除节点
				return true;
			}
			//(3)删除节点左右孩子都有
			//替换法,然后删除(找右子树的最小节点或者找到左子树的最大节点)
			else
			{
				//这里找右子树的最小节点替换cur
				Node* minParent = cur;
				Node* minRight = cur->_right;//从右子树开始找
				while (minRight->_left)		//当然一直往左找
				{
					minParent = minRight;
					minRight = minRight->_left;
				}
				//开始替换
				//1.替换值
				cur->_key = minRight->_key;
				//2.连接minParent和minRight(把minRight右边连上就OK)
				//minRight是minParent的左节点
				if (minRight == minParent->_left)
					minParent->_left = minRight->_right;
				//minRight是minParent的右节点
				else
					minParent->_right = minRight->_right;
				//3.删除节点
				delete minRight;
				return true;
			}
		}

	}
	return false;
}

🎱5.完整代码(非递归)

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
//描述二分查找树的一个结点
template<class K>
struct BSTNode
{
	K _key;				//数据域
	struct BSTNode* _left;		//指向左子树指针
	struct BSTNode* _right;		//指向右子树指针
	BSTNode(K key)
		:_key(key)
		,_left(nullptr)
		,_right(nullptr)
	{}
};
template<class K>
class BSTree
{
	typedef BSTNode<K> Node;
private:
	Node* _root;
	void Destory(Node* root)
	{
		if (root == nullptr) return;
		Destory(root->_left);
		Destory(root->_right);
		delete(root);
	}
	//拷贝函数(递归)
	Node* Copy(Node* root)
	{
		if (_root == nullptr)
		{
			return nullptr;
		}
		Node* newRoot = new Node(root->_key);
		newRoot->_left = Copy(root->_left);
		newRoot->_right = Copy(root->_right);
		return newRoot;
	}
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_key << ":" << root->_key<< endl;
		_InOrder(root->_right);
	}
public:
	//构造函数
	BSTree() {};
	//拷贝构造
	BSTree(const BSTree<K>& t) 
	{
		_root = Copy(t._root);
	}
	//析构
	~BSTree()
	{
		Destory(_root);		//调用销毁函数
		_root = nullptr;
	}
	//插入函数
	bool Insert(const K key)
	{
		Node* newnode = new Node(key);
		//空树时
		if (_root == NULL)
		{
			_root = newnode;
			return true;
		}
		Node* cur = _root;			//用来遍历
		Node* parent = nullptr;		//记录上一个节点
		while (cur)
		{
			parent = cur;
			//key< 结点值,遍历其左子树
			if (key < cur->_key)
			{
				cur = cur->_left;
			}
			//key> 结点值,遍历其右子树
			else if (key > cur->_key)
			{
				cur = cur->_right;
			}
			//不能插入相同的
			else
			{
				return false;
			}
		}
		if (key < parent->_key)
		{
			parent->_left = newnode;
		}
		if (key > parent->_key)
		{
			parent->_right = newnode;
		}
		return true;
	}
	//查找
	bool Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			//往左子树找
			if (key<cur->_key)
			{
				cur = cur->_left;
			}
			//往右子树找
			else if (key>cur->_key)
			{
				cur = cur->_right;
			}
			//找到了
			else
			{
				return true;	
			}
		}
		return false;	//没有找到
	}
	//删除
	bool Erase(const K& key)
	{
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			//往左走
			if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			//往右走
			else if(key>cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			//找到了,开删
			else
			{
				//(1)删除节点只有0-1个孩子的时候(只有右孩子或者没有孩子)
				//  这里包括了  cur->_left=nullptr&&cur->_right=nullptr的情况,就是没有孩子的情况
				if (cur->_left == nullptr)
				{
					//删除的是根节点
					//parent==nullptr;
					if (cur == _root)
					{
						_root = cur->_right;
					}
					else
					{
						//cur是parent的左子树,把cur的右子树托孤给parent的左/右子树
						if (parent->_left == cur)
						{
							parent->_left = cur->_right;
						}
						else
						{
							parent->_right = cur->_right;
						}
						
					}
					delete cur;		//删除节点
					return true;
				}
				//(2) 删除节点只有左孩子
				else if (cur->_right == nullptr)
				{
					//删除的是根节点
					//parent==nullptr;
					if (cur == _root)
					{
						_root = cur->_right;
					}
					else
					{
						//cur是parent的左子树,把cur的左子树托孤给parent的左/右子树
						if (parent->_left == cur)
						{
							parent->_left = cur->_left;
						}
						else
						{
							parent->_right = cur->_left;
						}

					}
					delete cur;		//删除节点
					return true;
				}
				//(3)删除节点左右孩子都有
				//替换法,然后删除(找右子树的最小节点或者找到左子树的最大节点)
				else
				{
					//这里找右子树的最小节点替换cur
					Node* minParent = cur;
					Node* minRight = cur->_right;//从右子树开始找
					while (minRight->_left)		//当然一直往左找
					{
						minParent = minRight;
						minRight = minRight->_left;
					}
					//开始替换
					//1.替换值
					cur->_key = minRight->_key;
					//2.连接minParent和minRight(把minRight右边连上就OK)
					//minRight是minParent的左节点
					if (minRight == minParent->_left)
						minParent->_left = minRight->_right;
					//minRight是minParent的右节点
					else
						minParent->_right = minRight->_right;
					//3.删除节点
					delete minRight;
					return true;
				}
			}

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


};


int main()
{
	BSTree<int> bst;
	//插入
	int arr[] = { 8,3,10,1,6,14,4,7,13 };
	int n = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < n; i++)
	{
		bst.Insert(arr[i]);
	}
	//中序遍历
	bst.InOrder();
	//查找
	//int p = 0;
	//cout << "输入要找的数:";
	//cin >> p;	
	//if (bst.Find(p))
	//{
	//	cout << "找到了" << endl;
	//}
	//else
	//{
	//	cout << "没有找到" << endl;
	//}
	//删除
	//1.测试没有孩子
	//bst.Erase(10);
	//2.测试一个孩子
	//bst.Erase(14);
	//3.测试有两个孩子
	//bst.Erase(3);
	bst.InOrder();
	return 0;
}

递归版本(扩展)

递归版本理解起来就相对与非递归版本更好理解了,直接看代码

(1)查找:

bool FindR(const K& key)
{  
	return _FindR(_root, key);
}
   
bool _FindR(Node* root, const K& key)
{
	if (root == nullptr)
		return false;
	if (root->_key < key)
	{
		return _FindR(root->_right, key);
	}
	else if (root->_key > key)
	{
		return _FindR(root->_left, key);
	}
	else
	{
		return true;
	}
}

逐层递归查找即可…

(2)插入:(重点)

bool InsertR(const K& key)
{
	return _InsertR(_root, key);
}

bool _InsertR(Node*& root, const K& key)
{
	//没有父指针,胜似父指针
	if (root == nullptr)
	{
		root = new Node(key);
		return true;
	}

	if (root->_key < key)
	{
		return _InsertR(root->_right, key);
	}
	else if (root->_key > key)
	{
		return _InsertR(root->_left, key);
	}
	else
	{
		return false;
	}
}

该如何链接上树呢?

  • 可以在递归的参数中多一个父亲结点,每次递归都更新一下Parent,然后再带到下一层递归
  • 显然这样在学过C++之后就麻烦了

用了一个指针的引用就解决了问题

  • 因为root的值此时是空,但是root同时是这个结点里的_left这个指针的别名
  • 相当于当前结点的父节点的左指针的别名
  • 意味着此时再去给root赋值就是去给该结点父亲结点的_left赋值
  • 那么此时就链接起来了

(3)删除:

bool EraseR(const K& key)
{
	return _EraseR(_root, key);
}

bool _EraseR(Node*& root, const K& key)
{
	//递归是用来找要删除的结点
	if (root == nullptr)
	{
		return false;
	}

	if (root->_key < key)
	{
		return _EraseR(root->_right, key);
	}
	else if (root->_key > key)
	{
		return _EraseR(root->_left, key);
	}
	else
	{
		Node* del = root;
		
		//root是要删除结点的左结点/右结点的别名

		if (root->_left == nullptr)
		{
			root = root->_right;
		}
		else if (root->_right == nullptr)
		{
			root = root->_left;
		}
		else
		{
			Node* minRight = root->_right;
			while (minRight->_left)
			{
				minRight = minRight->_left;
			}

			swap(root->_key, minRight->_key);

			return _EraseR(root->_right, key);
			//转换成在root->_right(右子树)中去删除key
			//这里删除这个key一定会走左为空的场景(找最小)
		}

		delete del;
		return true;
	}
}

相等时就开始删除了(递归只是用来查找要删除的数的位置)

  • root是要删除结点的左结点 / 右结点的别名

分三种情况删除:

  1. 要删除的结点左为空
  2. 要删除的结点右为空
  3. 要删除的结点左右都为空(替换法)

总的来说递归版本比非递归版本更容易理解,删除过程参考非递归删除过程……(有异曲同工之妙)

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

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

相关文章

【慕伏白教程】将 Windows11 装进口袋 -- 便携式 Windows 11 制作教程

目录 下载 Windows 11 镜像下载 Rufus开始安装 Windows 11 下载 Windows 11 镜像 打开微软 Windows 11 官方下载网站&#xff0c;找到 下载适用于 x64 设备的 Windows 11 磁盘映像 (ISO) 根据个人情况选择要下载的磁盘镜像&#xff0c;选择多版本 ISO 的话可在安装系统开始时进…

多IP连接

一.关闭防火墙 systemctl stop firewalld setenforce 0 二.挂在mnt mount /dev/sr0 /mnt 三.下载nginx dnf install nginx -y 四.启动nginx协议 systemctl start nginx 五.修改协议 vim /etc/nginx/nginx.conf 在root前加#并且下一行添加 root /www:&#xff08;浏…

基于图像拼接开题报告

选题的背景与意义 在日常生活中&#xff0c;使用普通相机获取宽视野的场景图像时&#xff0c;必须通过调节相机的焦距才可以提取完整的场景。由于相机的分辨率有限&#xff0c;拍摄场景越大&#xff0c;得到的图像分辨率就越低&#xff0c;因此只能通过缩放相机镜头减小拍摄的…

应对 .DevicData-X-XXXXXXXX 勒索病毒:防御与恢复策略

引言 随着信息技术的快速发展&#xff0c;网络安全问题愈发严峻。勒索病毒作为一种恶性网络攻击手段&#xff0c;已成为企业和个人面临的重大威胁之一。尤其是 .DevicData-X-XXXXXXXX 勒索病毒&#xff0c;其通过加密用户数据并勒索赎金&#xff0c;给受害者带来了巨大的经济损…

dolphinscheduler创建工作流及工作流中DataX的使用(简单操作)

一、在项目管理中创建项目&#xff1a;点击创建项目 用哪个用户登录的&#xff0c;所属用户就是哪个&#xff0c;直接输入项目名即可 二、点击项目&#xff0c;在项目中创建工作流&#xff0c;用DataX同步数据 按照图片的步骤依次填写完成&#xff0c;注意 图片中的第九步是写…

个税自然人扣缴客户端数据的备份与恢复(在那个文件夹)

一&#xff0c;软件能够正常打开&#xff0c;软件中的备份与恢复功能 1&#xff0c;备份 您按照下面的方法备份一下哦~ 进入要备份的自然人软件&#xff0c;点击左侧系统设置→→系统管理→→备份恢复&#xff1b; 在备份设置里&#xff0c;点击“备份到选择路径”&#xff0c;…

小白向的源码开发详解:直播带货系统与电商平台搭建指南

本篇文章&#xff0c;笔者将为小白们提供一份详细的源码开发指南&#xff0c;帮助你轻松搭建自己的直播带货系统和电商平台。 一、了解直播带货系统的基本构成 直播带货系统主要由以下几个部分组成&#xff1a; 1.前端界面 2.后端服务器 3.数据库 4.直播平台 二、技术选型…

【C++】— 一篇文章让你认识STL

文章目录 &#x1f335;1.什么是STL&#xff1f;&#x1f335;2.STL的版本&#x1f335;3.STL的六大组件&#x1f335;4.STL的重要性&#x1f335;5. 如何学习STL&#x1f335;6. 学习STL的三种境界 &#x1f335;1.什么是STL&#xff1f; STL是Standard Template Library的简称…

深入理解Redis锁与Backoff重试机制在Go中的实现

文章目录 流程图Redis锁的深入实现Backoff重试策略的深入探讨结合Redis锁与Backoff策略的高级应用具体实现结论 在构建分布式系统时&#xff0c;确保数据的一致性和操作的原子性是至关重要的。Redis锁作为一种高效且广泛使用的分布式锁机制&#xff0c;能够帮助我们在多进程或分…

Vue+ECharts+iView实现大数据可视化大屏模板

Vue数据可视化 三个大屏模板 样式还是比较全的 包括世界地图、中国地图、canvas转盘等 项目演示&#xff1a; 视频&#xff1a; vue大数据可视化大屏模板

神经网络模型内部

给大家展示一个三层4*24*24*2神经网络文件的内部&#xff1a; 大小5.06KB 想知道这个模型是怎么训练生成的看我的上一篇文章 用神经网络自动玩游戏

Centos7安装ZLMediaKit

https://github.com/ZLMediaKit/ZLMediaKit 一 获取代码 git clone https://gitee.com/xia-chu/ZLMediaKit cd ZLMediaKit git submodule update --init git submodule update --init 命令用于初始化和更新 Git 仓库中的子模块&#xff08;submodules&#xff09;。这个命令…

vue3 + ts + element-plus 二次封装 el-dialog

实现效果&#xff1a; 组件代码&#xff1a;注意 style 不能为 scoped <template><el-dialog class"my-dialog" v-model"isVisible" :show-close"false" :close-on-click-modal"false" :modal"false"modal-class&…

web网页QQ登录

代码&#xff1a; <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>QQ登录ent</title> </head> <style>ul > li{list-style: none; } a …

U盘数据丢失不用慌,这4个工具可以帮你恢复。

因为将大量的数据存到U盘里面很方便&#xff0c;所以U盘使用也很广泛。但是里面的数据丢失想必很多朋友都碰到过&#xff0c;不过现在有很多方法都可以帮助大家将数据回顾回来。这里我便筛选了几款比较好的数据恢复工具&#xff0c;在这里跟大家分享。 1、福昕U盘恢复软件 直通…

AI练中学,你的 AI 助教又升级啦!

你是否在代码学习过程中遇到过这些问题&#xff1f;理论学习和动手实践割裂&#xff1b;课上的示例代码跑起来很麻烦&#xff1b;需要自己配置开发环境&#xff0c;在服务器上配开发环境要付费&#xff0c;折腾半天之后报错。 在大模型应用开发领域&#xff0c;获取大模型 API…

设计模式:类与类之间关系的表示方式(聚合,组合,依赖,继承,实现)

目录 聚合关系 组合关系 依赖关系 继承关系 实现关系 聚合关系 聚合是一种较弱的“拥有”关系&#xff0c;表示整体与部分的关系&#xff0c;但部分可以独立于整体存在。例如&#xff0c;部门和员工之间的关系&#xff0c;一个部门可以包含多个员工&#xff0c;但员工可以…

MFC工控项目实例二十五多媒体定时计时器

承接专栏《MFC工控项目实例二十四模拟量校正值输入》 用多媒体定时器实现0.1秒计时器 1、在SEAL_PRESSUREDlg.h文件中添加代码 #include<MMSystem.h> #pragma comment(lib,"winmm.lib")class CSEAL_PRESSUREDlg : public CDialog { public:CSEAL_PRESSUREDlg(…

计算机网络基础进阶

三次握手四次挥手 三次握手 1------建立连接----------------------2 ACK1&#xff0c;seq0 2------传输数据&#xff0c;建立连接---------1 1------传输数据&#xff0c;建立连接---------2 三次握手用于建立TCP连接&#xff0c;确保通信双方都准备好进行数据传输。整个…

使用 Git LFS(大文件存储)

Git LFS&#xff08;Large File Storage&#xff09;是一种扩展 Git 的工具&#xff0c;旨在更有效地管理大文件的版本控制。它通过将大文件的内容存储在 Git 之外来解决 Git 在处理大文件时的性能问题。 主要特点 替代存储&#xff1a;Git LFS 不直接将大文件存储在 Git 仓库…