二叉树搜索

news2025/7/18 7:17:27

✅<1>主页:我的代码爱吃辣
📃<2>知识讲解:数据结构——二叉搜索树
☂️<3>开发环境
:Visual Studio 2022
💬<4>前言:在之前的我们已经学过了普通二叉树,了解了基本的二叉树的结构和基本操作了,不过我们之前的二叉树结构都是使用C语言实现的,我们这次来介绍二叉树中更加复杂的树结构,C语言在实现这些结构已经有些吃力了,更适合我们使用C++来实现。

目录

一.前言

二.二叉搜索树

三. 二叉搜索树操作

1.结点与整体结构

2.insert()

 3.find()

4.erase()

 5.构造与析构

四.二叉搜索树的应用

 五. 二叉搜索树的性能分析

六.整体代码


一.前言

map和set特性需要先铺垫二叉搜索树,而二叉搜索树也是一种树形结构,二叉搜索树的特性了解,有助于更好的理解map和set的特性,二叉树中部分面试题稍微有点难度,在前面讲解大家不容易接受,且时间长容易忘。本节借二叉树搜索树,对二叉树部分进行收尾总结。

二.二叉搜索树

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

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

三. 二叉搜索树操作

1.结点与整体结构

template<class K>
struct BSTreeNode
{
	BSTreeNode(K key)
		:_key(key),
		_right(nullptr),
		_left(nullptr)
	{
	}
	K _key;  //数值
	BSTreeNode<K>* _right;  //右孩子
	BSTreeNode<K>* _left;   //左孩子
};

template<class K>
class BSTree 
{
	typedef BSTreeNode<K> Node;
public:
    //...
private:
	Node* _root = nullptr;
};

2.insert()

我们主要针对两种情况:

  1. 二叉树中一个数据都没有
  2. 二叉树已经有数据了

如果二叉树中还没有数据,我们需要将插入的第一个数据作为二叉搜索树的根节点。

如果二叉搜索树中,已经有了数据,我们根据搜索二叉树的特性,如果插入的值比根小,我们就往根的左子树去插入,如果插入的值比根大,我们就往根的右子树去插入,如果遇到相同的值就算是插入失败了,循环上面得动作,直到找到一个空位置。

 循环版本:

    bool insert(const K& key)
	{
		//如果BSTree还没有结点
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}

		//找到插入的合适位置,和其父亲结点
		//父亲结点得作用是,我们新插入得结点要和父亲结点连接,
		//简单来说就是,父亲结点要孩子指针,要指向我们新的结点。
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		//创建新节点
		cur = new Node(key);
		//判断新插入得结点是父亲得左孩子还是右孩子
		if (key > parent->_key)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		return true;
	}

递归版本:

   
    bool Rinsert(const K& key)
	{
		return _Rinsert(_root, key);
	}

    bool _Rinsert(Node*& root, const K& key)
	{
		//如果BSTree还没有结点、或者已经找到得合适的空位置
		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}
		//BSTree已经有结点
		if (key < root->_key)
		{
			//key比当前结点小,往左树插入
			return _Rinsert(root->_left, key);
		}
		else if (key > root->_key)
		{
			//key比当前结点大,往右树插入
			return _Rinsert(root->_right, key);
		}
		else
		{
			return false;
		}
	}
    //中序遍历
    void InOrder()
	{
		_InOrder(_root);
	}

    void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_key << " " ;
		_InOrder(root->_right);
	}

测试:

二叉搜索树,中序遍历得结果就是排序结果,我们可以通过这个特性判断我们插入得是否正确。

int  main()
{
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	BSTree<int> b;
	BSTree<int> Rb;
	for (auto e : a)
	{
		b.insert(e);
		Rb.Rinsert(e);
	}
	b.InOrder();
	cout << endl;
	Rb.InOrder();

	return 0;
}

 3.find()

二叉搜索树的查找

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

找到以后返回目标结点得指针。

 

循环版本:

    Node* 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 cur;
			}
		}
		return nullptr;
	}

递归版本:

    
	Node* Rfind(const K& key)
	{
		return _Rfind(_root, key);
	}
    
    Node* _Rfind(Node*& root, const K& key)
	{
		if (root == nullptr)
		{
			return nullptr;
		}
		if (key < root->_key)
		{
			return _Rfind(root->_left, key);
		}
		else if (key > root->_key)
		{
			return _Rfind(root->_right, key);
		}
		else
		{
			return root;
		}
	}

4.erase()

二叉搜索树的删除

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

  • a. 要删除的结点无孩子结点
  • b. 要删除的结点只有左孩子结点
  • c. 要删除的结点只有右孩子结点
  • d. 要删除的结点有左、右孩子结点

看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程
如下:

  • 情况1:要删除的结点只有左孩子结点,删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点--直接删除
  • 情况2:要删除的结点只有右孩子结点,删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点--直接删除
  • 情况3:要删除的结点有左、右孩子结点,在它的右子树中寻找中序下的第一个结点(数值最小),用它的值填补到被删除节点中,再来处理该结点的删除问题--替换法删除

 情况二与情况一处理方法相同:

 循环版本:

bool erase(const K& key)
	{
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				//准备删除
				//待删除结点,左节点为空,将其右边结点交给父亲
				if (cur->_left == nullptr)
				{
					//此时如果删除的是根节点需要改变根节点指向
					if (cur == _root)
					{
						_root = _root->_right;
					}
					else
					{
						//判断待删除结点是父亲的左孩子还是右孩子
						if (cur == parent->_left)
						{
							parent->_left = cur->_right;
						}
						else
						{
							parent->_right = cur->_right;
						}
					}
					
					delete cur;
					return true;

				}//待删除结点,左节点为空,将其左边结点交给父亲
				else if (cur->_right == nullptr)
				{
					//此时如果删除的是根节点需要改变根节点指向
					if (cur == _root)
					{
						_root = _root->_left;
					}
					else
					{
						//判断待删除结点是父亲的左孩子还是右孩子
						if (cur == parent->_left)
						{
							parent->_left = cur->_left;
						}
						else
						{
							parent->_right = cur->_left;
						}
					}
					delete cur;
					return true;

				}	
				else
				{
					//由于删除的结点左右都有孩子
					//需要找一个能代替删除结点的位置结点,即比左子树大比右子树小
					//最适合的结点就是,左子树的最右结点(最大节点),右子树的最左节点(最小结点)
					Node* MinNode = cur->_right;
					Node* MinParent = cur;
					while (MinNode->_left)
					{
						MinParent = MinNode;
						MinNode = MinNode->_left;
					}
					//先将MinNode结点的孩子交给他的父亲
					//注意:不能因为是找的最左边结点就因为MinNode结点一定是MinParent的左孩子
					if (MinParent->_left == MinNode)
					{
						MinParent->_left = MinNode->_right;
					}
					else
					{
						MinParent->_right = MinNode->_right;
					}
					//将MinNode结点的值赋值给cur
					cur->_key = MinNode->_key;
					delete MinNode;
					return true;
				}
			}
		}
		return false;
	}

递归版本:

bool _Rerase(Node*& root, const K& key)
	{
		//空树、没有找到删除的结点
		if (root == nullptr)
		{
			return false;
		}

		if (key < root->_key)
		{
			//key比当前结点小,往左树删除
			return _Rerase(root->_left, key);
		}
		else if(key > root->_key)
		{
			//key比当前结点小,往左树删除
			return _Rerase(root->_right, key);
		}
		else
		{
			//找到,开始删除
			Node* cur = root;
			if (root->_left == nullptr)
			{
				//1.待删除结点,左孩子为空
				root = root->_right;
			}
			else if (root->_right == nullptr)
			{
				//2.待删除结点,右孩子为空
				root = root->_left;
			}
			else//待删除结点,左右孩子都不为空
			{
				//找到左树的最大结点
				Node* maxleft = root->_left;
				while (maxleft->_right)
				{
					maxleft = maxleft->_right;
				}
				//交换maxleft和待删除结点的Key值,
				//并再次转换成左树删除一个单孩子结点,复用上述情况一二的代码
				swap(maxleft->_key, root->_key);
				return _Rerase(root->_left, key);
			}
			delete cur;
		}

	}

测试:

int  main()
{
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	BSTree<int> b;
	BSTree<int> Rb;
	for (auto e : a)
	{
		b.insert(e);
		Rb.Rinsert(e);
	}

	for (auto e : a)
	{
		b.erase(e);
		b.InOrder();
		cout << endl;
		Rb.Rerase(e);
		Rb.InOrder();
		cout << endl;
	}
	return 0;
}

 5.构造与析构

拷贝构造:

前序创建结点,后续连接指向。

    BSTree(const BSTree<K>& root)
	{
		_root = _copy(root._root);
	}
    Node* _copy(const 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()
	{
		Destroy(_root);
	}
    void Destroy(Node* root)
	{
		if (root == nullptr)
			return;

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

默认构造:

如果我们写了拷贝构造,编译器就不会自己生成默认构造函数了,我们可以自己写一个默认构造函数,也可以强制编译器生成一个,但是默认构造只能有一个。

	//告诉编译器强制生成
	BSTree() = default;
	//自己写
	BSTree()
	{
	}

四.二叉搜索树的应用

1. K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到
的值。
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
2. KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方
式在现实生活中非常常见:
比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英
文单词与其对应的中文<word, chinese>就构成一种键值对;
再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出
现次数就是<word, count>就构成一种键值对。

例如:我们将上述的代码改造成K/V结构:

template<class K,class V>
struct BSTreeNode
{
	BSTreeNode(const K& key, const V& val)
		:_key(key),
		_val(val),
		_right(nullptr),
		_left(nullptr)
	{
	}
	K _key;
	V _val;
	BSTreeNode<K,V>* _right;
	BSTreeNode<K,V>* _left;
};

template<class K, class V >
class KV_BSTree
{
	typedef BSTreeNode<K,V> Node;
public:

	KV_BSTree() = default;

	KV_BSTree(const KV_BSTree<K,V>& root)
	{
		_root = _copy(root._root);
	}

	~KV_BSTree()
	{
		//...
	}

	bool insert(const K& key,const V& val)
	{
		//如果BSTree还没有结点
		if (_root == nullptr)
		{
			_root = new Node(key,val);
			return true;
		}

		//找到插入的合适位置,和其父亲结点
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		//判断链接
		cur = new Node(key,val);
		if (key > parent->_key)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		return true;
	}

	Node* find(const K& key)
	{
        //...
	}

	bool erase(const K& key)
	{
		//...
	}

	void InOrder()
	{
		_InOrder(_root);
	}

private:
	void Destroy(Node* root)
	{
        //...
	}

	void _InOrder(Node* root)
	{
		//...
	}

	Node* _root = nullptr;
};


int main()
{
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉" };
	KV_BSTree<string,int> b;
	for (auto e : arr)
	{
		auto cur = b.find(e);
		if (cur == nullptr)
		{
			b.insert(e, 1);
		}
		else
		{
			cur->_val++;
		}
	}
	b.InOrder();
	return 0;
}

 统计水果出现的次数:

 五. 二叉搜索树的性能分析

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

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二
叉搜索树的深度的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:$log_2 N$
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为:$\frac{N}{2}$
问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插
入关键码,二叉搜索树的性能都能达到最优?那么我们后续章节学习的AVL树和红黑树就可以上
场了。

六.整体代码

BSTree.hpp


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

template<class K>
struct BSTreeNode
{
	BSTreeNode(const K& key)
		:_key(key),
		_right(nullptr),
		_left(nullptr)
	{
	}
	K _key;
	BSTreeNode<K>* _right;
	BSTreeNode<K>* _left;
};

template<class K>
class BSTree 
{
	typedef BSTreeNode<K> Node;
public:
	//告诉编译器强制生成
	BSTree() = default;
	//自己写
	//BSTree()
	//{
	//}

	BSTree(const BSTree<K>& root)
	{
		_root = _copy(root._root);
	}

	~BSTree()
	{
		Destroy(_root);
	}
	
	bool insert(const K& key)
	{
		//如果BSTree还没有结点
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}

		//找到插入的合适位置,和其父亲结点
		//父亲结点得作用是,我们新插入得结点要和父亲结点连接,
		//简单来说就是,父亲结点要孩子指针,要指向我们新的结点。
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		//创建新节点
		cur = new Node(key);
		//判断新插入得结点是父亲得左孩子还是右孩子
		if (key > parent->_key)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		return true;
	}

	Node* 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 cur;
			}
		}
		return nullptr;
	}

	bool erase(const K& key)
	{
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				//准备删除
				//待删除结点,左节点为空,将其右边结点交给父亲
				if (cur->_left == nullptr)
				{
					//此时如果删除的是根节点需要改变根节点指向
					if (cur == _root)
					{
						_root = _root->_right;
					}
					else
					{
						//判断待删除结点是父亲的左孩子还是右孩子
						if (cur == parent->_left)
						{
							parent->_left = cur->_right;
						}
						else
						{
							parent->_right = cur->_right;
						}
					}
					
					delete cur;
					return true;

				}//待删除结点,左节点为空,将其左边结点交给父亲
				else if (cur->_right == nullptr)
				{
					//此时如果删除的是根节点需要改变根节点指向
					if (cur == _root)
					{
						_root = _root->_left;
					}
					else
					{
						//判断待删除结点是父亲的左孩子还是右孩子
						if (cur == parent->_left)
						{
							parent->_left = cur->_left;
						}
						else
						{
							parent->_right = cur->_left;
						}
					}
					delete cur;
					return true;

				}	
				else
				{
					//由于删除的结点左右都有孩子
					//需要找一个能代替删除结点的位置结点,即比左子树大比右子树小
					//最适合的结点就是,左子树的最右结点(最大节点),右子树的最左节点(最小结点)
					Node* MinNode = cur->_right;
					Node* MinParent = cur;
					while (MinNode->_left)
					{
						MinParent = MinNode;
						MinNode = MinNode->_left;
					}
					//先将MinNode结点的孩子交给他的父亲
					//注意:不能因为是找的最左边结点就因为MinNode结点一定是MinParent的左孩子
					if (MinParent->_left == MinNode)
					{
						MinParent->_left = MinNode->_right;
					}
					else
					{
						MinParent->_right = MinNode->_right;
					}
					//将MinNode结点的值赋值给cur
					cur->_key = MinNode->_key;
					delete MinNode;
					return true;
				}
			}
		}
		return false;
	}

	bool Rerase(const K& key)
	{
		return _Rerase(_root,key);
	}

	Node* Rfind(const K& key)
	{
		return _Rfind(_root, key);
	}

	bool Rinsert(const K& key)
	{
		return _Rinsert(_root, key);
	}

	void InOrder()
	{
		_InOrder(_root);
	}

private:

	Node* _copy(const 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;
	}

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

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

	bool _Rerase(Node*& root, const K& key)
	{
		//空树、没有找到删除的结点
		if (root == nullptr)
		{
			return false;
		}

		if (key < root->_key)
		{
			//key比当前结点小,往左树删除
			return _Rerase(root->_left, key);
		}
		else if(key > root->_key)
		{
			//key比当前结点小,往左树删除
			return _Rerase(root->_right, key);
		}
		else
		{
			//找到,开始删除
			Node* cur = root;
			if (root->_left == nullptr)
			{
				//1.待删除结点,左孩子为空
				root = root->_right;
			}
			else if (root->_right == nullptr)
			{
				//2.待删除结点,右孩子为空
				root = root->_left;
			}
			else//待删除结点,左右孩子都不为空
			{
				//找到左树的最大结点
				Node* maxleft = root->_left;
				while (maxleft->_right)
				{
					maxleft = maxleft->_right;
				}
				//交换maxleft和待删除结点的Key值,
				//并再次转换成左树删除一个单孩子结点,复用上述情况一二的代码
				swap(maxleft->_key, root->_key);
				return _Rerase(root->_left, key);
			}
			delete cur;
		}

	}

	Node* _Rfind(Node*& root, const K& key)
	{
		if (root == nullptr)
		{
			return nullptr;
		}

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

	bool _Rinsert(Node*& root, const K& key)
	{
		//如果BSTree还没有结点
		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}
		//BSTree已经有结点
		if (key < root->_key)
		{
			//key比当前结点小,往左树插入
			return _Rinsert(root->_left, key);
		}
		else if (key > root->_key)
		{
			//key比当前结点大,往右树插入
			return _Rinsert(root->_right, key);
		}
		else
		{
			return false;
		}
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_key << " " ;
		_InOrder(root->_right);
	}

	Node* _root = nullptr;
};

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

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

相关文章

Spring(四):Spring Boot 的创建和使用

关于Spring之前说到&#xff0c;Spring只是思想&#xff08;核心是IOC、DI和AOP&#xff09;&#xff0c;而具体的如何实现呢&#xff1f;那就是由Spring Boot 来实现&#xff0c;Spring Boot究竟是个啥呢&#xff1f; 什么是Spring Boot&#xff0c;为什么要学Spring Boot Sp…

Multi-UAV Disaster Environment Coverage Planning with Limited-Endurance

Multi-UAV Disaster Environment Coverage Planning with Limited-Endurance 有限续航时间下的多无人机灾害环境覆盖规划 定义问题将初始地图转换为热图产生优化路径 基于 已知的灾区热图&#xff0c;设计一个多无人机全覆盖搜索的路径规划方法。可以在无人机有限能量约束下探索…

dirsearch目录扫描工具的使用

文章目录 工具下载及环境准备查看帮助信息进行目录扫描 官方介绍 &#xff1a;An advanced command-line tool designed to brute force directories and files in webservers, AKA web path scanner 一个高级命令行工具&#xff0c;用于暴力破解网络服务器中的目录和文件&…

韦东山-电子量产工具项目:业务系统

代码结构 所有代码都已通过测试跑通&#xff0c;其中代码结构如下&#xff1a; 一、include文件夹 1.1 common.h #ifndef _COMMON_H #define _COMMON_Htypedef struct Region {int iLeftUpX; //区域左上方的坐标int iLeftUpY; //区域左下方的坐标int iWidth; //区域宽…

Apache DolphinScheduler 支持使用 OceanBase 作为元数据库啦!

DolphinScheduler是一个开源的分布式任务调度系统&#xff0c;拥有分布式架构、多任务类型、可视化操作、分布式调度和高可用等特性&#xff0c;适用于大规模分布式任务调度的场景。目前DolphinScheduler支持的元数据库有Mysql、PostgreSQL、H2&#xff0c;如果在业务中需要更好…

【视觉SLAM入门】5.2. 2D-3D PNP 3D-3D ICP BA非线性优化方法 数学方法SVD DLT

"养气之学&#xff0c;戒之躁急" 1. 3D-2D PNP1.1 代数法1.1.1 DLT(直接线性变换法)1.1.2. P3P 1.2 优化法BA (Bundle Adjustment)法 2. 3D-3D ICP2.1 代数法2.1.1 SVD方法 2.2 优化(BA)法2.2.2 非线性优化方法 前置事项&#xff1a; 1. 3D-2D PNP 该问题描述为&am…

使用swoole实现实时消息推送给客户端

一. 测试服务端 //测试服务端public function testServer(){$server new Server(192.168.0.144, 9501, SWOOLE_BASE, SWOOLE_SOCK_TCP);$server->on(request, function ($request, $response) {$response->header(Content-Type, text/plain);$response->end("He…

【操作系统】虚拟内存相关分段分页页面置换算法

虚拟内存是什么&#xff1f; 【进程地址空间虚拟地址空间C/C程序地址空间就是那个4G的空间】 虚拟内存是操作系统内核为了对进程地址空间进行管理&#xff0c;而设计的一个逻辑意义上的内存空间概念。在程序运行过程中&#xff0c;虚拟内存中需要被访问的部分会被映射到物理内…

Mock平台-08开发:项目管理(四)编辑功能和Component抽离

【Mock平台】为系列测试开发教程&#xff0c;从0到1编码带你一步步使用Spring Boot 和 Antd React框架完成搭建一个测试工具平台&#xff0c;希望作为一个实战项目对各位的测试开发学习之路有帮助&#xff0c;大奇一个专注测试技术干货原创与分享的家伙。 本篇重点&#xff1a;…

HCIP生成树STP总结

STP生成树 网桥的4个选举 根网桥&#xff1a; 有且仅有一台&#xff0c;且由BPDU中的桥ID来决定 桥ID 网桥优先级&#xff08;0-65535公有&#xff09; 默认32768 MAC地址&#xff08;只有…

湘潭大学 湘大 XTU OJ 1055 整数分类 题解(非常详细)

链接 整数分类 题目 Description 按照下面方法对整数x进行分类&#xff1a;如果x是一个个位数&#xff0c;则x属于x类&#xff1b;否则将x的各位上的数码累加&#xff0c;得到一个新的x&#xff0c;依次迭代&#xff0c;可以得到x的所属类。比如说24&#xff0c;246&#…

漏洞指北-VulFocus靶场专栏-高级01

漏洞指北-VulFocus靶场专栏-高级01 高级001 &#x1f338;骑士cms任意代码执行&#xff08;CVE-2020-35339&#xff09;&#x1f338;step1&#xff1a;进入页面&#xff0c;登入后台step2 系统——网站配置——网站域名step3 中国蚁剑连接 高级002 &#x1f338;Django SQL注入…

安装ps找不到vcruntime140_1.dll怎么回事?有哪些解决方法

安装ps找不到vcruntime140_1.dll怎么回事&#xff1f;这可能是因为您的计算机Visual C Redistributable包损坏&#xff0c;其中包含vcruntime140_1.dll文件。VCRuntime140_1.dll是一个重要的动态链接库文件&#xff0c;它包含了Visual C运行时所需的一些函数和资源。当这个文件…

银行客户关系管理系统springboot财务金融进销存java jsp源代码

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 银行客户关系管理系统springboot 系统有1权限&#x…

周易卦爻解读笔记——未济

第六十四卦未济 火水未济 离上坎下 未济卦由否卦所变&#xff0c;否卦六二与九五换位&#xff0c;象征尚未完成。 天地否 未济卦和既济卦既是错卦又是覆卦&#xff0c;这也是最后一卦&#xff0c;序卦传【物不可穷也&#xff0c;故受之以未济终焉】 未济卦象征尚未完成&…

NSSCTF之Misc篇刷题记录(14)

[SWPUCTF] 2021新生赛之Crypto篇刷题记录① [UUCTF 2022 新生赛]王八快跑[安洵杯 2020]BeCare4[HDCTF 2023]ExtremeMisc NSSCTF平台&#xff1a;https://www.nssctf.cn/ PS&#xff1a;记得所有的flag都改为NSSCTF [UUCTF 2022 新生赛]王八快跑 小游戏 小乌龟跑过线就有 fla…

BDA初级分析——SQL语句应用基础练习题

1、请检查OLYMPIC表中是否存在重复国家? SELECT Team,COUNT(*) AS 重复次数 FROM Olympic GROUP BY Team HAVING COUNT(*) > 1;SELECT COUNT(Team),COUNT(DISTINCT(Team)) FROM Olympic; 2、将OLYMPIC表中Armenia(ARM)的奖牌总数更新为14 UPDATE Olympic SET total_medals …

【C语言】使用C语言,实现九九乘法表(另附Python、Java、JavaScript实现方式)

文章目录 1. C语言实现1.1 思路1.2 代码实现 3.其他语言实现3.1 Python实现3.2 Java实现3.3 JavaScript实现 1. C语言实现 1.1 思路 九九乘法表图示&#xff1a; 思路如下&#xff1a;定义两层for循环即可实现九九乘法表 一共有9层&#xff0c;所以要定义一个变量i&#xff…

记录首次面试2023-08-18

人生第一次面试&#xff0c;大概一个小时左右。没有问我C的&#xff0c;上来一个数据库事务&#xff0c;虽然没有复习&#xff0c;但是还是能够记住一些&#xff0c;主要问的一些事务的隔离级别&#xff0c;以及都有什么作用&#xff0c;我是举例回答的&#xff0c;客户端A和客…

【激光雕刻与DIY Arduino SCARA机器人】

【激光雕刻与DIY Arduino SCARA机器人】 1. 项目概况2. 设计和3D模型3. 安装激光模块4. SCARA机器人激光雕刻机电路图5. 完成装配6. 马林固件,用于使用 SCARA 机器人进行激光雕刻7. 配置 Marlin 固件8. 控制软件 – 主机9. 使用 SCARA 机器人进行激光雕刻10. 生成用于激光雕刻…