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

news2024/7/6 19:05:47

深入篇【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;
		}
	}
	
}

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

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

相关文章

Cadence+硬件每日学习十个知识点(38)23.8.18 (Cadence的使用,界面介绍)

文章目录 1.Cadence有共享数据库的途径2.Cadence启动3.Cadence界面菜单简介&#xff08;file、edit、view、place、options&#xff09;4.Cadence界面的图标简介5.我的下载资源有三本书 1.Cadence有共享数据库的途径 答&#xff1a; AD缺少共享数据库的途径&#xff0c;目前我…

Apache-DBUtils

目录 封装方法 引出dbutils 案例 当关闭connection后&#xff0c;resultset结果集就无法使用了&#xff0c;这就使得resultset不利于数据的管理 封装方法 我们可以将结果集先存储在一个集合中&#xff0c;当connection关闭后&#xff0c;我们可以通过访问集合来访问结果集 …

Educational Codeforces Round 110 (Rated for Div. 2) C. Unstable String

dp写法&#xff1a;f[i][j]表示第i位&#xff0c;当前位为j&#xff0c;能往前找的最大的合法长度。 #include<bits/stdc.h> #define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); #define endl \nusing namespace std;typedef pair<int, int> PII; type…

Redis进阶篇 - 04发布订阅、布隆过滤器、过期策略、回收机制、管道...核心知识原理

Redis底层原理篇&#xff0c;​让学习绚丽多彩起来&#xff01;&#xff01;&#xff01;&#xff08;需要原图私信&#xff09;

【服务器】Strace显示后台进程输出

今天有小朋友遇到一个问题 她想把2331509和2854637这两个进程调到前台来&#xff0c;以便于在当前shell查看这两个python进程的实时输出 我第一反应是用jobs -l然后fg &#xff08;参考这里&#xff09; 但是发现jobs -l根本没有输出&#xff1a; 原因是jobs看的是当前ses…

【Docker】Docker Desktop配置资源:cpu、内存等(windows环境下)

Docker Desktop配置资源&#xff1a;cpu、内存等&#xff08;windows环境下&#xff09; 一、WSL2 以及 hyper-v区别&#xff0c;二者安装docker desktop1.WSL2和hyper-v区别2.安装Docker Desktop 二、docker desktop限额配置&#xff0c;资源配置方法 Docker 是指容器化技术&a…

Redis进阶底层原理- Redis结构图与底层数据编码结构

Redis底层原理篇&#xff0c;​让学习绚丽多彩起来&#xff01;&#xff01;&#xff01;&#xff08;需要原图私信&#xff09;

DAMO-YOLO:实时目标检测设计的报告

ReadPaperhttps://readpaper.com/pdf-annotate/note?pdfId4748421678288076801eId1920373270663763712 Abstract 在本报告中&#xff0c;我们提出了一种快速准确的目标检测方法&#xff0c;称为DAMO-YOLO&#xff0c;它比最先进的YOLO系列实现了更高的性能。DAMO-YOLO 通过…

并发编程系列 - ReadWriteLock

实际工作中&#xff0c;为了优化性能&#xff0c;我们经常会使用缓存&#xff0c;例如缓存元数据、缓存基础数据等&#xff0c;这就是一种典型的读多写少应用场景。缓存之所以能提升性能&#xff0c;一个重要的条件就是缓存的数据一定是读多写少的&#xff0c;例如元数据和基础…

[python] 使用Jieba工具中文分词及文本聚类概念

前面讲述了很多关于Python爬取本体Ontology、消息盒InfoBox、虎扑图片等例子&#xff0c;同时讲述了VSM向量空间模型的应用。但是由于InfoBox没有前后文和语义概念&#xff0c;所以效果不是很好&#xff0c;这篇文章主要是爬取百度5A景区摘要信息&#xff0c;再利用Jieba分词工…

衣服材质等整理(时常更新)

参考文章&图片来源 https://zhuanlan.zhihu.com/p/390341736 00. 天然纤维 01. 化学纤维 02. 聚酯纤维&#xff08;即&#xff0c;涤纶&#xff09; 一种由有机二元酸和二元醇通过化学缩聚制成的合成纤维。具有出色的抗皱性和保形性&#xff0c;所制衣物在穿着过程中不容…

2023HW-8月(10-15)53个0day,1day漏洞汇总含POC、EXP

点击"仙网攻城狮”关注我们哦~ 不当想研发的渗透人不是好运维 让我们每天进步一点点 简介 2023HW-8月10-15号0day、1day漏洞汇总&#xff08;已更新&#xff09;&#xff0c;包含以下漏洞需要自取。 链接&#xff1a;https://pan.baidu.com/s/1Tr94yVFSHn_C6YiJcVprAw 提取…

【C++初阶】string类字符串包不包含‘\0‘

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习C和算法 ✈️专栏&#xff1a;C航路 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&#x1…

Leetcode32 最长有效括号

给你一个只包含 ( 和 ) 的字符串&#xff0c;找出最长有效&#xff08;格式正确且连续&#xff09;括号子串的长度。 代码如下&#xff1a; class Solution {public int longestValidParentheses(String str) {Stack<Integer> s new Stack<>();int res 0;int st…

【可变形卷积3】 DCNv2 安装

使用RTM3D 代码&#xff0c;CenterTrack代码需要用DCN 1、安装DCNv2 &#xff08;1&#xff09;github上最新版的DCNv2源码在"https://github.com/CharlesShang/DCNv2"&#xff0c;但是该版本源码不支持PyTorch1.7&#xff0c;如果使其支持PyTorch1.7需要做以下修改…

ClickHouse(二十二):Clickhouse SQL DML操作及导入导出数据

进入正文前&#xff0c;感谢宝子们订阅专题、点赞、评论、收藏&#xff01;关注IT贫道&#xff0c;获取高质量博客内容&#xff01; &#x1f3e1;个人主页&#xff1a;含各种IT体系技术&#xff0c;IT贫道_Apache Doris,大数据OLAP体系技术栈,Kerberos安全认证-CSDN博客 &…

JLSX 模版指令导出Excel

1. 官方相关链接 官网&#xff1a;https://jxls.sourceforge.net/reference/if_command.html JxlsAPI&#xff1a; https://jxls.sourceforge.net/javadoc/jxls/index.html Jxls POI&#xff1a; https://jxls.sourceforge.net/javadoc/jxls/index.html Jxls JExcel&#xff1…

cesium学习记录09-turf.js的使用(画矩形结合地形生成三角网)

上一篇是绘制多边形&#xff0c;这一篇来说绘制矩形&#xff0c;但又因为只说绘制矩形太短了&#xff0c;所以就结合一下turf.js&#xff0c;生成一下地形三角网 Turf.js中文网 最终效果&#xff1a; 一、引入Turf.js 1&#xff0c;下载 npm install turf/turf2&#xff0c;…

html(七)meta标签

一 meta标签 1、背景&#xff1a;发现自带某些请求头2、本文没有实际的生产应用场景,仅仅作为技术积累 ① meta标签含义 1、metadata: 元数据,是用于描述数据的数据,它不会显示在页面上,但是机器却可以识别2、应用场景&#xff1a; [1]、SEO搜索引擎优化[2]、定义页面使用…

React 调试开发插件 React devtools 的使用

可以在谷歌扩展应用商店里获取这个插件。如果不能访问谷歌应用商店&#xff0c;可以点此下载最新版 安装插件后&#xff0c;控制台出现 “Components” 跟 “Profiler” 菜单选项。 查看版本&#xff0c;步骤&#xff1a; 下面介绍 react devtools 的使用方式。 在 Component…