【Cpp】手撕搜索二叉树(K模型)

news2025/1/25 4:39:09

文章目录

  • 二叉搜索树概念详解
    • 二叉搜索树的概念
    • 二叉搜索树的操作(大致思路)
      • 二叉搜索树的查找
      • 二叉搜索树的插入
      • 二叉搜索树的删除(最重点)
  • 手撕搜索二叉树代码
    • 结点定义(以key型为例,KV型将在下一篇博客中介绍)
    • 树结构定义
      • 深拷贝构造函数与构造函数
      • 赋值重载
      • 析构函数
      • 遍历(结果按从小到大遍历->中序遍历)
      • 增删查改
        • 增加结点(插入)->循环版本
        • 增加结点(插入)->递归版本
        • 查找结点->循环版本
        • 查找结点->递归版本
        • 删除结点->循环版本
        • 删除结点->递归版本
  • 搜索二叉树代码完全版
  • 总结

二叉搜索树概念详解

二叉搜索树的概念

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

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

二叉搜索树的操作(大致思路)

在这里插入图片描述

int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};

二叉搜索树的查找

a、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
b、最多查找高度次,走到到空,还没找到,这个值不存在。

二叉搜索树的插入

插入的具体过程如下:
a. 树为空,则直接新增节点,赋值给root指针
b. 树不空,按二叉搜索树性质查找插入位置,插入新节点
在这里插入图片描述

二叉搜索树的删除(最重点)

首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:
a. 要删除的结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点

看起来有待删除节点有4种情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下:
情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点–直接删除
情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点–直接删除
情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点
中,再来处理该结点的删除问题–替换法删除

形象地说,情况b\c,我们就采取"托孤"的政策,将要删除的那个节点的一个孩子交给自己的父亲带(即爷爷带孙子),但是如果要删除那个节点有两个孩子(左右节点)的话,"爷爷"就忙不过来了,此时,我们需要换一种策略–>请保姆.
那么怎么找到这个保姆呢?首先我们要明确的一点是,在我们对搜索二叉树进行操作的时候,我们需要时刻保证搜索二叉树的性质是成立的–即跟比左子树大比右子树小.
我们是需要找到这个"保姆"将我们需要删除的那个节点交换,为了保持二叉搜索树的性质,我们就需要在他的子树中找到满足这种性质的节点来替换:即左子树中的最大节点(替换后肯定比原左子树中的每一个数都大,但是又比原右子树中的每一个树都小,完美符合性质),同样的,我们利用对称性原理不难发现,右子树的最小节点也是满足这个性质的
综上,我们只需要找到左子树的最右节点或者右子树的最左节点作为"保姆"替换掉要删除的目标节点即可.

手撕搜索二叉树代码

结点定义(以key型为例,KV型将在下一篇博客中介绍)

template<class k>
struct BSTreeNode
{
	BSTreeNode<k>* left;//定义左子树
	BSTreeNode<k>* right;//定义右子树
	k _key;//存储关键码

	BSTreeNode(const k& key)//构造函数
		:_key(key)
		,_left(nullptr)
		,_right(nullptr)
	{}
}

树结构定义

上面我们已经把每个结点的结构定义出来了
这里我们需要把结点管理起来:

template<class k>
	class BSTree
	{
		typedef BSTreeNode<k> Node;//将结点名字重新命名,方便后续写代码
	public:
		BSTree() = default; 
		BSTree(const BSTree<k>& t){}
		BSTree<k>& operator=(BSTree<k> t){}

		bool Insert(const k& key);
		bool Erase(const k& key);
		void InOrder();

	protected:
		Node* Copy(Node* root){}
		void Destroy(Node*& root{}
		void _InOrder(Node* root){}
	private:
		Node* _root = nullptr;
	};	

深拷贝构造函数与构造函数

编译器会自动给我们生成一个拷贝构造函数出来,但是这是浅拷贝,不是深拷贝.
因此,我们先写一个(深)拷贝构造出来,构造方式其实就类似于遍历,要使用递归的方法
一般来说,对于类中的函数递归,需要我们封装一下,才能将头节点传入
所以我们需要在protect域中写一个copy函数,然后再把构造函数接口开放给外面.
这里需要注意的是,如果我们写了拷贝构造而没有写默认构造(此时编译器已经不再会为我们自动创建构造函数了),编译会报错
有两种解决方式:

  1. 手写一个默认构造函数
  2. 使用default关键字(我们下面采用这种方式)

我们在下面的代码都进行了演示:

template<class k>
struct BSTree
{
	typedef BSTreeNode<k> Node; 
public:
	/*BSTree()
			:_root(nullptr)
			{}*/

	BSTree() = default; // 制定强制生成默认构造
	
	BSTree(const BSTree<k>& t)//拷贝构造封装
	{
		_root = Copy(t->_root);//利用this指针,得到拷贝后的树
	}
protect:
	Node* Copy(Node* root)
	{
		if(Node* == nullptr)//如果递归到了空指针,则说明到底了
		{
			return nullptr;
		}
		//创建一个新的结点用来接收root的key:
		Node* newRoot = new Node(root->key);
		newRoot->_left = Copy(root->_left);
		newRoot->_right = Copy(root->_right);
		//先创建,后链接
		return newRoot;//返回此时的跟结点,会与上一层的根节点链接.
	}
private:
	Node* _root = nullptr;
	void Destroy(Node*& root)
		{
			if (root == nullptr)
				return;

			Destroy(root->_left);
			Destroy(root->_right);

			delete root;
			root = nullptr;
		}
}

赋值重载

既然都已经写了(深)拷贝构造了,那就直接把赋值重载也写了
这里我们直接采用更方便的"现代写法",即利用语法的机制,直接返回值:

BSTree<k>& operator=(BSTree<k> t)//传参进来的时候会拷贝构造,我们此时
{
	swap(_root, t._root);//这里的t是已经拷贝构造好的节点了,我们只需要交换他俩的值
	//就能直接窃取劳动成果了
	return *this;
}

析构函数

由于这里是树形结构,需要使用递归删除,而递归删除在类中一般都需要我们封装一层,才能将起始点传进去.

~BSTree(){
	Destroy(_root);
	//_root = nullptr;
}
protect:
void Destroy(Node*& root)//如果我们这里直接传引用的话,就可以省略上面的那句注释中的代码
		{
			if (root == nullptr)
				return;

			Destroy(root->_left);//先删除左子树
			Destroy(root->_right);//再删除右子树

			delete root;//最后删除根节点
			root = nullptr;//将根节点置空
		}
	

遍历(结果按从小到大遍历->中序遍历)

一样的,需要我们封装一层

public:
		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}
protect:
		void _InOrder(Node* root)
		{
			if(root == nullptr)//设置递归的终止条件.
			{
				return;
			}
			_InOrder(root->left);
			cout << root->key << " ";
			_InOrder(root->right);
		}	

增删查改

增加结点(插入)->循环版本

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;//如果是插入树中已经有的key值,则插入失败
				}
			}

			cur = new Node(key);
			// 再链接(首先要判断是父节点的左子树还是右子树)
			if (parent->_key < key)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}

			return true;//走到这里,则已经插入成功
		}

增加结点(插入)->递归版本

public:
bool InsertR(const k& key)
		{
			return _InsertR(_root, key);
		}
protect:
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;
			}
		}

查找结点->循环版本

bool Find(const K& key)//该函数只需要返回是否能够找到即可,因此是bool类型的返回值
		{
			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;
		}

查找结点->递归版本

public:
bool FindR(const k& key)
		{
			return _FindR(_root, key);
		}
protect:
bool _FindR(Node* root, const k& key)
		{
			if (root == nullptr)
				return false;

			if (root->_key == key)
				return true;

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

删除结点->循环版本

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)//因为外面设置了parent,如果cur是跟节点的话,parent还是null,会发生错误,所以这里需要单独判断一下
						{
							_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;
					}
					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;
		}

删除结点->递归版本

private:
bool EraseR(const k& key)
		{
			return _EraseR(_root, key);
		}
protect:
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;

				// 开始准备删除
				if (root->_right == nullptr)
				{
					root = root->_left;//这里的root是别名,因此可以直接这样用,不用找父节点
				}
				else if (root->_left == nullptr)
				{
					root = root->_right;
				}
				else
				{
					Node* maxleft = root->_left;
					while (maxleft->_right)
					{
						maxleft = maxleft->_right;
					}

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

					return _EraseR(root->_left, key);//交换了以后继续递归删除即可
				}

				delete del;

				return true;//走到这里说明已经删除完毕了
			}
		}

搜索二叉树代码完全版

#pragma once
#include<iostream>
using namespace std;

// BinarySearchTree -- BSTree

namespace key
{
	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:
		/*BSTree()
			:_root(nullptr)
			{}*/

		BSTree() = default; // 制定强制生成默认构造

		BSTree(const BSTree<K>& t)
		{
			_root = Copy(t._root);
		}

		BSTree<K>& operator=(BSTree<K> t)
		{
			swap(_root, t._root);
			return *this;
		}

		~BSTree()
		{
			Destroy(_root);
			//_root = nullptr;
		}

		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;
		}

		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;
		}

		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;
					}
					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;
		}

		bool FindR(const K& key)
		{
			return _FindR(_root, key);
		}

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

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

		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}

	protected:
		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 Destroy(Node*& root)
		{
			if (root == nullptr)
				return;

			Destroy(root->_left);
			Destroy(root->_right);

			delete root;
			root = nullptr;
		}

		bool _FindR(Node* root, const K& key)
		{
			if (root == nullptr)
				return false;

			if (root->_key == key)
				return true;

			if (root->_key < key)
				return _FindR(root->_right, key);
			else
				return _FindR(root->_left, 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;
			}
		}

		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;

				// 开始准备删除
				if (root->_right == nullptr)
				{
					root = root->_left;
				}
				else if (root->_left == nullptr)
				{
					root = root->_right;
				}
				else
				{
					Node* maxleft = root->_left;
					while (maxleft->_right)
					{
						maxleft = maxleft->_right;
					}

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

					return _EraseR(root->_left, key);
				}

				delete del;

				return true;
			}
		}

		void _InOrder(Node* root)
		{
			if (root == nullptr)
				return;

			_InOrder(root->_left);
			cout << root->_key << " ";
			_InOrder(root->_right);
		}
	private:
		Node* _root = nullptr;
	};
}

总结

这篇文章写的都是搜索二叉树的K模型.
关于KV模型的代码手撕将在下一篇文章中,到时候查看我所写的博客即可.
希望大家学习了本篇博客后能够根据目录结构将代码全部重写一遍,只有自己写出来了才是真正的学会了.

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

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

相关文章

软件测试的当下分析

在没有清晰能见度的情况下驾驶汽车不仅非常危险&#xff0c;也十分鲁莽。这会让我们和我们周边的人随时面临着碰撞、受伤、甚至死亡的风险。如果不能看到前方的道路&#xff0c;我们就无法预测潜在的危险或障碍&#xff0c;从而无法做出明智的决定并采取适当的行动。 同样&…

什么是ddos攻击?ddos攻击有哪些危害?

一、什么是 DDoS 攻击&#xff1f; DDoS 是 Distributed Denial of Service 的缩写&#xff0c;翻译成中文就是 “分布式拒绝服务”。DDoS 攻击将处于不同位置的多个计算机联合起来作为攻击平台&#xff0c;对一个和多个目标发动 DDoS 攻击&#xff0c;从而成倍提高攻击威力。…

分布式系统概念和设计-进程通信中的(网络API设计)

分布式系统概念和设计 进程间通信 中间件层 请求-应答协议 编码和外部数据表示 因特网协议的API 进程间通信的特征 一对进程间进行消息传递需要两个消息通信操作的支持&#xff08;send和receive&#xff09;&#xff0c;用于定义目的地和消息的定义。 为了能够使一个进程能…

煤化工废水除总氮除硬度,矿井水除砷除氟解决方案

随着环保标准的提升&#xff0c;大部分煤矿企业对矿井水要求执行地表三类水标准&#xff0c;氟化物要求小于1mg/l&#xff0c;这类项目存在体量大、氟含量低、水质偏差等特点。 RO工艺制备纯水是煤化工行业生产的一个辅助环节&#xff0c;会产生大量的浓盐水&#xff0c;由于浓…

十五分钟带你学会 Electron

文章目录 什么是 Electron为什么要选择 Electron安装 Electron桌面CSDN实战Electron 基础配置Electron 进程主进程渲染进程主进程与渲染进程的区别主进程与渲染进程的通信 Electron 跨平台问题Electron 部署打包应用程序发布应用程序 Electron 跨端原理总结 什么是 Electron E…

NE555 Motor LED Chaser

文章目录 1.前言2.资料下载 1.前言 这个是从YouTube上搬运来的&#xff0c;如图所示 2.资料下载 所需材料 #1# 10k resistor 1 #2# 10k variable resistor 1 #3# 10uf capacitor 1 #4# 3mm blue led 4 #5# 3mm yellow led 4 #6# 3mm red led 4 #7# 3mm green led 4 #8# 3mm…

【Linux网络】网络基础(TCP/IP协议栈、局域网通信原理、封装与解包、有效载荷分用)

文章目录 1、认识网络1.1 重新看待计算机结构1.2 网络的问题1.3 初识网络协议1.4 TCP/IP五层结构 2、网络与操作系统2.1 网络和OS的关系2.2 局域网&#xff08;以太网&#xff09;通信原理和MAC地址2.3 主机的跨网络2.4 有效载荷的分用 1、认识网络 在早年计算机之间是相互独立…

关于自身存在的严重问题总结_4/19

今早二次面试喜马拉雅&#xff0c;面试官给我的评价是&#xff1a; 1.经验不足&#xff1b; 2.实用方面生疏、理解不到位&#xff1b; 原因很正常&#xff0c;我项目自己亲手实操的太少了&#xff0c;一直在背&#xff0c;但是背 不是去读源码 去理解&#xff1b; 项目也大…

基于springboot的班级综合测评管理系统源码数据库论文

目录 1 绪论 1.1课题研究的背景 1.2 课题研究的内容 1.3 系统开发的意义 1.4初步设计方法与实施方案 1.5 本文研究内容 2相关技术介绍 2.1 Java技术 2.2B/S架构 2.3 MySQL介绍 2.4 Springboot框架 3系统需求分析 3.1 可行性分析 3.1.1 经济可行性分…

AI大模型在各行业肆虐,打工人该如何保住自己的饭碗?

开篇我先下个结论&#xff0c;那就是&#xff1a;人类在科技领域的高效率竞争&#xff0c;正在把我们生活的这个商业世界一步步地数字化。而数字化&#xff0c;不单单是AI智能的发展成果&#xff0c;更会成为它所热衷的生长温床&#xff0c;为后续人工智能的一路狂飙奠定了绝佳…

2、picodet转onnx裁剪及python onnxruntime推理

文章目录 1 对picodet xs1.1 动态图转静态图1.2 静态图转onnx1.3 paddle 含后处理 all 版本的推理1.4 onnx 含后处理 all 进行推理1.5 onnx 不含后处量 base模型推理1.5.1 获取onnx模型任一节点的输出1.5.2 base模型的推理 1.6、对picodet-xs模型进行优化1.6.1 picodet-xs base…

项目文档规范及总体布局

软件文档可以分为开发文档和产品文档两大类&#xff0c;交付用户还有用户文档。 1|1开发文档 软件开发计划需求规格说明书软件概要设计说明数据库设计说明软件详细设计说明可执行程序生成说明软件测试计划软件测试说明软件测试报告安装部署手册源代码交付说明上线部署方案上线…

spark读写时序数据库 TDengine 错误总结

最近在用spark读取、写入TDengine 数据库遇到了这样一个问题&#xff1a; JDBCDriver找不到动态链接库&#xff08;no taos in java.library.path&#xff09; 我本地都好好的&#xff0c;但是一上服务器写入就会报这个错误&#xff0c;看了很久没有排查出问题&#xff0c;后…

图像分割领域的GPT-4.0,分割一切的AI算法:Segment Anything

一、图像分割领域的GPT-4.0 大家好&#xff0c;我是千与千寻&#xff0c;今天给大家介绍的AI算法可以称得上是图像分割领域的GPT-4.0&#xff0c;号称可以分割一切的AI图像分割算法——Segment Anything。 提到GPT-4.0模型&#xff0c;相信不必我多说&#xff0c;大家都不会陌生…

C++笔记——第十三篇 种一颗 AVL树,长大变成 红黑树,开出了 map和set

目录 一、引入 1. 关联式容器 二、键值对 三、树形结构的关联式容器 3.1 set 3.1.1 set的介绍 3.1.2 set的使用 3.2 map3.2.1 map的介绍 3.2.2 map的使用 3.3 multiset 3.3.1 multiset的介绍 3.4 multimap 3.4.1 multimap的介绍 四、底层结构 4.1 AVL 树 4.1.1 AVL树的概念…

实验进行套路【1】

实验是用来证明猜想正确与否的关键方法&#xff0c;做好实验对提升论文发表效率至关重要。本篇博客结合自身经历总结做实验的方法论&#xff0c;希望能对读者有用。本篇不会从技术实现角度来介绍做实验的方法&#xff0c;而会从指导思想角度入手。如果你是一个科研工作者的话&a…

AIPRM for ChatGPT插件让ChatGPT如虎添翼

ChatGPT大热&#xff0c;家人们都申请到了账号&#xff0c;可是总是在一问一答的基础上来完成基本的应用&#xff0c;而不能很好的使用「咒语」&#xff08;Prompt&#xff09;&#xff0c;收获的答案不是通过很多次的反复问获取答案&#xff0c;就是获取的不是想要的答案。所以…

最重要的 JVM 参数总结

1.概述 在本篇文章中&#xff0c;你将掌握最常用的 JVM 参数配置。 2.堆内存相关 Java 虚拟机所管理的内存中最大的一块&#xff0c;Java 堆是所有线程共享的一块内存区域&#xff0c;在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例&#xff0c;几乎所有的对象实…

Auto-GPT来啦,手把手教你安装更稳定的stable的Auto-GPT,实现两个AutoGPT合作执行任务,AutoGPT代理同时执行任务

进入Auto-GPT项目 https://github.com/Significant-Gravitas/Auto-GPT 应该在git bash还是git cmd命令窗口敲以上命令 应该在git bash窗口中敲git命令。 Git Bash是Windows上的一个搭载了Git的终端仿真器&#xff0c;提供了类似Unix的命令行环境&#xff0c;使得在Windows上…

深入浅出剖析JAVA多线程原理

1. 线程基础知识 1.1 线程与进程 1.1.1 进程 ●程序由指令和数据组成&#xff0c;但这些指令要运行&#xff0c;数据要读写&#xff0c;就必须将指令加载至 CPU&#xff0c;数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理…