深入篇【C++】手搓模拟实现二叉搜索树(递归/非递归版本)常见应用场景(K模型与KV模型)

深入篇【C++】手搓模拟实现二叉搜索树(递归/非递归版本)&&常见应用场景

  • Ⅰ.二叉搜索树概念
  • Ⅱ.二叉搜索树模拟实现(递归与非递归)
      • ①.定义结点
      • ②.构造二叉树
      • ③.插入结点
      • ④.删除结点(重要)
      • ⑤.查找结点
      • ⑥.析构二叉树
      • ⑦.拷贝二叉树
      • ⑧.二叉树赋值
  • Ⅲ.二叉搜索树应用
      • ①.K模型与KV模型

Ⅰ.二叉搜索树概念

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

1.当它的左子树不为空,则左子树上所有的结点的值都要小于根节点。
2.当它的右子树不为空,则右子树上所有的结点的值都要大于根结点。
3.它的左右子树都是二叉搜索树。

在这里插入图片描述

Ⅱ.二叉搜索树模拟实现(递归与非递归)

①.定义结点

二叉树的结点:含有左右指针和数据的结点。

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>
	struct BSTree
	{
		typedef BSTreeNode<K> Node;

		BSTree()//构造函数,初始化
			:_root(nullptr)
		{}
	private:
	Node* _root;//封装一个指向结点的指针	
     }

③.插入结点

1.非递归版本:
插入结点的方法很简单,当这颗树为空树时,直接开辟出一个结点给它。当这颗树不为空时,则按照二叉搜索树的特性来比较,将插入结点插入到正确位置。

1.当插入的结点值key要比根结点大,则key需要到根的右树进行比较,当key的值比根结点小,则key需要到根的左树进行比较,当key的值与根结点相同时,则返回fasle,按照这样的方式循环下去,当要比较的结点为空时,则就可以将结点插入到这个位置上了。每次比较中都要记录父节点的位置,因为最后需要链接起来。
2.最后链接起来需要和父结点比较一下才能知道链接到父节点的左边还是右边。如果大于父节点则链接到右边,如果小于父节点则连接到左边。

bool insert(const K& key)
		{
			if (_root == nullptr)
			{
				_root = new Node(key);
			}
			else
			{
				Node* cur = _root;
				Node* parent = nullptr;
				while (cur)
				{
					if (cur->_key < key)
					{
						parent = cur;
						cur = cur->right;
					}
					else if (cur->_key > key)
					{
						parent = cur;
						cur = cur->left;
					}
					else
					{
						return false;
					}
				}

				if (parent->_key < key)
				{
					parent->right = new Node(key);
				}
				if (parent->_key > key)
				{
					parent->left = new Node(key);
				}
			}
			return true;
		}

2.递归版本
我们要知道递归每次都要改变结点的位置,我们就必须要传结点(结点指针)作为参数,但在类外面,我们想要调用递归函数,也需要传结点指针了,但这里结点指针被封装起来不能访问,所以不能直接用一个递归函数就能完成,需要靠一个子函数来获取结点指针。而真正的递归函数就不需要手动传结点指针啦。

1.当根节点为空时,直接开辟出结点给它。
2.当根结点不为空时,就要将key与结点值进行比较,当key大于结点值时,就要转化为子问题,递归到右子树进行比较,当key小于结点值时,就递归到左子树进行比较,当key等于结点值时,就返回false。
3.当递归结束时,就可以将开辟好结点链接起来。(递归的过程就是不断的在创建结点,回来的过程就是不断地将结点链接起来)。
4.这里不需要像非递归那样,每次比较都需要记录父节点的位置,我们这里用一个引用就可以轻松解决问题!我们的指针参数使用引用,即子函数的参数是递归函数参数的别名。
这样做就有一个绝妙的关系:root结点是父节点的左指针或者右指针。直接可以将父节点和新开辟的结点链接起来。

      bool insertR(const K& key)//每次递归都需要要改变结点的状态,所以必须要传结点的指针过来,这里使用一个子函数
		{
			return _insertR(_root, key);
		}
		
      bool _insertR(Node*& root, const K& key)
		{
		//
			//root是父节点左子树或者右子树的别名

			if (root == nullptr)
			{
				root = new Node(key);//将父节点与新结点链接起来
			}

			if (root->_key < key)
			{
				return _insertR(root->right, key);
			}
			else if (root->_key > key)
			{
				return _insertR(root->left, key);
			}
			else
			{
				return false;
			}
			return true;

		}

④.删除结点(重要)

1.非递归版本
删除结点要比较复杂,因为存在多种情况,当删除的结点只有一个孩子时,当删除的结点没有孩子时,当删除的结点有两个孩子时,要分三种情况讨论。

1.当删除结点没有孩子时,使用托孤法。将父节点与空链接起来。
2.当删除结点只有一个孩子时,使用托孤法,将父节点与孤结点链接起来。
3.当删除结点有两个孩子时,使用找保姆法,找一个可以替代本身的结点。交换这两个结点,删除这个替换结点。
这个保姆结点可以是左子树的最大结点或者是右子树的最小结点。

要删除某个结点,首先需要找到这个结点,按照二叉搜索树的特性来比较查找,key大于结点值,到右树找,key小于结点值,到左树找,当key等于结点值时,就说明找到了。
而找到要删除的结点后,又要分三种情况来讨论,要删除的结点是属于哪种的,是没有孩子结点的?还是只有一个结点的?还是有两个孩子结点的?其实第一种和第二种是可以合并成一种的。
【当存在一个孩子结点时】
当右子树是空时,说明左子树是孤结点。需要将左子树托孤给父节点。(每次比较的时候需要记录父节点位置)。
而托孤给父节点也是有讲究的,因为不知道是将这个孤结点链接到父结点的左边还是右边,我们要注意,当删除结点是父结点的左孩子时,则这个删除结点的任何一个孩子结点都要小于删除结点的父节点,所以必须链接到父节点的左边。而当删除结点是父节点的右孩子时,删除结点的任何一个孩子结点都要大于删除结点的父节点,所以必须链接到父节点的右边。
所以我们根据当前删除结点是父节点的左子树还是右子树来决定将孤结点链接到父节点的哪边。
在这里插入图片描述
最后还有一种特殊情况比如情况1和情况2,当删除结点是8时,没有左子树或者右子树,但是父节点为空。
所以需要特殊讨论一下。

【当存在两个孩子结点时】
当存在两个孩子结点时,就需要使用保姆法。比如要删除的结点是根节点8,那么它的孩子有两个。我们首先需要找到一个可以替代它的结点,比如左子树的最大结点7,就可以替代它,将它们两个交换。
在这里插入图片描述
然后删除这个替代结点就可以啦。其实从这里我们就可以观察到,只要找到保姆后,再交换一下,那这个问题就变成要删除的结点只有一个孩子的问题了,因为这个替代结点必定没有右孩子(它是左子树的最大结点,即左子树的最右边).
然后就可以使用托孤法,将这个结点的左子树托孤给父节点即可。
在这里插入图片描述

bool erase(const K& key)
		{
			//首先需要找到要删除的结点,这个过程需要记录父节点
			Node* cur = _root;
			Node* parent = nullptr;//父节点一开始为空
			while (cur)
			{
				if (cur->_key < key)//特殊情况需要讨论一下

				{
					parent = cur;//记录父节点
					cur = cur->right;
				}
				else if (cur->_key > key)
				{
					parent = cur;//记录父节点
					cur = cur->left;
				}
				else//找到要删除的结点了--cur就是要删除的结点
				{
					//1.右子树为空,左子树为孤结点,需要托孤给父节点
					//特殊情况:当删除结点为根节点时

					if (cur->right == nullptr)					{
						if (cur == _root)
						{
							_root = cur->left;//直接往右挪动
						}
						else
						{
							if (parent->left == cur)//托孤
							{
								parent->left = cur->left;
							}
							else
							{
								parent->left = cur->left;
							}
						}

					}
					//2.左子树为空,右子树为孤结点,需要托孤给父节点
					else if (cur->left == nullptr)
					{
						if (cur == _root)//特殊情况需要讨论一下

						{
							_root = cur->right;
						}
						else
						{
							if (parent->left == cur)//托孤
							{
								parent->left = cur->right;
							}
							else
							{
								parent->right = cur->right;
							}
						}
					}
					//3.左右子树都存在,找保姆
					else
					{
						//保姆:当前要删除结点的最右结点
						Node* leftMax = cur->left;
						Node* parent = cur;
						//这里不能给nullptr
						while (leftMax->right)
						{
							parent = leftMax;
							leftMax = leftMax->right;
						}
						//找到保姆后交换
						std::swap(cur->_key, leftMax->_key);

						//转化为上面问题--因为最右结点的右结点肯定为空
						//那左节点就是孤结点,需要托孤
						if (parent->right == leftMax)
						{
							parent->right = leftMax->left;
						}
						if (parent->left == leftMax)
						{
							parent->left = leftMax->left;
						}

						cur = leftMax;
					}

					delete cur;//最后删除这个结点
					return true;
				}
			}
			return false;
		}

2.递归版本
递归版本就要比非递归版本简洁多了,只不过有点难理解。这里还是需要子函数来获取结点指针。
要删除某个结点,首先需要找到这个结点。

当key比根结点大的时候,递归到右子树进行比较,当key比根结点值小的时候,递归到左子树进行比较,当key跟结点值相同时,表明找到了。找到后就要分三种情况讨论。

【当存在一个孩子结点时】
不同于非递归版本需要记录父节点,递归版本不需要记录父节点,因为一个引用,让我们省去了很多麻烦。root就是父节点的左指针或者右指针。托孤直接托孤给root即可。因为root就是父节点的左右指针指向。
当右子树不存在时,直接将要删除结点的左子树托孤给root。当左子树不存在时,直接将要删除结点的右子树托孤给root。

【当存在两个孩子结点时】
当存在两个孩子结点时,还是需要使用保姆法,首先找到保姆结点,然后将要删除结点的值与保姆结点值交换。
这样就转化成子问题了。从整棵树来看,因为保姆结点和删除结点交换,而改变了搜索二叉树的特性。不能使用了。
但从左子树来看,还是完整的二叉搜索树,并且要删除结点就只有一个孩子,直接转化为上面的问题。

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就是父节点左子树或者右子树的引用!,不需要找父节点了
				//1.左子树为空
				if (root->left == nullptr)
				{
					root = root->right;//直接托孤
				}
				//2.右子树为空
				else if (root->right == nullptr)
				{
					root = root->left;
				}
				//3.左右子树都不为空
				else
				{

					//首先找保姆
					Node* leftMax = root->left;
					while (leftMax->right)
					{
						leftMax = leftMax->right;
					}
					std::swap(del->_key, leftMax->_key);

					//转化为子问题
					//交换完后就不是一个搜索树了,结构被破坏了,但左子树没有,并且这时要删除的结点只有一个结点或者没有结点
					return _eraseR(root->left, key);//但左子树还是完整的二叉搜索树

				}
				delete del;
				return true;
			}

		}

⑤.查找结点

查找二叉树中的某个结点,简单的很,当key的值大于结点值时就递归到右子树去找,当key 的值小于结点的值就递归到左子树去找,当key等于结点值时,就表明找到了,返回true。当找到空则返回false。

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

⑥.析构二叉树

对于二叉树的析构,通常使用后序遍历来析构。
遇到空就返回,先析构左子树,再析构右子树,最后析构根结点。

      ~BSTree()
		{
			Destroy(_root);
		}
	void Destroy(Node* root)
		{
			//析构走后序遍历
			if (root == nullptr)
				return;

			Destroy(root->left);
           //递归析构左子树
			Destroy(root->right);
			//递归析构右子树
			delete root;
			//析构根结点
			root = nullptr;
		}

⑦.拷贝二叉树

拷贝二叉树,我们不能使用insert来一个一个插入,因为inset带有筛选的功能,最后结果顺序会不同的。
我们使用类似于前序遍历的方式,进行拷贝,遍历到哪就相当于拷贝到哪。
首先遇到空就返回。1.拷贝结点 2.递归拷贝左子树3.递归拷贝右子树。4.最后将拷贝结点返回。

         BSTree(BSTree<K>& t)
			:_root(nullptr)
		{
			_root = _Copy(t._root);//前序递归拷贝
		}
		Node* _Copy(Node* root)//前序递归拷贝
		{
			if (root == nullptr)
				return nullptr;
			//根  左子树  右子树
			Node* newnode = new Node(root->_key);

			newnode->left = _Copy(root->left);//递归拷贝
			newnode->right = _Copy(root->right);
			//递归时,拷贝创建结点,返回时,链接起来

			return newnode;
		}

⑧.二叉树赋值

现代写法走起

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

Ⅲ.二叉搜索树应用

①.K模型与KV模型

1.K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到
的值。即应用在查找某个对象在不在问题。
2… KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。即应用通过一个对象找另外的一个对应的对象问题。
而关联式容器map和set其实就是key_value模型和key模型。
比如查英汉词典就是类似,通过输入英文,而获取对应的中文。
或者计算一盒水果的个数,每种水果有着对应的个数。
比如我们可以将K模型修改成KV模型,就是存两个对象,一个是K,一个是V。


namespace key_value
{
	template <class K,class V>
	struct BSTreeNode
	{
		BSTreeNode<K,V>* left;
		BSTreeNode<K,V>* right;
		K _key;
		V _value;
		BSTreeNode(const K& key,const V& value)
			:_key(key)
			,_value(value)
			, left(nullptr)
			, right(nullptr)
		{}
	};

	template <class K,class V>
	struct BSTree
	{
		typedef BSTreeNode<K,V> Node;

		BSTree()
			:_root(nullptr)
		{}

		BSTree(BSTree<K,V>& t)
			:_root(nullptr)
		{
			_root = _Copy(t._root);//前序递归拷贝
		}
		BSTree<K,V>* operator=(BSTree<K,V> t)
		{
			swap(_root, t._root);
			return this;
		}
		void Inoder()
		{
			_Inoder(_root);
		}

		
		bool insertR(const K& key,const V& value)//每次递归都需要要改变root的状态,所以必须要传root过来,这里使用一个子函数
		{
			return _insertR(_root, key,value);
		}

		bool eraseR(const K& key)
		{
			return _eraseR(_root, key);
		}
		~BSTree()
		{
			Destroy(_root);
		}
		Node* FindR(const K& key)
		{
			return _FindR(_root, key);
		}
	private:
		Node* _FindR(Node* root, const K& key)//key是无法被修改的,value是可以被修改的,所以要传Node* 来修该value
		{
			if (root == nullptr)
			{
				return nullptr;
			}
			if (root->_key < key)
			{
				_FindR(root->right, key);
			}
			else if (root->_key > key)
			{
				_FindR(root->left, key);
			}
			else
			{
				return root;
			}
		}
		Node* _Copy(Node* root)//前序递归拷贝
		{
			if (root == nullptr)
				return nullptr;
			//根  左子树  右子树
			Node* newnode = new Node(root->_key,root->_value,root->_value);

			newnode->left = _Copy(root->left);//递归拷贝
			newnode->right = _Copy(root->right);
			//递归时,拷贝创建结点,返回时,链接起来

			return newnode;
		}
		void Destroy(Node* root)
		{
			//析构走后序遍历
			if (root == nullptr)
				return;

			Destroy(root->left);

			Destroy(root->right);
			delete root;
			root = nullptr;
		}
		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就是父节点左子树或者右子树的引用!,不需要找父节点了
				//1.左子树为空
				if (root->left == nullptr)
				{
					root = root->right;
				}
				//2.右子树为空
				else if (root->right == nullptr)
				{
					root = root->left;
				}
				//3.左右子树都不为空
				else
				{

					//首先找保姆
					Node* leftMax = root->left;
					while (leftMax->right)
					{
						leftMax = leftMax->right;
					}
					std::swap(del->_key, leftMax->_key);

					//转化为子问题
					//交换完后就不是一个搜索树了,结构被破坏了,但左子树没有,并且这时要删除的结点只有一个结点或者没有结点
					return _eraseR(root->left, key);

				}
				delete del;
				return true;
			}

		}
		bool _insertR(Node*& root, const K& key,const V& value)
		{
			//root是父节点左子树或者右子树的别名

			if (root == nullptr)
			{
				root = new Node(key,value);
			}

			if (root->_key < key)
			{
				return _insertR(root->right,key,value);
			}
			else if (root->_key > key)
			{
				return _insertR(root->left, key,value);
			}
			else
			{
				return false;
			}
			return true;

		}
		void _Inoder(Node* _root)
		{
			if (_root == nullptr)
				return;

			_Inoder(_root->left);
			cout << _root->_key << ":"<<_root->_value<<endl;
			_Inoder(_root->right);

		}
		Node* _root;
	};

}

两种应用场景:


void test2()
{
	key_value::BSTree<string, int> couttree;//key_value模型 计算水果个数
	string a[] = { "西瓜","香蕉","火龙果","橘子","梨子","西瓜","苹果","香蕉","火龙果" };
	 
	for (auto& e : a)
	{
		auto ret = couttree.FindR(e);
		if (ret == nullptr)
		{
			couttree.insertR(e, 1);
		}
		else
		{
			ret->_value++;
		}

	}
	couttree.Inoder();
}
void test1()
{
	key_value::BSTree<string, string> dic;//查字典  key_value模型
	dic.insertR("insert", "插入");
	dic.insertR("delete", "删除");
	dic.insertR("love", "喜欢");
	dic.insertR("print", "打印");
	dic.Inoder();
	string name;
	while (cin >> name)
	{
		auto ret = dic.FindR(name);
		if (ret != nullptr)
		{
			cout << ret->_value << endl;
		}
		else
		{
			cout << "不存在" << endl;
		}
	}
	
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Docker服务编排Docker Compose介绍

1.服务编排概念 2.Docker Compose介绍 3.Docker Compose安装及使用

蓝桥杯每日N题(杨辉三角形)

大家好 我是寸铁 希望这篇题解对你有用&#xff0c;麻烦动动手指点个赞或关注&#xff0c;感谢您的关注 不清楚蓝桥杯考什么的点点下方&#x1f447; 考点秘籍 想背纯享模版的伙伴们点点下方&#x1f447; 蓝桥杯省一你一定不能错过的模板大全(第一期) 蓝桥杯省一你一定不…

【Docker】 使用Docker-Compose 搭建基于 WordPress 的博客网站

引 本文将使用流行的博客搭建工具 WordPress 搭建一个私人博客站点。部署过程中使用到了 Docker 、MySQL 。站点搭建完成后经行了发布文章的体验。 WordPress WordPress 是一个广泛使用的开源内容管理系统&#xff08;CMS&#xff09;&#xff0c;用于构建和管理网站、博客和…

[Go版]算法通关村第十一关白银——位运算的高频算法题

目录 专题1&#xff1a;位移的妙用题目&#xff1a;位1的个数&#xff08;也被称为汉明重量&#xff09;解法1&#xff1a;遍历所有位&#xff0c;判断每个位的数字是否是1Go代码 解法2&#xff1a;依次消除每个1的位 numnum&(num-1)Go代码 题目&#xff1a;比特位计数思路…

C#引用Web Service 类型方法,添加搜索本地服务器Web Service 接口调用方法

首先保证现在网络能调用web service接口&#xff0c;右键项目添加服务引用 ![![在这里插入图片描述](https://img-blog.csdnimg.cn/555ba4fa5e2a418f8f85539a9406bcd6.png) 点击高级 添加web服务 输入搜索的服务器接口&#xff0c;选中你要添加调用的方法即可 添加完成调用方…

win10在vmware16.2.3上安装macos13.1系统

第一步、安装vmware版本信息如下 第二步、下载unlocker426放到安装文件夹 第三步、管理员身份运行unlock.exe 第四步、运行vmware新建虚拟机 第五步、启动新创建的虚拟机macOS13.1并选择语言 第六步、选择磁盘工具抹掉格式化安装磁盘 第七步、格式化完成后退出磁盘工具 第八步、…

DAY4,ARM(用c语言点亮LED灯,封装库代码,软件编程控制硬件)

---gpio.h头文件--- #ifndef __LED_H__ #define __LED_H__//1RCC_MP_AHB4ENSETR寄存器封装 #define RCC_MP_AHB4ENSETR (*(volatile unsigned int*)0x50000a28)//2GPIO封装结构体 typedef struct {volatile unsigned int MODER;volatile unsigned int OTYPER;volatile unsigne…

SpringBoot集成Solr(二)搜索数据

SpringBoot集成Solr&#xff08;二&#xff09;搜索数据 1.1 构建查询条件 //创建 solr查询参数对象 SolrQuery query new SolrQuery(); StringBuilder params new StringBuilder(); params.append(" subject_s:*").append(text).append("*"); params.a…

【深度学习 | 感知器 MLP(BP神经网络)】掌握感知的艺术: 感知器和MLP-BP如何革新神经网络

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…

.NET Core发布到IIS

项目介绍 1、开发工具Visual Studio 2017&#xff0c;语言C#&#xff0c;SQL SERVER&#xff0c;WIN10 2、本地IIS&#xff0c;手机上或其他用户在和本地在同一个局域网内访问,同时要把防火墙关掉 3、IIS全名Internet Information Services&#xff0c;用来发布网站 先决条件 安…

渗透测试面试题汇总(附答题解析+配套资料)

注&#xff1a;所有的资料都整理成了PDF&#xff0c;面试题和答案将会持续更新&#xff0c;因为无论如何也不可能覆盖所有的面试题。 一、思路流程 1、信息收集 a、服务器的相关信息&#xff08;真实ip&#xff0c;系统类型&#xff0c;版本&#xff0c;开放端口&#xff0c;…

lvs集群与nat模式

一&#xff0c;什么是集群&#xff1a; 集群&#xff0c;群集&#xff0c;Cluster&#xff0c;由多台主机构成&#xff0c;但是对外只表现为一个整体&#xff0c;只提供一个访问入口&#xff08;域名与ip地址&#xff09;&#xff0c;相当于一台大型计算机。 二&#xff0c;集…

== 和 equals 的对比 [面试题]

和 equals 的对比[面试题] 文章目录 和 equals 的对比[面试题]1. 和 equals 简介2. Object 类中 equals() 源码3. String 类中 equals() 源码4. Integer 类中 equals() 源码5. 如何重写 equals 方法 1. 和 equals 简介 是一个比较运算符 &#xff1a;既可以判断基本数据类型…

ArcGIS Maps SDK for JavaScript系列之三:在Vue3中使用ArcGIS API加载三维地球

目录 SceneView类的常用属性SceneView类的常用方法vue3中使用SceneView类创建三维地球项目准备引入ArcGIS API创建Vue组件在OnMounted中调用初始化函数initArcGisMap创建Camera对象Camera的常用属性Camera的常用方法 要在Vue 3中使用ArcGIS API for JavaScript加载和展示三维地…

Linux多线程【初识线程】

✨个人主页&#xff1a; 北 海 &#x1f389;所属专栏&#xff1a; Linux学习之旅 &#x1f383;操作环境&#xff1a; CentOS 7.6 阿里云远程服务器 文章目录 &#x1f307;前言&#x1f3d9;️正文1、什么是线程&#xff1f;1.1、基本概念1.2、线程理解1.3、进程与线程的关系…

双向-->带头-->循环链表

目录 一、双向带头循环链表概述 1.什么是双向带头循环链表 2.双向带头循环链表的优势 3.双向带头循环链表简图 二、双向带头循环链表的增删查改图解及代码实现 1.双向带头循环链表的头插 2.双向带头循环链表的尾插 3.双向带头循环链表的头删 4.双向带头循环链表的尾删…

Linux - 借助 inotifywait,轻松实现 Linux 文件/目录事件监听

文章目录 inotify-tools 依赖包使用示例 inotify-tools 依赖包 [rootVM-24-3-centos ~]# yum install inotify-tools Loaded plugins: fastestmirror, langpacks Repository epel is listed more than once in the configuration Determining fastest mirrors ...... ...... ..…

Python实现透明隧道爬虫ip:不影响现有网络结构

作为一名专业爬虫程序员&#xff0c;我们常常需要使用隧道代理来保护个人隐私和访问互联网资源。本文将分享如何使用Python实现透明隧道代理&#xff0c;以便在保护隐私的同时不影响现有网络结构。通过实际操作示例和专业的解析&#xff0c;我们将带您深入了解透明隧道代理的工…

End-to-End Object Detection with Transformers

DERT 目标检测 基于卷积神经网络的目标检测回顾DETR对比Swin Transformer摘要检测网络流程DERT网络架构编码器概述解码器概述整体结构object queries的初始化Decoder中的Muiti-Head Self-AttentionDecoder中的Muiti-Head Attention 损失函数解决的问题 基于卷积神经网络的目标检…

【数据结构】 ArrayList简介与实战

文章目录 什么是ArrayListArrayList相关说明 ArrayList使用ArrayList的构造无参构造指定顺序表初始容量利用其他 Collection 构建 ArrayListArrayList常见操作获取list有效元素个数获取和设置index位置上的元素在list的index位置插入指定元素删除指定元素删除list中index位置上…