C++模拟实现二叉搜索树

news2024/10/8 16:31:11

目录

1.二叉搜索树的概念

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

3.二叉搜索树的结构和中序遍历

3.1二叉搜索树中节点的结构

3.2二叉搜索树的结构 

3.3中序遍历 

4.二叉搜索树的插入

5.二叉搜索树的查找 

6.二叉树搜索树的删除

7. 二叉搜索树的默认成员函数

8.参考代码 

9.二叉搜索树key和key/value使用场景

9.1key搜索场景

9.2key/value搜索场景


1.二叉搜索树的概念

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

        (1)若它的左子树不为空,则左子树上所有节点的值都小于根节点的值

        (2)若它的右子树不为空,则右子树上所有节点的值都大于根节点的值

        (3)它的左右子树也分别为二叉搜索树

        (4)二叉搜索树可以支持插入相等的值,也可以不支持插入相等的值,具体看使用场景定义。如下图,左边是不支持插入相等的值,右边支持插入相等的值。

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

        最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其高度为:O(log_{2}N)。最差情况下,二叉树退化为单支树(或者类似单支),其高度为:O(N)。所有综合而言二叉搜索树增删查的时间复杂度为:O(N)

        另外需要说明的是,二分查找也可以实现O(log_{}N)级别的查找效率,但是二分查找有两个大的缺陷:
        (1)需要存储在支持下标随机访问的结构中,并且有序。

        (2)插入和删除数据效率很低,因为存储在下标随机访问的结构(例如数组)中,插入和删除数据一般需要挪动数据。

        所以二叉搜索树就能体现出价值所在,即通过二叉树中序遍历就能有序的访问数据,并且插入和删除都是节点的操作,不需要挪动数据。二叉搜索树的查找效率为O(N),但是这个缺陷在后续的平衡二叉树AVL树已经红黑树中可以解决,将效率提高到O(log_{}N)

3.二叉搜索树的结构和中序遍历

3.1二叉搜索树中节点的结构

        _key为节点中的值,_left和_right分别是指向左节点和右节点的指针。

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

3.2二叉搜索树的结构 

        二叉树的结构中只用一个_root成员。 

template<class K>
class BSTree
{
	//typedef BSTNode<K> Node;
	using Node = BSTNode<K>;
private:
    Node* _root = nullptr;
};

3.3中序遍历 

       因为二叉搜索树左边比根节点小,右边比根节点大,中序遍历的顺序是:左子树,根节点,右子树,所以通过中序遍历遍历二叉搜索树,天然就是有序的。

        中序遍历需要传一个根节点,在类外面不能直接访问私有成员,所以这里的中序遍历实现在private里面,然后外面再用InOrder()函数进行封装,这里在类外面直接调用InOrder()函数就可以实习中序遍历了,不需要传入根节点。

template<class K>
class BSTree
{
	//typedef BSTNode<K> Node;
	using Node = BSTNode<K>;
public:
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
private:
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}

    Node* _root = nullptr;
};

4.二叉搜索树的插入

        插入的具体过程如下:
        (1)树为空,则直接新增节点,赋值给root。

        (2)树不为空,按二叉搜索树的性质,插入值比当前节点大往右走,插入值比当前节点小往左走,找到空位置插入新节点。

        (3)如果支持插入相等的值,插入值跟当前节点相等的值可以往右走,也可以往左走,找到空位置插入新节点。(要注意保持逻辑的一致性,插入相等的值不要一会往右走,一会往左走)

        这里以插入下列这棵树为例子:

        1.不允许相同的值插入 

//不允许相同的值插入
		bool Insert(const K& key)    //K是插入的值的类型
		{
			if (_root == nullptr)    //当为空树时,直接赋值给root
			{
				_root = new Node(key);
				return true;
			}

			Node* parent = nullptr;    //parent指向cur的父亲节点
			Node* cur = _root;    

			while (cur)
			{
				if (cur->_key < key)    //当插入值key大于cur节点的值,往右走
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > key)    //当插入值key小于cur节点的值,往左走
				{
					parent = cur;
					cur = cur->_left;
				}
				else    //插入相等的值时,插入失败
				{
					return false;
				}
			}
            
            //此时找到空位置了,cur == nullptr, parent指向cur的父亲节点,进行插入
			cur = new Node(key);
			if (parent->_key < key)       //如果插入值大于parent节点的值,插入在parent的右边
			{
				parent->_right = cur;
			}
			else    //如果插入值小于parent节点的值,插入在parent的左边
			{
				parent->_left = cur;
			}

			return true;
		}

        2.允许相同的值插入 

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

			cur = new Node(key);
			if (parent->_key <= key)    //相同的值插入到parent的右边
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}

			return true;
		}

        实现上述二叉树的插入(不允许相同的值插入):

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

	t.Insert(16);
	t.InOrder();

	return 0;
}

5.二叉搜索树的查找 

        (1)从根节点开始,查找x,x比根的值大则往右边走继续查找,反之,往左边走继续查找。

        (2)最多查找高度次,走到空还没找到,这个值不存在。

        (3)如果不支持插入相等的值,找到x即可返回。

        (4)如果支持插入相等的值,意味着有多个值为x的节点存在,一般要求查找中序的第一个x。如下图查找3,要找到1的右孩子的那个3进行返回。

 

        查找的实现其实在插入中就已经实现了,插入中的第一个步骤就是要先进行查找,看是否有相同的值。 这里实现的是没用相同的值的查找,找到了直接返回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;
		}

6.二叉树搜索树的删除

        首先查找元素是否在二叉树中,如果不存在,则返回false。如果元素存在则存在以下四种 情况(假设要删除的节点为N):

        (1)N的左右孩子均为空。

        (2)N的左孩子为空,右孩子不为空。

        (3)N的右孩子为空,左孩子不为空。

        (4)N的左右孩子均不为空。

        上述四种情况,对应进行分别处理:

        (1)把N节点的父亲节点指向N节点的指针置为nullptr,然后直接删除N节点(情况1可以当成2或者3处理)

        (2)把N节点的父亲节点指向N节点的指针指向N的右孩子,直接删除N节点。

        (3)把N节点的父亲节点指向N节点的指针指向N的左孩子,直接删除N节点。

        (4)无法直接删除N节点,因为直接删除N节点之后,左右孩子与这棵树就断掉了,只能用替换法删除。找到N左子树中值最大的节点R(即左子树的最右节点)或者N右子树中值最小的节点R(即右子树的最左节点)替代N,因为这两个节点替代N之后,二叉树左子树小于根右子树大于根的性质没有被破坏。替代N的意思是交换N和R两个节点的值,转而删除R节点,这样R节点就是上述情况1,2,3中的一种,直接删除即可。

    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)
					{
						//处理删除的节点是_root的情况
						if (cur == _root)
						{
							_root = cur->_right;
						}
						else
						{
							//如果cur是parent的左节点,让parent的左节点指向cur的右节点
							if (parent->_left == cur)
							{
								parent->_left = cur->_right;
							}
							else
							{
								parent->_right = cur->_right;
							}
						}
						delete cur;

					}
					else if (cur->_right == nullptr)    //2. 右为空
					{
						if (cur == _root)
						{
							_root = cur->_left;
						}
						else
						{
							if (parent->_left == cur)
							{
								parent->_left = cur->_left;
							}
							else
							{
								parent->_right = cur->_left;
							}
						}
						delete cur;
					}
					else    //3. 左右都不为空
					{
						//replace可以是右子树的最左节点或者左子树的最右节点
						//这里的replaceParent直接给cur是为了处理删除cur时cur右子树的根就是右
                        //子树的最左节点,此时就不会进入下列循环,replaceParent就没有更新
						Node* replaceParent = cur;
						Node* replace = cur->_right;
						while (replace->_left)
						{
							replaceParent = replace;
							replace = replace->_left;
						}

						cur->_key = replace->_key;
						//这里如果右子树的根节点就是右子树的最左节点时,这种情况下对于
                        //replaceParent来说,replace就不是replaceParent的左孩子了
						//replace就变成了上述左为空的情况了,这时就需要判断replace是
                        //replaceParent的左孩子还是右孩子了
						if (replaceParent->_left == replace)
							replaceParent->_left = replace->_right;
						else
							replaceParent->_right = replace->_right;
						delete replace;
					}

					return true;
				}
			}

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

	t.InOrder();


	for (auto e : a)
	{
		t.Erase(e);
		t.InOrder();
	}
	return 0;
}

7. 二叉搜索树的默认成员函数

         这里的拷贝构造和析构都实现在private里面,在public里面进行封装,和中序遍历的实现一样。拷贝构造的实现就是通过递归的方式前序遍历原二叉搜索树,然后每遍历一个节点就复制一个节点。赋值运算符的实现和之前STL容器的赋值运算符实现类似。析构函数的实现是通过一个后序遍历对二叉树进行一个节点一个节点的释放。

template<class K>
class BSTree
{
public:
    BSTree() {}

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

    BSTree& operator=(BSTree tmp)
    {
	    swap(_root, tmp._root);
	    return *this;
    }

    ~BSTree()
    {
	    Destroy(_root);
	    _root = nullptr;
    }
private:
    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;
	}
};

8.参考代码 

//BinarySearchTree.h
#pragma once
namespace key
{
	template<class K>
	struct BSTNode
	{
		K _key;
		BSTNode<K>* _left;
		BSTNode<K>* _right;
		BSTNode(const K& key)
			:_key(key)
			, _left(nullptr)
			, _right(nullptr)
		{}
	};

	template<class K>
	class BSTree
	{
		//typedef BSTNode<K> Node;
		using Node = BSTNode<K>;
	public:
		BSTree() {}

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

		//传值传参是一种拷贝,会调用拷贝构造,构造出一个临时对象
		BSTree& operator=(BSTree tmp)
		{
			swap(_root, tmp._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;
		}

		//允许相等的值插入,下面写的逻辑是相等的值插入root的右边
		//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;
		//		}
		//	}

		//	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)
					{
						//处理删除的节点是_root的情况
						if (cur == _root)
						{
							_root = cur->_right;
						}
						else
						{
							//如果cur是parent的左节点,让parent的左节点指向cur的右节点
							if (parent->_left == cur)
							{
								parent->_left = cur->_right;
							}
							else
							{
								parent->_right = cur->_right;
							}
						}
						delete cur;

					}
					else if (cur->_right == nullptr)    //2. 右为空
					{
						if (cur == _root)
						{
							_root = cur->_left;
						}
						else
						{
							if (parent->_left == cur)
							{
								parent->_left = cur->_left;
							}
							else
							{
								parent->_right = cur->_left;
							}
						}
						delete cur;
					}
					else    //3. 左右都不为空
					{
						//replace可以用右子树的最左节点或者左子树的最右节点
						//这里的replaceParent直接给cur是为了处理删除cur时cur右子树的根就是右子树的最左节点,此时就不会进入下列循环,replaceParent就没有更新
						Node* replaceParent = cur;
						Node* replace = cur->_right;
						while (replace->_left)
						{
							replaceParent = replace;
							replace = replace->_left;
						}

						cur->_key = replace->_key;
						//这里如果右子树的根节点就是右子树的最左节点时,这种情况下对于replaceParent来说,replace就不是replaceParent的左孩子了
						//replace就变成了上述左为空的情况了,这时就需要判断replace是replaceParent的左孩子还是右孩子了
						if (replaceParent->_left == replace)
							replaceParent->_left = replace->_right;
						else
							replaceParent->_right = replace->_right;
						delete replace;
					}

					return true;
				}
			}

			return false;
		}

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

	private:
		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;
		}
		void _InOrder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}
			_InOrder(root->_left);
			cout << root->_key << " ";
			_InOrder(root->_right);
		}


		Node* _root = nullptr;
	};
}

9.二叉搜索树key和key/value使用场景

9.1key搜索场景

        只有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索到的值,搜索场景只需要判断key在不在。key的搜索场景实现的⼆叉树搜索树⽀持增删查,但是不⽀持修改,修改key破坏搜索树结构了。

        场景1:⼩区⽆⼈值守⻋库,⼩区⻋库买了⻋位的业主⻋才能进⼩区,那么物业会把买了⻋位的业主的⻋牌号录⼊后台系统,⻋辆进⼊时扫描⻋牌在不在系统中,在则抬杆,不在则提⽰⾮本⼩区⻋辆,⽆法进⼊。

        场景2:检查⼀篇英⽂⽂章单词拼写是否正确,将词库中所有单词放⼊⼆叉搜索树,读取⽂章中的单词,查找是否在⼆叉搜索树中,不在则波浪线标红提⽰。

9.2key/value搜索场景

        每⼀个关键码key,都有与之对应的值value,value可以任意类型对象。树的结构中(结点)除了需要存储key还要存储对应的value,增/删/查还是以key为关键字⾛⼆叉搜索树的规则进⾏⽐较,可以快速查找到key对应的value。key/value的搜索场景实现的⼆叉树搜索树⽀持修改,但是不⽀持修改key,修改key破坏搜索树结构了,可以修改value。
        场景1:简单中英互译字典,树的结构中(结点)存储key(英⽂)和vlaue(中⽂),搜索时输⼊英⽂,则同时查找到了英⽂对应的中⽂。
        场景2:商场⽆⼈值守⻋库,⼊⼝进场时扫描⻋牌,记录⻋牌和⼊场时间,出⼝离场时,扫描⻋牌,查找⼊场时间,⽤当前时间-⼊场时间计算出停⻋时⻓,计算出停⻋费⽤,缴费后抬杆,⻋辆离场。
        场景3:统计⼀篇⽂章中单词出现的次数,读取⼀个单词,查找单词是否存在,不存在这个说明第⼀次出现,(单词,1),单词存在,则++单词对应的次数。

        这里给出一个key/value二叉搜索树的代码仅供参考,与key场景的代码相似,就是多存储了一个value值:

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

	template<class K, class V>
	class BSTree
	{
		//typedef BSTNode<K> Node;
		using Node = BSTNode<K, V>;
	public:
		BSTree() {}

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

		BSTree& operator=(BSTree tmp)
		{
			swap(_root, tmp._root);
			return *this;
		}

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

		//不允许相同的值插入
		bool Insert(const K& key, const V& value)
		{
			if (_root == nullptr)
			{
				_root = new Node(key, value);
				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, value);
			if (parent->_key < key)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}

			return true;
		}

		//允许相等的值插入,下面写的逻辑是相等的值插入root的右边
		/*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;
				}
			}

			cur = new Node(key);
			if (parent->_key <= key)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}

			return true;
		}*/

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

		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)
					{
						//处理删除的节点是_root的情况
						if (cur == _root)
						{
							_root = cur->_right;
						}
						else
						{
							//如果cur是parent的左节点,让parent的左节点指向cur的右节点
							if (parent->_left == cur)
							{
								parent->_left = cur->_right;
							}
							else
							{
								parent->_right = cur->_right;
							}
						}
						delete cur;

					}
					else if (cur->_right == nullptr)    //2. 右为空
					{
						if (cur == _root)
						{
							_root = cur->_left;
						}
						else
						{
							if (parent->_left == cur)
							{
								parent->_left = cur->_left;
							}
							else
							{
								parent->_right = cur->_left;
							}
						}
						delete cur;
					}
					else    //3. 左右都不为空
					{
						//replace可以用右子树的最左节点或者左子树的最右节点
						//这里的replaceParent直接给cur是为了处理删除cur时cur右子树的根就是右子树的最左节点,此时就不会进入下列循环,replaceParent就没有更新
						Node* replaceParent = cur;
						Node* replace = cur->_right;
						while (replace->_left)
						{
							replaceParent = replace;
							replace = replace->_left;
						}

						cur->_key = replace->_key;
						//这里如果右子树的根节点就是右子树的最左节点时,这种情况下对于replaceParent来说,replace就不是replaceParent的左孩子了
						//replace就变成了上述左为空的情况了,这时就需要判断replace是replaceParent的左孩子还是右孩子了
						if (replaceParent->_left == replace)
							replaceParent->_left = replace->_right;
						else
							replaceParent->_right = replace->_right;
						delete replace;
					}

					return true;
				}
			}

			return false;
		}

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

	private:
		Node* Copy(Node* root)
		{
			if (root == nullptr)
				return nullptr;
			Node* newRoot = new Node(root->_key, root->_value);
			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;
		}
		void _InOrder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}
			_InOrder(root->_left);
			cout << root->_key << ":" << root->_value << endl;
			_InOrder(root->_right);
		}

		Node* _root = nullptr;
	};
}

 

 

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

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

相关文章

常见的图像处理算法:Canny边缘检测

一、Canny边缘检测算子的由来 Canny 边缘检测算子是一种多级检测算法。1986 年由 John F. Canny 提出&#xff0c;同时提出 了边缘检测的三大准则&#xff1a; 1、低错误率的边缘检测&#xff1a;检测算法应该精确地找到图像中的尽可能多的边缘&#xff0c;尽可能的减少漏检…

【一起学Rust | 框架篇 | Tauri2.0框架】高级概念之安全特性的权限与能力

文章目录 前言一、开发前准备1. 准备项目2. 需求分析1. 监听系统热键2. 切换窗口无边框3. 切换窗口全屏 二、安装插件三、前端实现功能四、配置权限 前言 当前时间为 2024 年 9 月&#xff0c;距离Tauri 2.0 的 RC 版本发布迄今已近一个月。从 Tauri 官方渠道可以看出&#xf…

李飞飞:我不知道什么是AGI

图片来源&#xff1a;Stanford University 你对人工通用智能&#xff08;AGI&#xff09;感到困惑吗&#xff1f;这就是 OpenAI 执着于最终以“造福全人类”的方式创造的东西。你可能想认真对待他们&#xff0c;因为他们刚筹集了 66 亿美元以更接近这个目标。 但如果你仍然在…

揭秘Sui存储基金:灵活且可持续的链上数据管理解决方案

链上数据存储的方法常常被忽视&#xff0c;因为所使用的机制通常是传统和常见的。然而&#xff0c;在去中心化网络中&#xff0c;数据存储对确保数据完整性和长期可访问性至关重要。Sui的链上存储与其他区块链有所不同。 Sui存储基金是为了解决链上数据永久存储问题而设计的核…

企业架构系列(16)ArchiMate第14节:实施和迁移视角

在企业架构中&#xff0c;为了有效地规划和管理架构的变更与实施&#xff0c;通常会使用不同的视角来描述架构的不同方面。本篇涉及到三个主要视角&#xff1a;项目视角、迁移视角以及实施与迁移视角。 一、实施和迁移视角概览 1.项目视角 元素与关系&#xff1a;关注项目本身…

“网络安全等级保护测评入门:基础概念与重要性“

网络安全等级保护测评&#xff08;简称“等保测评”&#xff09;是依据国家网络安全等级保护制度&#xff0c;对信息系统安全等级进行评估和评定的过程。它是提高信息系统安全性、保障信息安全的重要手段。以下是关于等保测评的基础概念与重要性的详细解读&#xff1a; 一、等…

【钱拿不回来了,中介说开源吧】《刚体旋转的四元数模型及捷联惯性导航系统中定向算法》

《刚体旋转的四元数模型及捷联惯性导航系统中定向算法》 1. 摘要 本文深入探讨了四元数在刚体旋转描述中的核心作用以及其在捷联惯性导航系统&#xff08;SINS&#xff09;中确定方向的算法。详细阐述了四元数的理论基础、数学性质和实际应用优势&#xff0c;包括与卡丹角和欧…

小红书AI商单变现,单月收入10000+,真猛!

AI相关的话题在这两年来一直很火爆&#xff0c;很多行业也纷纷和AI结合起来&#xff0c; 从而达到更好的变现效果&#xff0c;这也是未来的发展趋势&#xff0c; 有这个工具确实能给我们的工作和生活带来一些便利 之前也拆解过很多类似的玩法&#xff0c; 比如AI古诗词、数…

软件测试之压力测试

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 压力测试 压力测试是一种软件测试&#xff0c;用于验证软件应用程序的稳定性和可靠性。压力测试的目标是在极其沉重的负载条件下测量软件的健壮性和错误处理能力&…

GSLAM——一个通用的SLAM架构和基准

GSLAM: A General SLAM Framework and Benchmark 开源地址 摘要&#xff1a; SLAM技术最近取得了许多成功&#xff0c;并吸引了高科技公司的关注。然而&#xff0c;如何同一现有或新兴算法的界面&#xff0c;一级有效地进行关于速度、稳健性和可移植性的基准测试仍然是问题。本…

芯片干货 | 同步内置MOS升压恒压5V/2A芯片FP6276B,输入限流可调

芯片概述 FP6276B是一个具有PWM/PSM控制的电流模式增压直流-直流转换器。它的PWM电路内置40mΩ高侧开关和40mΩ低侧开关使该调节器高高效。内部补偿网络还将外部组件计数最小化到只有6个。一个内部的0.6V电压被连接到误差放大器的非反相输入作为精度参考电压。内置的软启动功能…

Llama 3.2 智能代理开发教程

构建研究代理可能很复杂&#xff0c;但使用 LangChain 和 Ollama&#xff0c;它会变得更加简单和模块化。 在本教程中&#xff0c;我们将向你展示如何基于Llama 3.2创建一个研究代理&#xff0c;该代理可以路由查询、执行网络搜索并使用工作流和 LLM 的组合生成详细响应。最后…

我的书第三次重印啦,做一波活动,参与可抽现金红包~

大家好&#xff0c;我是拭心&#xff0c;好久不见。 四月份我的《Android 性能优化入门与实战》顺利出版&#xff0c;在朋友们帮忙宣传下&#xff0c;初印的两三千册很快卖完&#xff0c;四月份第二次重印&#xff0c;五个月后&#xff0c;又迎来了第三次重印。 这样的销量算不…

如何解决Lenovo笔记本电脑很快就自动休眠,自动锁屏,需要密码登录的问题

前段时间电脑经常会很快就锁屏了&#xff0c;只要离开电脑1分钟不到&#xff0c;就自动锁屏&#xff0c;然后就要输入密码登录&#xff0c;太烦了&#xff0c;后来百度和谷歌了不少帖子和方案&#xff0c;给的建议都是调整电源选项之类的参数。 尝试了各种修改参数&#xff0c…

栈和队列--DS

1. 栈(Stack) 1.1 栈的定义 **栈是一种特殊的线性表&#xff0c;其只允许在固定的一端(栈顶)进行元素插入和删除元素操作。**进行数据插入和删除操作的一段称为栈顶&#xff0c;另一端则称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)原则。 1.2 栈的核心操…

Android 无Bug版 多语言设计方案!

出海业务为什么要做多语言&#xff1f; 1.市场扩大与本地化需求&#xff1a; 通过支持多种语言&#xff0c;出海项目可以触及更广泛的国际用户群体&#xff0c;进而扩大其市场份额。 本地化是吸引国际用户的重要策略之一&#xff0c;而语言本地化是其中的核心。使用用户的母语…

E37.【C语言】动态内存管理练习题

目录 1. 答案速查 分析 源代码分析 反汇编代码分析(底层) 2. 答案速查 分析 3. 答案速查 分析 VS逐步调试 1. 求下列代码的执行结果 #include <stdio.h> char* GetMemory(void) {char p[] "hello world";return p; }void Test(void) {char* str…

软件测试学习笔记丨allure学习指南

本文转自测试人社区&#xff0c;原文链接&#xff1a;https://ceshiren.com/t/topic/32336 安装与下载 需要下载本地文件&#xff0c;并且添加到环境变量里 windows&#xff1a;下载&#xff0c;解压&#xff0c;并配置环境变量 mac&#xff1a;brew install allure 环境变量…

1688代采系统-反向海淘系统详细介绍

Onebound凡邦1688代采系统-反向海淘系统是一种专为海外买家及跨境电商提供一站式采购解决方案的平台。其核心功能和服务旨在解决跨境采购中的语言、货币等常见问题&#xff0c;并优化采购流程&#xff0c;提高采购效率。以下是对该系统的详细介绍。 一、核心功能 商品采集与展…

==与equals比较

在JVM中&#xff0c;内存分为堆内存跟栈内存。他们二者的区别是&#xff1a; 当我们创建一个对象&#xff08;new Object&#xff09;时&#xff0c;就会调用对象的构造函数来开辟空间&#xff0c;将对象数据存储到堆内存中&#xff0c;与此同时在栈内存中生成对应的引用&#…