进阶了解C++(5)——搜索二叉树

1. 什么是搜索二叉树:

        在之前针对数据结构的文章中,对数、二叉树以及堆进行了介绍,在本部分,将针对二叉搜索树进行介绍。对于二叉搜索树,其于二叉树相比,最大的特点就是结点的排布是存在规则的。在搜索二叉树中,假设根结点存储的数值为n,则根结点的右子树所存储的数值均>n,根结点的左子树所存储的数值均<n。并且其左右子树也都满足这个特点。例如:

2. 搜索二叉树的实现——循环实现:

2.1 搜索二叉树的基本结构 :

       对于搜索二叉树,其单个结点的结构与二叉树相同,即:一个用于存储数值的变量,两个指针变量,一个指向其左结点,一个指向其右结点。对应代码如下:

template<class K>
struct BSTreeNode
{
	typedef BSTreeNode<K> Node;

	BSTreeNode(const K& val)
		: _val(val)
		, _left(nullptr)
		, _right(nullptr)
	{}

	K _val;
	Node* _left;
	Node* _right;
};

       其中,上面类中的三个成员变量_val,_left,_right分别表示该结点存储的数值,指向的左子树的根结点、指向右子树的根结点。

       在利用BSTreeNode这个类表示完搜索二叉树中单个结点的结构后,需要对于搜索二叉树的基本结构进行定义,即:将BSTreeNode引入,且利用这个类创建一个指针变量表示搜索二叉树的根结点_root并且将其初始化为nullptr,即:

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

2.2 搜索二叉树功能实现——搜索结点:

       前面说到,搜索二叉树的特点是,根结点的右子树的所有结点存储的数值均>根结点存储数值,根结点的左子树所有结点所存储的数值均<根结点存储的数值。因此,对于结点的搜索,假设需要搜索的值为n,如果满足n>root->val,则表示n在右子树部分,否则在左子树部分。对应代码如下:

bool Find(const K& val)
{
	Node* cur = _root;
	while (cur)
	{
		if (val > cur->_val)
		{
			cur = cur->_right;
		}
		else if (val < cur->_val)
		{
			cur = cur->_left;
		}
		else
		{
			return true;
		}
	}
	return false;
}

2.3 搜索二叉树功能实现——插入结点:

针对于插入结点这一功能的实现,需要分为两种情况:

1.搜索二叉树为一个空树:

     对于空树这种情况,只需要单独对于根结点进行一次检索,即_root ==nullptr;如果条件满足,则直接在根结点上利用操作符new创建结点即可,机:

bool Insert(const K& val)
{
	if (_root == nullptr)
	{
		_root = new Node(val);
	}
}

2.搜索二叉树不为空:

    对于树不为空的情况,首先需要检测插入的数值val与根结点存储数值的大小关系,如果大于,则插入在右子树,如果小于,则插入在左子树。需要注意,在搜索二叉树中,不允许存在存储相同数值的结点。具体操作可以由下面的图片演示:

       需要插入的数值为17,首先创建一个变量cur用于接收_root的地址,以便进行遍历,在最开始,由于17>8,因此cur=cur->right

      重复上述步骤,当cur指向的结点为存储数值14的结点时,由于17>14,因此会再次进行cur=cur->right的操作,此时cur的位置可以如下图所示:

      此时,由于没有记录父结点的地址,所以无法进行插入,为了解决这个问题,可以在创建一个变量parent,每次cur指针的指向改变之前,均让parent记录一次cur中存储的地址。在加入了parent后,当cur处于上图中的位置时,parent的位置可以由下图表示,即:
 

    此时,虽然存储了父结点的地址,但是也不能立即进行插入,需要在对cur,parent两个结点中的数值进行一次判定,如果cur->val > parent->val;则插入到parent的右边,否则插入到左边。对应代码如下:

bool Insert(const K& val)
{
	if (_root == nullptr)
	{
		_root = new Node(val);
	}

	Node* cur = _root;
	Node* parent = nullptr;
	while (cur)
	{
		if (val > cur->_val)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (val < cur->_val)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}

	cur = new Node(val);

	if (val > parent->_val)
	{
		parent->_right = cur;
		return true;
	}
	else if (val < parent->_val)
	{
		parent->_left = cur;
		return true;
	}
}

2.4 搜索二叉树功能实现——中序遍历:

       对于中序遍历,在之前针对于二叉树的文章给出过充分的说明,本部分只给出代码,对于不同遍历的具体介绍可以在一起学数据结构(9)——二叉树的链式存储及相关功能实现_链表 二叉树 存储 查询-CSDN博客

进行查看。不过本处需要注意,由于前序遍历需要访问树的根结点,但是根结点的访问权限为private,因此,无法在外部直接进行访问。为了解决这个问题,文章选择对前序遍历函数进行一次嵌套,对应代码如下:

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

	_InOrder(root->_left);
	cout << root->_val << ' ';
	_InOrder(root->_right);
}

void InOrder()
{
	_InOrder(_root);
}

2.5 搜索二叉树功能测试:

通过下面的代码对于上方的功能函数进行测试:

int main()
{
	BSTree<int> t;
	int a[] = { 8,3,1,10,6,4,7,14,13 };
	for (auto e : a)
	{
		t.Insert(e);
	}
	t.InOrder();
	return 0;
}

运行结果如下:

2.6 搜索二叉树功能实现——删除结点:

       对于搜索二叉树中的删除结点功能,可以说是最复杂的一个功能。因为在删除结点时,需要针对不同的情况来给出特定的删除结点的途径。对于搜索二叉树结点的删除,在大体上可以分为三个部分:

      1.删除叶子结点,例如下图中的13,7,4

      2.删除的结点有一个子结点,例如下图中的14,10

      3.删除的结点有两个子结点,例如下图中的8,6,3

对于上面给出的三种情况,其中第1,2种可以看作一种,具体原因在下面给出说明

       在删除一个结点之前,首先要找到这个结点,以及这个结点的父结点,对于这类操作在插入中已进行了解释,因此不再过多叙述。假设需要删除存储14的结点,则在找到这个结点的地址以及其父结点的地址后,首先需要对存储14的结点中的left,right进行判断,如果left为空,则让父结点链接到right。如果right为空,则让父结点链接到left。如果left,right都为空,则可以看作为叶子结点。

       例如上图: 在找到需要删除的结点curparent后,由于判定cur->right==nullptr,因此需要让parent链接cur->left。但是需要注意,此时并不知道parentcur的链接关系,因此,在让parent,cur->left链接之前,需要再次进行对于parent,cur两个结点关系的判定。如果cur=parent->left;,则在链接时,让parent->left=cur->left。反之同理。由此,可以得到下面的代码:

bool Erase(const K& val)
{
	Node* cur = _root;
	Node* parent = nullptr;

	while (cur)
	{
		if (val > cur->_val)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (val < cur->_val)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			//找到了需要删除的结点
			if (cur->_left == nullptr)
			{
				if (cur == parent->_left)
				{
					parent->_left = cur->_right;
				}
				else if( cur == parent->_right)
				{
					parent->_right = cur->_right;
				}
				delete cur;
				return true;
			}
			else if (cur->_right == nullptr)
			{
				if (cur == parent->_left)
				{
					parent->_left = cur->_left;
				}
				else if (cur == parent->_right)
				{
					parent->_right = cur->_left;
				}
				delete cur;
				return true;
			}
			else
			{
				;
			}
		}

	}
	return false;
}

在测试代码种,尝试删除存储值为14的结点,即:

int main()
{
	BSTree<int> t;
	int a[] = { 8,3,1,10,6,4,7,14,13 };
	for (auto e : a)
	{
		t.Insert(e);
	}
	t.InOrder();
	t.Erase(14);
	t.InOrder();
	return 0;
}

运行结果如下:

如图所示,删除了值为14的结点。

        目前,上方的代码暂时满足了删除叶子结点或者有一个子结点的结点。但是对于有两个子结点的结点如何删除,例如对于存储数值为3的结点,即:

       对于拥有两个叶子结点的父结点的删除,文章本处选择替换删除法,即:先找到一个合适的值用于替换待删除结点的值,随后进行删除。

       而对于合适的值的选择,需要同时满足两个条件:大于待删除结点的左子树根结点存储的值,小于待删除结点的右子树根结点存储的值。而满足这两个条件的结点,在图中分别为2,4。通过搜索二叉树的特点,不难得出,这个合适的点存在于两个地方:

       一个是待删除结点的左子树的最右结点,即左子树中的最大的结点。

       另一个则是待删除结点的右子树的最左结点,即右子树种的最小的结点。

     在进行替换删除法时,对于上面两种结点均可以选择。文章本处选择查找右子树中的最小结点。具体方法如下:

     定义一个指针rightMin,并且用于存储待删除结点的右子树的根结点,对于上图,则rightMin用于存储3结点的右子树的根结点,即6结点。再令rightMin=rightMin->left;来查找右子树的最左结点。并且为了防止让rightMin存储空,每次向下查找之前,需要进行一次判定rightMin->left\, \, !=\, \, nullptr;。再找到和合适的结点后,将待删除结点的数值改为rightMin->val。具体如下:

在进行删除结点时,只需要删除rightMin所对应的结点即可。即:

由于需要删除rightMin结点,同时还需要让rightMin的父结点的指向发生改变,例如上图中的6结点。因此,在查找rightMin时,还需要找到其父结点,为了方便表达,文章将这个父结点命名为rightMinParent,对于上述过程的具体代码如下所示:

Node* rightMin = cur->_right;
Node* rightMinParent = nullptr;
while (rightMin->_left != nullptr)
{
				rightMinParent = rightMin;
				rightMin = rightMin->_left;
}

cur->_val = rightMin->_val;
rightMinParent->_left = rightMin->_right;

但是这种删除方法并不完全正确,例如针对删除根结点8这一极端情况,此时cur,rightMin,rightMinParent的初始位置可以由下图表示:

如果按照上方的代码运行,由于rightMin->left \, \, ==\, \, nullptr,因此rightMin的指向不会改变,并且rightMinParent的指向也不会改变,即一直为nullptr。因此,为了解决这个问题,在初始化rightMinParent时,不能初始化为nullptr,而是初始化为cur。在修改后,此时的三个指针的指向可以由下图进行表示:

但是,如果继续按照上面的代码运行,当运行到

rightMinParent->_left = rightMin->_left;

        不难发现,此时rightMin\, \, =\, \, rightMinParent->right;因此,会发生错误。所以,在修改rightMinParent的指向时,需要在进行一次判定。对应代码如下:

Node* rightMin = cur->_right;
Node* rightMinParent = cur;
while (rightMin->_left != nullptr)
{
				rightMinParent = rightMin;
				rightMin = rightMin->_left;
}

cur->_val = rightMin->_val;
if (rightMin == rightMinParent->_left)
{
				rightMinParent->_left = rightMin->_right;
}
else if (rightMin == rightMinParent->_right)
{
				rightMinParent->_right = rightMin->_right;

}
delete rightMin;
return true;

但是上述代码只能针对完整的树起作用,假设需要对下面图的树的根结点进行删除,即:

按照上面的逻辑,如果待删除的结点左子树为空,则首先需要判断待删除结点是父结点的左子结点还是右子结点。但是,如果删除两面两棵树的根结点8,此时并不存在根结点,因此会造成错误。所以,针对删除根结点这一特殊情况需要进行特殊的处理,即:如果根结点的左子树为空,则root=root->right反之同理。因此,可以将删除结点的代码完善如下:
 

bool Erase(const K& val)
{
	Node* cur = _root;
	Node* parent = nullptr;

	while (cur)
	{
		if (val > cur->_val)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (val < cur->_val)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			//找到了需要删除的结点
			if (cur->_left == nullptr)
			{
				if (cur == _root)
				{
					_root = cur->_right;
				}
				else
				{
					if (cur == parent->_right)
					{
						parent->_right = cur->_right;
					}
					else
					{
						parent->_left = cur->_right;
					}
				}
				delete cur;
				return true;
			}
			else if (cur->_right == nullptr)
			{
				if (cur == _root)
				{
					_root = _root->_left;
				}
				else
				{
					if (cur == parent->_right)
					{
						parent->_right = cur->_left;
					}
					else
					{
						parent->_left = cur->_left;
					}
				}
				delete cur;
				return true;
			}
			else
			{
				Node* rightMinParent = cur;
				Node* rightMin = cur->_right;
				
				while (rightMin->_left )
				{
					rightMinParent = rightMin;
					rightMin = rightMin->_left;
				}

				cur->_val = rightMin->_val;
				if (rightMin == rightMinParent->_left)
				{
					rightMinParent->_left = rightMin->_right;
				}
				else
				{
					rightMinParent->_right = rightMin->_right;

				}
				delete rightMin;
				return true;
			}
		}

	}
	return false;
}

3.搜索二叉树的实现——递归实现:

       在上面实现中序遍历时说到,由于中序遍历需要树的根结点,但是根结点的访问权限是private的,因此,并不能直接使用,需要对中序遍历函数进行一次嵌套。在本处的递归实现搜索二叉树部分,由于递归操作均需要用到根结点,因此,都需要利用函数嵌套来解决这个问题。

3.1 搜索二叉树功能实现——搜索结点:

原理相同,只是将代码改为了递归形式,因此不进行过多解释,只给出代码:

void FindR(const K& val)
{
	_FindR(_root, val);
}
bool _FindR(Node* root, const K& val)
{
	if (root == nullptr)
	{
		return false;
	}

	if (val > root->_val)
	{
		return _FindR(root->_right, val);
	}
	else if (val < root->_val)
	{
		return _FindR(root->_left, val);
	}
	else
	{
		return true;
	}
}

3.2 搜索二叉树功能实现——插入结点:

       对于递归实现插入结点这一功能,只需要注意,在给定函数的形式参数用于接收二叉树的根结点时,使用引用可以帮助完成插入结点,具体如下:

void Insert(const K& val)
{
	_Insert(_root, val);
}
bool _Insert(Node*& root, const K& val)
{
	if (root == nullptr)
	{
		root = new Node(val);
		return true;
	}

	if (val > root->_val)
	{
		return _Insert(root->_right, val);
	}
	else if (val < root->_val)
	{
		return _Insert(root->_left, val);
	}
	else
	{
		return false;
	}
}

3.3 搜索二叉树功能实现——删除结点:

原理与循环实现相同,本处只给出代码:
 

void EraseR(const K& val)
{
	_EraseR(_root, val);
}
bool _EraseR(Node*& root, const K& val)
{
	if (root == nullptr)
	{
		return false;
	}

	if (val > root->_val)
	{
		return _EraseR(root->_right, val);
	}
	else if (val < root->_val)
	{
		return _EraseR(root->_left, val);
	}
	else
	{
		Node* del = root;
		if (root->_left == nullptr)
		{
			root = root->_right;
		}
		else if (root->_right == nullptr)
		{
			root = root->_left;
		}
		else
		{
			Node* rightMin = root->_right;
			while (rightMin->_left)
			{
				rightMin = rightMin->_left;
			}

			swap(root->_val, rightMin->_val);

			return _EraseR(root->_right, val);

		}

		delete del;
		return true;
	}
}

3.4 搜索二叉树功能实现——拷贝构造、赋值重载、析构:

本部分只作为额外补充,对于搜索二叉树的意义并不重要,因此只给出代码:

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

Node* Copy(Node* root)
{
	if (root == nullptr)
	{
		return nullptr;
	}

	Node* newnode = new Node(root->_val);
	newnode->_left = Copy(root->_left);
	newnode->_right = Copy(root->_right);
	return newnode;

}

~BSTree()
{
	Destory(_root);
}

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

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

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

	delete root;
}


4. 代码总览:

#include<iostream>
using namespace std;

template<class K>
struct BSTreeNode
{
	typedef BSTreeNode<K> Node;

	BSTreeNode(const K& val)
		: _val(val)
		, _left(nullptr)
		, _right(nullptr)
	{}

	K _val;
	Node* _left;
	Node* _right;
};

template<class K>
class BSTree
{
public:
	typedef BSTreeNode<K> Node;

	BSTree() =default;

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

	Node* Copy(Node* root)
	{
		if (root == nullptr)
		{
			return nullptr;
		}

		Node* newnode = new Node(root->_val);
		newnode->_left = Copy(root->_left);
		newnode->_right = Copy(root->_right);
		return newnode;

	}

	~BSTree()
	{
		Destory(_root);
	}

	BSTree<K>& operator=(BSTree<K> t)
	{
		swap(_root, t._root);
		return *this;
	}
	void Destory(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

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

		delete root;
	}
	bool Find(const K& val)
	{
		Node* cur = _root;
		while (cur)
		{
			if (val > cur->_val)
			{
				cur = cur->_right;
			}
			else if (val < cur->_val)
			{
				cur = cur->_left;
			}
			else
			{
				return true;
			}
		}
		return false;
	}

	bool Insert(const K& val)
	{
		if (_root == nullptr)
		{
			_root = new Node(val);
		}

		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (val > cur->_val)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (val < cur->_val)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(val);

		if (val > parent->_val)
		{
			parent->_right = cur;
			return true;
		}
		else if (val < parent->_val)
		{
			parent->_left = cur;
			return true;
		}
	}

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

		_InOrder(root->_left);
		cout << root->_val << ' ';
		_InOrder(root->_right);
	}

	


	bool Erase(const K& val)
	{
		Node* cur = _root;
		Node* parent = nullptr;

		while (cur)
		{
			if (val > cur->_val)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (val < cur->_val)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				//找到了需要删除的结点
				if (cur->_left == nullptr)
				{
					if (cur == _root)
					{
						_root = cur->_right;
					}
					else
					{
						if (cur == parent->_right)
						{
							parent->_right = cur->_right;
						}
						else
						{
							parent->_left = cur->_right;
						}
					}
					delete cur;
					return true;
				}
				else if (cur->_right == nullptr)
				{
					if (cur == _root)
					{
						_root = _root->_left;
					}
					else
					{
						if (cur == parent->_right)
						{
							parent->_right = cur->_left;
						}
						else
						{
							parent->_left = cur->_left;
						}
					}
					delete cur;
					return true;
				}
				else
				{
					Node* rightMinParent = cur;
					Node* rightMin = cur->_right;
					
					while (rightMin->_left )
					{
						rightMinParent = rightMin;
						rightMin = rightMin->_left;
					}

					cur->_val = rightMin->_val;
					if (rightMin == rightMinParent->_left)
					{
						rightMinParent->_left = rightMin->_right;
					}
					else
					{
						rightMinParent->_right = rightMin->_right;

					}
					delete rightMin;
					return true;
				}
			}

		}
		return false;
	}


	void FindR(const K& val)
	{
		_FindR(_root, val);
	}


	void InsertR(const K& val)
	{
		_InsertR(_root, val);
	}


	void EraseR(const K& val)
	{
		_EraseR(_root, val);
	}

	

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


private:
	Node* _root = nullptr;

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

		if (val > root->_val)
		{
			return _FindR(root->_right, val);
		}
		else if (val < root->_val)
		{
			return _FindR(root->_left, val);
		}
		else
		{
			return true;
		}
	}

	bool _InsertR(Node*& root, const K& val)
	{
		if (root == nullptr)
		{
			root = new Node(val);
			return true;
		}

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

	bool _EraseR(Node*& root, const K& val)
	{
		if (root == nullptr)
		{
			return false;
		}

		if (val > root->_val)
		{
			return _EraseR(root->_right, val);
		}
		else if (val < root->_val)
		{
			return _EraseR(root->_left, val);
		}
		else
		{
			Node* del = root;
			if (root->_left == nullptr)
			{
				root = root->_right;
			}
			else if (root->_right == nullptr)
			{
				root = root->_left;
			}
			else
			{
				Node* rightMin = root->_right;
				while (rightMin->_left)
				{
					rightMin = rightMin->_left;
				}

				swap(root->_val, rightMin->_val);

				return _EraseR(root->_right, val);

			}

			delete del;
			return true;
		}
	}
};
#include"BinarySearchTree.h"


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

	/*for (auto e : a)
	{
		t.EraseR(e);
	}*/
	
	t.InOrder();

	BSTree<int> t1(t);
	t1.InOrder();
	t1 = t;

	return 0;
}

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

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

相关文章

seleniumUI自动化实例(登录CSDN页面)

今天分享一个CSDN登录模块的登录场景 1.配置文件 CSDNconf.py&#xff1a; from selenium import webdriver options webdriver.ChromeOptions() options.binary_location r"D:\Program Files\360\360se6\Application\360se.exe" # 360浏览器安装地址 driver w…

Spark 3.5.0 特性速览

介绍 Spark 3系列已经发布了第六版3.5.0&#xff0c;目前最新3.5.1。 使用最广泛的大数据可扩展计算引擎。数以千计的公司&#xff0c;包括 80% 的财富 500 强企业&#xff0c;都在使用 Apache Spark。来自业界和学术界的 2000 多名开源项目贡献者。 Apache Spark 3.5.0 是…

单片机——数电复习(1)

1逻辑门电路的分类 2高电平与低电平的含义 1逻辑门电路的分类 1.1按了逻辑功能分 与门 或门 非门 异或门 与非门 或非门 与或非门 与门&#xff08;全1为1&#xff09;YAB 全为高电平才输出高电平 使用仿真看现象 当只有一个输入只有一个为1时小灯不亮 当输入都为1时 &a…

【RabbitMQ | 第四篇】基于RabbitMQ实现延迟队列

文章目录 4.基于RabbitMQ实现延迟队列4.1延迟队列定义4.2基于DLX&#xff08;死信交换机&#xff09;实现延迟队列4.2.1实现思路4.2.2主要流程4.2.3实战&#xff08;1&#xff09;创建两个消息队列&#xff1a;原始消息队列、死信队列 and 为原始消息队列关联私信交换机&#x…

搜维尔科技:OptiTrack提供了一个通用、精确、灵活和可监控的系统!

MELS集成OptiTrack与最前沿的虚拟生产阶段 在加拿大蒙特利尔&#xff0c;MELS Studios and Postproduction设有20个工作室&#xff0c;以满足各种规模的电影和电视项目的需求。凭借先进的技术设施和专业的技术团队&#xff0c;梅尔斯为电影行业的合作伙伴提供从摄影棚和设备租…

Python分析无人驾驶汽车在桂林市文旅行业推广的问卷

【项目背景】 通过市场调研、文本分析、访谈和问卷调查等方法&#xff0c;探讨&#xff1a; 网民对无人驾驶汽车出行服务的态度。无人驾驶安全员的行业背景。不同人群在旅游时的交通选择偏好。游客及当地居民对桂林市文旅路线的交通满意度。乘客对无人驾驶汽车的满意度。桂林…

策略模式实战

项目推荐最近开发完成的项目中使用到了策略模式&#xff0c;实现多种支付方式&#xff0c;避免了后期支付方式if-else代码的冗余&#xff0c;也有利于后期支付的一个扩展。同时这个项目非常适合于做毕设&#xff0c;想了解这个项目的同学可以联系我QQ&#xff1a;3808898981 前…

【项目管理后台】Vue3+Ts+Sass实战框架搭建一

项目管理后台 建立项目最好是卸载Vetur 新建.env.d.ts文件安装Eslint安装校验忽略文件添加运行脚本 安装prettier新建.prettierrc.json添加规则新建.prettierignore忽略文件 安装配置stylelint新建.stylelintrc.cjs 添加后的运行脚本配置husky配置commitlint配置husky 强制使用…

【十三】【算法分析与设计】二分查找(1)

704. 二分查找 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否则返回 -1。 示例 1: 输入: nums [-1,0,3,5,9,12], target 9 输出: 4…

HarmonyOS NEXT应用开发之元素超出List区域

介绍 本示例介绍在List组件内实现子组件超出容器边缘的布局样式的实现方法。 List组件clip属性默认为true&#xff0c;超出容器边缘的子组件会按照List的布局范围被裁剪。为此&#xff0c;可以在List组件内部添加一个占位的ListItem&#xff0c;以达到预期的布局效果。List占…

机器学习-05-特征工程

总结 本系列是机器学习课程的系列课程&#xff0c;主要介绍机器学习中特征工程部分。 参考 机器学习之特征工程详解 特征工程&#xff08;Feature Engineering&#xff09; 特征工程是指使用专业的背景知识和技巧处理数据&#xff0c;使得特征能在机器学习算法上发生更好的…

弹幕视频网站|基于JSP技术+ Mysql+Java+ Tomcat的弹幕视频网站设计与实现(可运行源码+数据库+设计文档)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含java&#xff0c;ssm&#xff0c;springboot的平台设计与实现项目系统开发资源&#xff08;可…

【重温设计模式】状态模式及其Java示例

状态模式的基本概念 在编程世界的大海中&#xff0c;各种设计模式就如同灯塔&#xff0c;为我们的代码编写指明方向。其中&#xff0c;状态模式是一种行为设计模式&#xff0c;它让你能在一个对象的内部状态改变时改变其行为&#xff0c;使得对象看起来就像改变了其类一样。这…

1949年-2021年历史县级行政区划分布数据 中国行政村边界数据、乡镇街道边界、行政区划边界

数据范围&#xff1a;全国历史年份县级行政区划 数据类型&#xff1a;面状数据&#xff0c;全国各省市县行政区划边界 数据属性&#xff1a;标准行政区划编码 时间属性&#xff1a;1949年-2021年 分辨率&#xff1a;1:2万--1:5万 数据格式&#xff1a;SHP数据&#xff08;…

力扣刷题之17.电话号码的字母组合

仅做学习笔记之用。 题目&#xff1a; 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 示例 1&#xff1a; 输入&#x…

自注意力机制的理解

一、自注意力要解决什么问题 循环神经网络由于信息传递的容量以及梯度消失问题&#xff0c;只能建立短距离依赖关系。为了建立长距离的依赖关系&#xff0c;可以增加网络的层数或者使用全连接网络。但是全连接网络无法处理变长的输入序列&#xff0c;另外&#xff0c;不同的输…

从服务器到云原生:企业IT基础设施的演进之路

随着数字经济的迅猛发展&#xff0c;企业IT数字化转型已成为推动业务创新和提升竞争力的关键。在这一转型过程中&#xff0c;基础设施的建设与升级显得尤为重要。企业需要不断优化和更新他们的基础设施&#xff0c;以适应不断变化的市场需求和技术发展。本文将探讨企业IT数字化…

软件测试 -- Selenium常用API(java)

写在前面 // 如果文章有问题的地方, 欢迎评论区或者私信指正 目录 什么是Selenium 一个简单的用例 元素定位 id定位 xpath定位 name定位 tag name 定位和class name 定位 操作元素 click send_keys submit text getAttribute 1. 获取元素的 class 属性 2. 获取元素…

Linux TCP参数——tcp_allowed_congestion_control

tcp_allowed_congestion_control 设置允许普通进程使用的拥塞控制算法。这个参数的值阈是tcp_available_congestion_control参数的子集。默认值为"reno"加上tcp_congestion_control参数设置的算法。 reno 慢启动阶段&#xff1a;在开始的时候&#xff0c;cwnd按指数…

vue项目:使用xlsx导出Excel数据

文章目录 一、安装xlsx二、报错及解决三、编写公共方法四、方法使用 一、安装xlsx 执行命令&#xff1a;npm i xlsx file-saver --save 二、报错及解决 使用时&#xff1a;import XLSX from "xlsx"; 发现如下报错信息 报错原因&#xff1a;xlsx版本不兼容。 解…