【C++】二叉搜索树BST

news2024/11/24 21:05:44

目录

  • 1.二叉搜索树的性质
  • 2.二叉搜索树功能的实现
    • 1.二叉搜索树的框架
    • 2.插入
    • 3.查找
    • 4.删除(难点)
      • 解析
  • 3.二叉搜索树功能的递归实现
    • 1.查找递归实现
    • 2.插入递归实现
      • 递归形式中新建节点的链接问题
    • 3.删除的递归实现
  • 4.二叉搜索树部分默认成员函数实现
    • 1.构造函数
    • 2.拷贝构造函数
    • 3.析构函数
    • 4.赋值运算符重载
  • 5.二叉搜索树实现集合
  • 5.二叉搜索树增删查改的时间复杂度
  • 6.二叉搜索树的应用
    • K模型(key)
    • KV模型(key-value)

1.二叉搜索树的性质

二叉搜索树又称二叉排序树,具有以下性质:

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

搜索二叉树不允许数据冗余,也就是说其中没有重复的数据

2.二叉搜索树功能的实现

1.二叉搜索树的框架

template<class K>
struct BSTreeNode
{
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
	K _key;
};

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

	//功能:插入...
	
private:
	Node* _root = nullptr;
}

2.插入

插入的过程:

  1. 树为空,则直接新增节点,赋值给root指针(所以插入的第一个值就是根)
  2. 树不空,按二叉搜索树性质查找插入位置,插入新节点

注意:二叉搜索树不允许数据冗余,插入相同的数据返回false,用返回值用bool即可。

//插入
bool insert(const K& key)
{
	//空树情况
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}

	Node* cur = _root;
	Node* parent = nullptr;//记录父节点,插入后用以链接插入的数据

	//找到应该插入的叶子结点位置,
	//cur为空时就是该位置的下一个位置(应该插入的位置)
	while (cur)
	{
		if (key > cur->_key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (key < cur->_key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}
	//开辟空间初始化要插入的对象
	cur = new Node(key);

	//链接插入的子节点
	if (key > parent->_key)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}

	return true;
}

3.查找

//查找
bool Find(const K& key)
{
	Node* cur = _root;
	
	while (cur)
	{
		if (key > cur->_key)
		{
			cur = cur->_right;
		}
		else if (key < cur->_key)
		{
			cur = cur->left;
		}
		else
		{
			return true;
		}
		
		return false;
	}
}

4.删除(难点)

删除分为三种情况:

  1. 删除节点为叶子结点
    在这里插入图片描述

  1. 删除节点的左子树为空或右子树为空
    在这里插入图片描述

  1. 删除节点的左右子树都不为空
    在这里插入图片描述

因为左子树的最大节点与右子树的最小节点都可以满足二叉搜索树的性质:

  • 左子树上所有节点的值都小于根节点的值
  • 右子树上所有节点的值都大于根节点的值

代码实现:

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

	while (cur)
	{
		//查找要删除的节点
		if (key > cur->_key)
		{
			cur = cur->_right;
		}
		else if (key < cur->_key)
		{
			cur = cur->_left;
		}
		else//key == cur->_key找到了 ==> 删除
		{
			//0.要删除的节点左右子树都为空
			if (cur->_left == nullptr && cur->_right == nullptr)
			{
				parent->_left = nullptr;
				parent->_right = nullptr;
				delete cur;
			}
			//1.要删除的节点只有右子树,没有左子树
			else if (cur->_left == nullptr)
			{
				//如果要删除的是根,没有父节点,直接更新_root即可
				if (cur == _root)
				{
					_root = cur->_right;
				}
				else
				{
					if (parent->_left == cur)
					{
						parent->_left == cur->_right;
					}
					else//parent->_right == cur
					{
						parent->_right == cur->_right;
					}
				}
				delete cur;
			}
			//2.要删除的节点只有左子树,没有右子树
			else if (cur->_right == nullptr)
			{
				//如果要删除的是根,没有父节点,直接更新_root即可
				if (cur == _root)
				{
					_root = cur->_lift;
				}
				else
				{
					if (parent->_left == cur)
					{
						parent->_left == cur->_left;
					}
					else//parent->_right == cur
					{
						parent->_right == cur->_left;
					}
				}
				delete cur;
			}
			//3.要删除节点的左右子树都不为空
			else
			{
				//查找 a 或 b :
				//a.右子树的最小节点-->右子树的最左节点
				//b.左子树的最大节点-->左子树的最右节点

				//查找右子树的最左节点
				Node* Pmin_Right = cur;
				Node* min_Right = cur->_right;

				while (min_Right->_left)
				{
					Pmin_Right = min_Right;
					min_Right = min_Right->_left;
				}

				//将min_Right的值给cur,等于删除了cur,之后释放min_Right即可
				cur->_key = min_Right->_key;

				if (Pmin_Right->_left == min_Right)
				{
					Pmin_Right->_left = min_Right->_right;
				}
				else
				{
					Pmin_Right->_right = min_Right->_right;
				}

				delete min_Right;
			}
		}
	}
}

解析

注意删除节点的时候,都需要记录一下删除节点的父节点,因为需要清除父节点的指针或者需要修改父节点的子树!

1.在树中查找要删除的节点:
在这里插入图片描述

2.删除要分三种情况:
a.要删除的节点为叶子结点,左右子树都为空:
直接删除即可。
在这里插入图片描述


b.要删除的节点只有一个子树:
删除后要托孤,将子树给删除节点的父节点。
注意:删除的节点如果是根节点,父节点为nullptr,要特殊处理,要不然会报错!!
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述


c.要删除的节点有两个子树:
删除该节点后,无法托孤给父节点,因为一个节点只能链接两个子节点。
所以要特殊处理:
从该节点的左子树或者右子树中查找一个满足二叉搜索树规则的节点来替换该节点,不就等于删除了该节点吗?
——满足条件的节点:1.左子树的最大节点;2.右子树的最小节点;
那么为什么这两个节点满足条件呢?
——因为这两个节点满足二叉搜索树的规则:左子树的所有节点都小于该节点,右子树的所有节点都大于该节点。
那么这两个节点应该如何找到呢?
——左子树的最小节点:左子树的最靠右一个节点;右子树的最大节点:右子树的最靠左一个节点;

1.这里以右子树的最左节点为例:
(记录其父节点Pmin_Right)
在这里插入图片描述
2.找到对应的节点以后,对其与要删除的节点进行替换:
注意这里要分两种情况:
注意:min_Right已经是右子树的最左节点了,所以它不可能有左子树,也就是只可能存在min_Right->right

情况一:
这种情况下,Pmin_Right->left == min_Right,可以直接将min_Right->right赋值给Pmin_Right->left。(因为是最左节点,所以只可能是赋值给父节点的左子树)
在这里插入图片描述

情况二:
这种情况下Pmin_Right->left != min_Right,Pmin_Right->right == min_Right,则要将min_Right->right赋值给Pmin_Right->right。(min_Right不可能有左子树,因为它是最左节点)在这里插入图片描述
实现:
在这里插入图片描述

3.二叉搜索树功能的递归实现

1.查找递归实现

//查找递归实现
bool FindR(const K& key)
{
	return _FindR(_root, key);
}

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

2.插入递归实现

bool InsertR(const K& key)
{
	return _InsertR(_root, key);
}

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

	if (root->_key < key)
	{
		return _InsertR(root->_right, key);
	}
	else if (root->_key > key)
	{
		return _InsertR(root->_left, key);
	}
	else//root->_key == key
	{
		return false;
	}
}

递归形式中新建节点的链接问题

解析:递归形式中链接的问题
在插入递归实现的时候,需要新建立节点然后再链接节点,在传root参数的时候,使用传引用传参,就可以做到自动链接新建的节点。
当我们在当前函数栈帧root = new Node(key)创建新的节点时,因为用了引用,其实这里的root就是上一个函数栈帧中的root->right,所以在创建节点完成,返回的时候就等同于完成了链接,其本质就等同于:root->_right = new Node(key);
在这里插入图片描述

3.删除的递归实现

//删除递归实现
bool EraseR(const K& key)
{
	return _EraseR(_root, key);
}

bool _EraseR(Node*& root, const K& key)
{
	if(root == nullptr)
	{
		return false;
	}
	if(root->_key < key)
	{
		return _EraserR(root->_right, key);
	}
	else if (root->_key > key)
	{
		return _EraseR(root->_left, key);
	}
	else//key == _key
	{
		//找到了,开始删除
		Node* del = root;
		if(root->_left == nullptr)
		{
			root = root->_right;
		}
		else if(root->_right == nullptr)
		{
			root = root->_left;
		}
		else
		{
			//左右子树都在,找左子树的最大节点/右子树最小节点
			Node* maxleft = root->_left;
			while(maxfile->_right)
			{
				maxleft = maxleft->_right;
			}
			swap(root->_key, maxleft->_key);
			
			return _EraseR(root->_left, key);
		}
		
		delete del;
		return true;
	}
}

解析:
递归删除与普通删除的实现原理相同:
1.要删除的节点为叶子结点,左右子树都为空;
2.要删除的节点只有一个子树;
3.要删除的节点有两个子树;
三种方式删除后都需要delete释放该节点,所以需要用一个del变量来记录要删除的root指针。(删除后root指针置空找不到了)


1.其中1和2两种方式可以用同一种方式解决:
两种情况可以通过下面一段代码完成:
在这里插入图片描述

要删除的节点为叶子结点:
因为root->_left == nullptr,则root = root->_right = nullptr,最后delete del后,即删除完成该节点。
在这里插入图片描述
要删除的节点只有一个子树:
root->_right == nullptr,root = root->_left,直接用其子节点覆盖该节点,最后释放del该节点即可。
在这里插入图片描述


2.要删除的节点有两个子树:
思路与非递归实现删除的思路相同,寻找左子树的最大节点或右子树的最小节点后进行替换然后删除即可。
进而引入递归的思路:
找到左子树的最大节点(右子树的最小节点)后,将其与要删除的节点进行swap交换key,将删除有两个子树节点的问题转化为删除没有子树节点的问题或只有一个子树节点的问题。
交换完成后再次递归进入左子树重新查找key,找到后直接删除即可(问题3转化为1、2情况来删除)。
在这里插入图片描述
在这里插入图片描述


4.二叉搜索树部分默认成员函数实现

1.构造函数

//强制生成默认构造,root声明时候给了缺省值nullptr
BSTree() = default;

解析:
C++11的新特性:default
在函数声明后加=default,将该函数声明为 default 函数,编译器将为显式声明的default函数自动生成函数体。

defaulted函数特性仅适用于类的特殊成员函数,且该特殊成员函数没有默认参数。
defaulted函数既可以在类体里(inline)定义,也可以在类体外定义。

2.拷贝构造函数

//拷贝构造
BSTree(const BSTree<K>& t)
{
	_root = copy(t._root);
}

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

3.析构函数

//析构(递归)
~BSTree()
{
	Destory(_root);
}

void Destory(Node*& root)
{
	if (root == nullptr)
	{
		return;
	}
	//左-右-根
	Destory(root->_left);
	Destory(root->_right);

	delete root;
	root = nullptr;
}

4.赋值运算符重载

//赋值运算符重载
BSTree<K>& operator=(BSTree<K> t)
{
	swap(_root, t._root);
	return *this;
}

5.二叉搜索树实现集合

namespace key
{
	template<class K>
	struct BSTreeNode
	{
		BSTreeNode<K>* _left;
		BSTreeNode<K>* _right;
		K _key;

		BSTreeNode(const K& key)
			:_left(nullptr)
			, _right(nullptr)
			, _key(key)
		{}
	};

	template<class K>
	class BSTree
	{
		typedef BSTreeNode<K> Node;
	public:
		//强制生成默认构造,下面给了缺省值
		BSTree() = default;

		//拷贝构造
		BSTree(const BSTree<K>& t)
		{
			_root = copy(t._root);
		}

		//赋值运算符重载
		BSTree<K>& operator=(BSTree<K> t)
		{
			swap(_root, t._root);
			return *this;
		}

		//析构(递归)
		~BSTree()
		{
			Destory(_root);
		}

		//插入
		bool insert(const K& key)
		{
			//空树情况
			if (_root == nullptr)
			{
				_root = new Node(key);
				return true;
			}

			Node* cur = _root;
			Node* parent = nullptr;//记录父节点,插入后用以链接插入的数据

			//找到应该插入的叶子结点位置,
			//cur为空时就是该位置的下一个位置(应该插入的位置)
			while (cur)
			{
				if (key > cur->_key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (key < cur->_key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else//二叉搜索树不能有相同的节点
				{
					return false;
				}
			}
			//开辟空间初始化要插入的对象
			cur = new Node(key);

			//链接插入的子节点
			if (key > parent->_key)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}

			return true;
		}


		//查找
		bool Find(const K& key)
		{
			Node* cur = _root;
			while (cur)
			{
				if (key > cur->_key)
				{
					cur = cur->_right;
				}
				else if (key < cur->_key)
				{
					cur = cur->left;
				}
				else
				{
					return true;
				}
			}
			return false;
		}

		//删除
		bool Erase(const K& key)
		{
			Node* parent = nullptr;
			Node* cur = _root;

			while (cur)
			{
				//查找要删除的节点
				if (key > cur->_key)
				{
					cur = cur->_right;
				}
				else if (key < cur->_key)
				{
					cur = cur->_left;
				}
				else//key == cur->_key找到了 ==> 删除
				{
					//0.要删除的节点左右子树都为空
					if (cur->_left == nullptr && cur->_right == nullptr)
					{
						parent->_left = nullptr;
						parent->_right = nullptr;
						delete cur;
					}
					//1.要删除的节点只有右子树,没有左子树
					else if (cur->_left == nullptr)
					{
						//如果要删除的是根,没有父节点,直接更新_root即可
						if (cur == _root)
						{
							_root = cur->_right;
						}
						else
						{
							if (parent->_left == cur)
							{
								parent->_left == cur->_right;
							}
							else//parent->_right == cur
							{
								parent->_right == cur->_right;
							}
						}
						delete cur;
					}
					//2.要删除的节点只有左子树,没有右子树
					else if (cur->_right == nullptr)
					{
						//如果要删除的是根,没有父节点,直接更新_root即可
						if (cur == _root)
						{
							_root = cur->_lift;
						}
						else
						{
							if (parent->_left == cur)
							{
								parent->_left == cur->_left;
							}
							else//parent->_right == cur
							{
								parent->_right == cur->_left;
							}
						}
						delete cur;
					}
					//3.要删除节点的左右子树都不为空
					else
					{
						//查找 a 或 b :
						//a.右子树的最小节点-->右子树的最左节点
						//b.左子树的最大节点-->左子树的最右节点

						//查找右子树的最左节点
						Node* Pmin_Right = cur;
						Node* min_Right = cur->_right;

						while (min_Right->_left)
						{
							Pmin_Right = min_Right;
							min_Right = min_Right->_left;
						}

						//将min_Right的值给cur,等于删除了cur,之后释放min_Right即可
						cur->_key = min_Right->_key;

						if (Pmin_Right->_left == min_Right)
						{
							Pmin_Right->_left = min_Right->_right;
						}
						else
						{
							Pmin_Right->_right = min_Right->_right;
						}

						delete min_Right;
					}
				}
			}
		}

		//中序遍历
		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}

		//查找递归实现
		bool FindR(const K& key)
		{
			return _FindR(_root, key);
		}

		//插入递归实现
		bool InsertR(const K& key)
		{
			return _InsertR(_root, key);
		}

		//删除递归实现
		bool EraseR(const K& key)
		{
			return _EraseR(_root, key);
		}

	protected:
		//拷贝构造
		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 Destory(Node*& root)
		{
			if (root == nullptr)
			{
				return;
			}
			//左-右-根
			Destory(root->_left);
			Destory(root->_right);

			delete root;
			root = nullptr;
		}

		//中序遍历
		void _InOrder(Node* root)//1.缺省值必须是全局变量或者常量2.访问root需要this指针,this指针也是另一个参数,这里不一定能用
		{
			if (root == nullptr)
			{
				return;
			}

			//左->根->右
			_InOrder(root->_left);
			cout << root->_key << " ";
			_InOrder(root->_right);
		}

		//查找递归实现
		bool _FindR(Node* root, const K& key)
		{
			if (root == nullptr)
			{
				return false;
			}

			if (root->_key == key)
			{
				return true;
			}

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

		//插入递归实现
		bool _InsertR(Node*& root, const K& key)
		{
			if (root == nullptr)
			{
				root = new Node(key);
				return true;
			}

			if (root->_key < key)
			{
				return _InsertR(root->_right, key);
			}
			else if (root->_key > key)
			{
				return _InsertR(root->_left, key);
			}
			else//root->_key == key
			{
				return false;
			}
		}

		//删除递归实现
		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//key == _key
			{
				//开始删除
				Node* del = root;
				if (root->_left == nullptr)
				{
					root = root->_right;
				}
				else if (root->_right == nullptr)
				{
					root = root->_left;
				}
				else
				{
					Node* maxleft = root->_left;
					while (maxleft->_right)
					{
						maxleft = maxleft->_right;
					}

					swap(root->_key, maxleft->_key);

					return _EraseR(root->_left, key);
				}

				delete del;
				return true;
			}

		}

	private:
		Node* _root = nullptr;
	};
}

5.二叉搜索树增删查改的时间复杂度

时间复杂度为:
O(logN)~O(N)
最优情况下,接近或就是完全二叉树,时间复杂度为O(logN)。
最坏情况下二叉搜索树可能会出现单支树,此时为O(N)。
对二叉搜索树进行优化,控制左右子树的平衡就有了:AVL树和红黑树。
(AVL树与红黑树后续学习)

6.二叉搜索树的应用

K模型(key)

应用于搜索场景:在不在的问题。

结构体中只存在关键码key,关键码就是所需要查找的值。

比如:检查单词是否拼写正确,将词库中的每个单词都作为key构建二叉搜索树,进而检索单词查看是否拼写正确。

KV模型(key-value)

应用于搜索场景:通过一个值查找另一个值的问题。

**每个关键码key,都有与之对应的值value,结构中存在<key, value>的键值对。**通过key来查找value。

比如:中英互译的字典<english, chinese>,就是一对键值对;
或者可以统计单词出现的次数<word, count>,也是一对键值对;

(我们上面实现的是key模型的二叉搜索树)

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

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

相关文章

SpringBoot(8)日志监控

日志监控 监控的意义使用监控监控原理 自定义监控指标Metrics端点自定义端点 监控的意义 监控服务状态是否宕机监控服务运行指标(内存&#xff0c;虚拟机&#xff0c;线程&#xff0c;请求等)监控程序运行日志管理服务(服务上下线) 监控的实施方式 1.显示监控信息的服务器&am…

低功耗设计方法学——篇Ⅱ

引言 低功耗设计关乎ASIC芯片的性能稳定。对ASIC 特别是一些Soc芯片的设计有着重要的影响&#xff0c;随着集成规模的大幅度增加&#xff0c;芯片自身的功耗问题暴露也越来越明显。低功耗设计的需求和必要性也越来越值得关注。本文就《Low Power Methodology Manual For Syste…

逍遥自在学C语言 | 赋值运算符

前言 在C语言中&#xff0c;赋值运算符用于将一个值赋给变量 这个过程分为两个步骤&#xff1a; 计算赋值运算符右侧的表达式将结果赋给左侧的变量。 C语言提供了多个不同的赋值运算符&#xff0c;包括基本的赋值运算符、复合赋值运算符以及条件赋值运算符等 一、人物简介…

LeetCode121 买卖股票的最佳时机 遍历法和动态规划

题目地址&#xff1a;https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/ 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股…

Python爬虫之MongoDB

目录 一、Mongo概述 二、安装&下载 1.下载&#xff1a; 2.安装 三、基本命令 插⼊数据 查询数据 修改数据 删除数据 索引 四、Python与MongoDB交互 1.安装pymongo 2.使⽤ 一、Mongo概述 MongoDB是什么&#xff1f; MongoDB是⾮关系型数据库(No sql) 为啥需要…

无线传感网络课程作业 1-dijkstra算法计算最短路径并输出经过的节点

无线传感网络课程作业 1 仅供参考-如有需要可订阅专栏 题目有如下网络图: abc三个数以a=3,b=1,c=1为例 运行环境:anaconda3环境管理工具,Python 3.9.12,Windows 10 22H2算法实现原理流程i).

Java——二叉搜索树的后序遍历序列

题目链接 牛客在线oj题——二叉搜索树的后序遍历序列 题目描述 输入一个整数数组&#xff0c;判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则返回 true ,否则返回 false 。假设输入的数组的任意两个数字都互不相同。 数据范围&#xff1a; 节点数量 0≤n≤1000 …

PLECS的基本介绍

PLECS的基本介绍 一&#xff1a;PLECS的基本介绍简单说明二&#xff1a;多功能的辅助工具三&#xff1a;主要功能介绍1、独特的热分析功能2、功能强大的示波器3、极快的仿真速度4、强大的波形分析工具5、C 语言控制器6、丰富的元件库 一&#xff1a;PLECS的基本介绍简单说明 1&…

QMS-云质说质量 - 7 IATF 16949哪个条款严重不符合项最多?

云质QMS原创 转载请注明来源 作者&#xff1a;王洪石 引言 AIAG 《质量2020》报告的数据是否让你惊讶&#xff1f; AIAG与德勤合作发布的汽车行业《质量2020》报告指出&#xff0c;"OEMs和供应商都将问题解决和CSR&#xff08;Customer Specific Requirement顾客特定要求…

What...MiniGPT-4居然开源了,提前感受 GPT-4 的图像对话能力

说在前面的话&#xff1a; 一个月前&#xff0c;OpenAI向外界展示了GPT-4如何通过手绘草图直接生成网站&#xff0c;令当时的观众瞠目结舌。 在GPT-4发布会之后&#xff0c;相信大家对ChatGPT的对话能力已有所了解。圈内的朋友们应该已经亲身体验过无论是文本生成、编写代码&…

涨点技巧:基于Yolov5/Yolov7的困难样本挖掘---LRM loss,提升难样本检测精度

1.hard example mining(困难样本挖掘)✨✨✨ 困难例挖掘方法通常可以提高目标检测器的性能,因为它受到不平衡训练集的影响。为了通过RoI正确地挖掘困难例,引入了在线困难例挖掘(OHEM)方法[15]。该方法建议只考虑对反向传播最有利的RoI。给出最高损失值的RoI被认为是最难的…

【汽车品牌案例 Objective-C语言】

一、刚才,我给大家说了一下这个单元格的重用,接下来,我给大家再做一个案例, 1.再做一个什么案例呢,还是显示那个汽车品牌, 咱们上午是不是做过一个那个汽车品牌的展示了,做过那个展示,那是简单的一个展示,咱们再做一个,使用另外一个不同的一个plist文件,car_total.…

必应,百度,神马头条,搜狗专用站长seo推送工具大全

软件介绍&#xff1a; 百度开始打击滥用api问题&#xff0c;针对这个问题已经开发了拟人推送系列功能&#xff0c;放心使用。 五合一高效推送软件&#xff0c;目前支持百度&#xff0c;神马&#xff0c;必应&#xff0c;搜狗&#xff0c;头条&#xff0c;谷歌六大搜索引擎同步…

setup,ref,reactive

初识setup 1.理解:Vue3.0中一个新的配置项&#xff0c;值为一个函数。 2.setup是所有Composition API(组合API)“表演的舞台”。 3.组件中所用到的:数据、方法等等&#xff0c;均要配置在setup中。 4.setup函数的两种返回值: 1.若返回一个对象&#xff0c;则对象中的属性、…

IDEA(七) 使用UML类图

目录 1.相关快捷键1.1 查看快捷键1.2 其他快捷键1.3 分析类图1.4 定制展示类中的内容1.5 适配窗口和1:1展示1.6 定制布局1.7 其他设置 1.相关快捷键 1.1 查看快捷键 Ctrl Alt U &#xff1a;以弹窗的形式查看UML类图。Ctrl Shift Alt U &#xff1a;以标签的形式查看UML…

C learning_9 (函数篇)

目录 函数 函数的概念 函数的作用 函数的分类 函数的参数 函数的调用 函数 函数的概念 概念&#xff1a;C语言中的函数是一种封装了一定功能的代码块&#xff0c;可以在程序的任意位置调用。 返回值类型 函数名(参数列表) { 函数体 return 返回值; } 1.返回值类型指定了…

Vue的路由实现:hash模式 和 history模式原理及区别

目录标题 1、hash模式2、history模式 Vue-Router有两种模式: ** hash 模式和 history**模式。默认的路由模式是hash模式。 1、hash模式 简介&#xff1a;hash模式是开发中默认的模式&#xff0c;它的URL带着一个#&#xff0c;例如:http://www.abc.com/#/vue&#xff0c;它的…

从广交会,看懂海尔智家逆势增长的秘密

中国企业的全球化战略应从何处、以何种方式推进&#xff1f;作为行业全球化最彻底的企业&#xff0c;海尔智家是个很好的参考。 4月15日&#xff0c;在第133届中国进出口贸易交易会&#xff08;以下简称“广交会”&#xff09;上&#xff0c;海尔智家展示了其扎根本土&#xf…

Learning Dynamic Facial Radiance Fields for Few-Shot Talking Head Synthesis 笔记

Learning Dynamic Facial Radiance Fields for Few-Shot Talking Head Synthesis 笔记 摘要 Talking head synthesis is an emerging technology with wide applications in film dubbing, virtual avatars and online education. Recent NeRF-based methods generate more n…

举个栗子~Tableau 技巧(253):让筛选器只显示全部以及需要的类别

用户反馈了一个需求&#xff1a;我的业务数据有很多类别&#xff0c;但其实经常查看的只有几个&#xff0c;Tableau 筛选器能不能设置一下&#xff0c;只显示全部和经常查看的那几个类别&#xff1f; 这个是可以实现的&#xff01;如下示例&#xff0c;数据类别有&#xff1a;…