《二叉搜索树》

news2024/12/28 19:48:33

文章目录

  • 一、二叉搜索树的概念
  • 二、二叉搜索树的实现
    • 2.1 插入
      • 迭代插入
      • 递归插入
    • 2.2 查找
      • 迭代查找
      • 递归查找
    • 2.3 删除
      • 迭代删除
      • 递归删除
    • 2.4 中序遍历
  • 三、二叉搜索树的应用
    • 1、K模型
    • 2、KV模型
  • 四、二叉树的性能分析


一、二叉搜索树的概念

二叉搜索树又叫做二叉排序树。

  • 左子树的结点值都小于根
  • 右子树的结点值都大于根
  • 左右子树分别是二叉搜索树

因为在二叉搜索树中,每个结点左子树上所有结点的值都小于该结点的值,右子树上所有结点的值都大于该结点的值,因此对二叉搜索树进行中序遍历后,得到的是升序序列。
在这里插入图片描述

二、二叉搜索树的实现

2.1 插入

迭代插入

思想:插入数据仍然保持它是搜索二叉树
为什么要有返回值呢?
因为搜索二叉树要减少数据的冗余,遇到重复的数字是插入不了的

图解:
在这里插入图片描述
所以我们在插入过程中必须要记录一下上一个结点的位置,然后再迭代着往后走,不然我们是无法链接起来的。

// 插入数据保持继续是二叉搜索树
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;
		}
		// _key==key
		else
		{
			return false;
		}
	}

	// 这里说明走到合适的空位置了,需要链接起来,
	//但是还是要判断要插入的数比parent大还是小,这决定插在哪边
	cur = new Node(key);
	if (key > parent->_key)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	return true;
}

递归插入

图解
这里的指针引用非常细节,这样就能保证链接起来,C语言中就要使用二级指针了。
在这里插入图片描述

bool _InsertR(Node*& root, const K& key)
{
	if (root == nullptr)
	{
		//开始插入
		//因为是引用,root就是上一层的左或者右的别名,所以修改root就是修改上一层的左或者右
		root = new Node(key);
		return true;
	}

	if (key > root->_key)
		return _InsertR(root->_right, key);
	else if (key < root->_key)
		return _InsertR(root->_left, key);
	else// 相等不允许插入
		return false;
}

2.2 查找

迭代查找

// 查找
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;
}

递归查找

Node* _FindR( Node* root,const K& key)
{
	if (root == nullptr)
		return nullptr;
	if (key > root->_key)
		return _FindR(root->_right, key);
	else if (key < root->_left)
		return _FindR(root->_left, key);
	else// 相等就是找到了,返回节点
		return root;
}

2.3 删除

删除是重头戏,这是面试很爱考察的!

删除的节点分类(1,2可以归结为同一类):
1、叶子节点
2、只有一个孩子的节点
3、有两个孩子节点

情况1:删除叶子节点
在这里插入图片描述
情况2:删除只有一个孩子的节点
在这里插入图片描述
分析:
1️⃣第一种和第二种都是直接删除,其实可以归类为同一种:

  1. 被删除节点,左为空,让父亲指向被删除节点的右;
  2. 右为空,让父亲指向被删除节点的左,

注意

  1. 要注意特殊情况,被删除的节点是父亲的什么,此处需要特判断一下。
  2. 出现父亲节点为空时,就又会出问题,我们需要更新_root
    在这里插入图片描述

情况3:删除有两个孩子的节点(找保姆)
替换法:找寻左子树的最大节点,或者右子树的最小节点进行替换删除
在这里插入图片描述
分析:

  1. cur的右子树的左不为空,此时将最小节点min的值和要删除节点的值互换,然后将min节点的父亲连接min的右边(左边不能有节点,因为左边的节点一定比min小),删除min即可
    在这里插入图片描述
  2. cur的右子树的左为空,那么cur的右子树就是最小节点。
    这里删除8在这里插入图片描述

迭代删除

// 删除
bool Erase(const K& key)
{
	//先找到我们要删除的那个节点和它的父亲节点
	Node* cur = _root;
	Node* parent = nullptr;

	while (cur)
	{
		if (key > cur->_key)
		{
			cur = cur->_right;
			parent = cur;
		}
		else if (key < cur->_left)
		{
			cur = cur->_left;
			parent = cur;
		}
		else
		{
			// 找到了 准备开始删除
			if (cur->_left == nullptr)
			{
				// 当父亲为空时,也需要特殊处理一下
				if (parent == nullptr)
				{
					_root = cur->_right;
				}
				else
				{
					// 需要特判一下,被删除节点是父亲的哪个节点
					if (cur == parent->_left)
						parent->_left = cur->_right;
					else
						parent->_right = cur->_right;
				}
				delete cur;
			}
			else if (cur->_right == nullptr)
			{
				// 当父亲为空时,也需要特殊处理一下
				if (parent == nullptr)
				{
					_root = cur->_left;
				}
				else
				{
					if (cur == parent->_left)
						parent->_left = cur->_left;
					else
						parent->_right = cur->_left;
				}
				
				delete cur;
			}
			else
			{
				// 替换法删除
				Node* minParent = cur;
				Node* min = cur->_right;//右树的最小节点
				while (min->_left)
				{
					minParent = min;
					min = min->_left;
				}

				// 覆盖cur节点
				cur->_key = min->_key;
				//转换成删除替代节点min

				if (minParent->_left==min)
					minParent->_left = min->_right;
				else
					minParent->_right = min->_right;

				delete min;
			}
			return true;
		}
	}
	return false;
}

递归删除

bool _EraseR(Node* &root, const K& key)
{
	if (root == nullptr)
		return false;
	if (key > root->_key)
		return _EraseR(root->_right, key);
	else if (key < root->_key)
		return _EraseR(root->_left, key);
	else// 开始删除
	{
		Node* del = root;// 记录一下要删除的节点
		if (root->_left == nullptr)
			root = root -> _right;
		else if (root->_right == nullptr)
			root = root->_left;
		else
		{
			//替代法删除
			//左右都不为空
			Node* min = root->_right;
			while (min->_left)
			{
				min = min->_left;

			}
			std::swap(min->_key, root->_key);
			// 递归到右子树去删除
			return _EraseR(root->_right, key);
		}
		delete del;
		return true;
	}
}

2.4 中序遍历

public:
void InOrder()
{
	_InOrder(_root);
}
// 中序遍历
void _InOrder(Node * root)
{
	if (root == nullptr)
	{
		return;
	}
	_InOrder(root->_left);
	std::cout << root->_key << " ";
	_InOrder(root->_right);
}

三、二叉搜索树的应用

1、K模型

K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。

主要用于解决在不在问题:
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
以单词集合中的每个单词作为key,构建一棵二叉搜索树
在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

namespace K 
{
	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>
	struct BSTree
	{
		typedef BSTreeNode<K> Node;
	public:
		BSTree() :_root(nullptr) {}
		~BSTree() {}

		// 递归版本
		// 因为递归都需要根,但是我们在类外穿参数时,都拿不到根,所以要嵌套一个子函数
		bool InsertR(const K& key)
		{
			return _InsertR(_root, key);
		}

		Node* FindR(const K& key)
		{
			return _FindR(_root, key);
		}

		bool EraseR(const K& key)
		{
			return _EraseR(_root, key);
		}


		// 插入数据保持继续是二叉搜索树
		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;
				}
				// _key==key
				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* cur = _root;
			Node* parent = nullptr;

			while (cur)
			{
				if (key > cur->_key)
				{
					parent = cur;
					cur = cur->_right;

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

				}
				else
				{
					// 找到了 准备开始删除
					if (cur->_left == nullptr)
					{
						// 当父亲为空时,也需要特殊处理一下
						if (parent == nullptr)
						{
							_root = cur->_right;
						}
						else
						{
							// 需要特判一下,被删除节点是父亲的哪个节点
							if (cur == parent->_left)
								parent->_left = cur->_right;
							else
								parent->_right = cur->_right;
						}
						delete cur;
					}
					else if (cur->_right == nullptr)
					{
						// 当父亲为空时,也需要特殊处理一下
						if (parent == nullptr)
						{
							_root = cur->_left;
						}
						else
						{
							if (cur == parent->_left)
								parent->_left = cur->_left;
							else
								parent->_right = cur->_left;
						}

						delete cur;
					}
					else
					{
						// 替换法删除
						Node* minParent = cur;
						Node* min = cur->_right;//右树的最小节点
						while (min->_left)
						{
							minParent = min;
							min = min->_left;
						}

						// 覆盖cur节点
						cur->_key = min->_key;
						//转换成删除替代节点min

						if (minParent->_left == min)
							minParent->_left = min->_right;
						else
							minParent->_right = min->_right;

						delete min;
					}
					return true;
				}
			}
			return false;
		}
		void InOrder()
		{
			_InOrder(_root);
			std::cout << std::endl;
		}
		// 中序遍历
		void _InOrder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}
			_InOrder(root->_left);
			std::cout << root->_key << " ";
			_InOrder(root->_right);
		}
	private:
		Node* _FindR(Node* root, const K& key)
		{
			if (root == nullptr)
				return nullptr;
			if (key > root->_key)
				return _FindR(root->_right, key);
			else if (key < root->_left)
				return _FindR(root->_left, key);
			else// 相等就是找到了,返回节点
				return root;
		}
		bool _InsertR(Node*& root, const K& key)
		{
			if (root == nullptr) {
				root = new Node(key);
				return true;
			}

			if (key > root->_key)
				return _InsertR(root->_right, key);
			else if (key < root->_key)
				return _InsertR(root->_left, key);
			else// 相等不允许插入
				return false;
		}
		bool _EraseR(Node*& root, const K& key)
		{
			if (root == nullptr)
				return false;
			if (key > root->_key)
				return _EraseR(root->_right, key);
			else if (key < root->_key)
				return _EraseR(root->_left, key);
			else// 开始删除
			{
				Node* del = root;// 记录一下要删除的节点
				if (root->_left == nullptr)
					root = root->_right;
				else if (root->_right == nullptr)
					root = root->_left;
				else
				{
					//替代法删除
					//左右都不为空
					Node* min = root->_right;
					while (min->_left)
					{
						min = min->_left;

					}
					std::swap(min->_key, root->_key);
					// 递归到右子树去删除
					return _EraseR(root->_right, key);
				}
				delete del;
				return true;
			}
		}
	private:
		Node* _root;
	};

	void TestBSTree()
	{
		BSTree<int> t;
		int a[] = { 9,8,7,6,5,4,3,2,1,0 };
		for (auto e : a)
		{
			t.InsertR(e);
		}
		t.InOrder();
		t.EraseR(5);
		t.InOrder();
		t.EraseR(7);
		t.InOrder();
	}
}

2、KV模型

KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。
主要解决给一个值查找另外一个值

该种方式在现实生活中非常常见:比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。

比如:实现一个简单的英汉词典dict,可以通过英文找到与其对应的中文,具体实现方式如下:

  • <单词,中文含义>为键值对构造二叉搜索树,注意:二叉搜索树需要比较,键值对比较时只比较Key
  • 查询英文单词时,只需给出英文单词,就可快速找到与其对应的key
namespace KV
{
	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)
			:_left(nullptr),
			_right(nullptr),
			_key(key),
			_value(value)
		{}
	};

	template<class K, class V>
	struct BSTree
	{
		typedef BSTreeNode<K, V> Node;
	public:
		BSTree() :_root(nullptr) {}
		~BSTree() {}

		// 插入数据保持继续是二叉搜索树
		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;
				}
				// _key==key
				else
				{
					return false;
				}
			}

			// 这里说明走到合适的空位置了,需要链接起来
			cur = new Node(key, value);
			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->_right;
				}
				else if (key < cur->_key)
				{
					cur = cur->_left;
				}
				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->_right;

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

				}
				else
				{
					// 找到了 准备开始删除
					if (cur->_left == nullptr)
					{
						// 当父亲为空时,也需要特殊处理一下
						if (parent == nullptr)
						{
							_root = cur->_right;
						}
						else
						{
							// 需要特判一下,被删除节点是父亲的哪个节点
							if (cur == parent->_left)
								parent->_left = cur->_right;
							else
								parent->_right = cur->_right;
						}
						delete cur;
					}
					else if (cur->_right == nullptr)
					{
						// 当父亲为空时,也需要特殊处理一下
						if (parent == nullptr)
						{
							_root = cur->_left;
						}
						else
						{
							if (cur == parent->_left)
								parent->_left = cur->_left;
							else
								parent->_right = cur->_left;
						}

						delete cur;
					}
					else
					{
						// 替换法删除
						Node* minParent = cur;
						Node* min = cur->_right;//右树的最小节点
						while (min->_left)
						{
							minParent = min;
							min = min->_left;
						}

						// 覆盖cur节点
						cur->_key = min->_key;
						cur->_value = min->_value;
						//转换成删除替代节点min

						if (minParent->_left == min)
							minParent->_left = min->_right;
						else
							minParent->_right = min->_right;

						delete min;
					}
					return true;
				}
			}
			return false;
		}
		void InOrder()
		{
			_InOrder(_root);
			std::cout << std::endl;
		}
		// 中序遍历
		void _InOrder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}

			_InOrder(root->_left);
			cout << root->_key << ":" << root->_value << endl;
			_InOrder(root->_right);
		}
private:
	Node* _root;
	};

//测试如下
	void TestBSTree_KV1()
	{
		// 字典模型KV
		BSTree<std::string, std::string> dict;
		dict.Insert("sort", "排序");
		dict.Insert("study", "学习");
		dict.Insert("C++", "C加加");
		
		dict.InOrder();

		std::string str;
		while (std::cin >> str)
		{
			
			BSTreeNode<std::string, std::string>* ret = dict.Find(str);
			if (ret)
			{
				std::cout << "对应的中文解释为: " << ret->_value << std::endl;
			}
			else
			{
				std::cout << "词库无此单词!!" << std::endl;
			}
		}
	}

	void TestBSTree_KV2()
	{
		//统计水果出现的次数
		string fruits[] = { "香蕉","香蕉","香蕉","香蕉","橘子","苹果","橘子","西瓜","橘子" ,"橘子" ,"橘子","苹果","苹果","苹果","苹果" };
		BSTree <string, int> countTree;

		for (auto& str : fruits)
		{
			//BSTreeNode<std::string, int>* ret = countTree.Find(e);
			auto ret = countTree.Find(str);
			if (ret != nullptr)
			{
				ret->_value++;
			}
			else
			{
				countTree.Insert(str,1);
			}
		}
		countTree.InOrder();
	}
}

四、二叉树的性能分析

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

在这里插入图片描述

最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:logN
最差情况下,二叉搜索树退化为单支树,其平均比较次数为:N/2

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

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

相关文章

diffusion model(三)—— classifier guided diffusion model

classifier guided diffusion model 背景 对于一般的DM&#xff08;如DDPM&#xff0c; DDIM&#xff09;的采样过程是直接从一个噪声分布&#xff0c;通过不断采样来生成图片。但这个方法生成的图片类别是随机的&#xff0c;如何生成特定类别的图片呢&#xff1f;这就是clas…

前沿重器[35] | 提示工程和提示构造技巧

前沿重器 栏目主要给大家分享各种大厂、顶会的论文和分享&#xff0c;从中抽取关键精华的部分和大家分享&#xff0c;和大家一起把握前沿技术。具体介绍&#xff1a;仓颉专项&#xff1a;飞机大炮我都会&#xff0c;利器心法我还有。&#xff08;算起来&#xff0c;专项启动已经…

MySQL数据库主从复制与读写分离(图文详解!)

目录 前言 一&#xff1a;MySQL数据库主从复制与读写分离 1、什么是读写分离&#xff1f; 2、为什么要读写分离呢&#xff1f; 3、什么时候要读写分离&#xff1f; 4、主从复制与读写分离 5、mysql支持的复制类型 &#xff08;1&#xff09;STATEMENT &#xff08;2&…

SLAM面试笔记(5) — C++面试题

目录 第1章 C基础 1 C中static静态变量有什么作用&#xff0c;在什么情况下会用&#xff1f; 2 类中的this指针指向哪里&#xff1f; 3 说一下const的作用。 4 std::string类型为啥不能memset&#xff1f; 5 emplace_back( )和push_back( )有什么区别&#xff1f; 6 tra…

【状态估计】基于无味卡尔曼滤波模拟倾斜传感器研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

通过Redisson的管道批量操作来提高Redis Io效率

一、背景 当在对redis进行刷数操作时&#xff0c;大部分的redis框架对于单次执行的效率差不多&#xff0c;但我们有时需要一次性写入大量的redis key时&#xff0c;一次一次的操作速度就很慢。尤其是处于跨region的环境&#xff0c;一次的redis io就高达数十毫秒&#xff0…

Android aar包的生成与使用

前言 最近用Android Studio开发Android时&#xff0c;会经常接触到aar包&#xff08;Java Archive&#xff09;&#xff0c;aar包含所有资源&#xff0c;class以及res资源文件全部包含。 优势 Android通过aar方式把代码和资源打成一个包&#xff0c;提供给第三方使用或者是开…

什么是AOP?

目录 一、AOP简介 1、AOP简介和作用 2、AOP的概念 二、AOP的基本实现 三、AOP工作流程 1 、AOP工作流程 2、AOP核心概念 四、AOP切入点表达式 1、语法格式 2、通配符 五、AOP通知类型 1、AOP通知分类 2、AOP通知详解 &#xff08;1&#xff09;前置通知 &#xf…

Java Web JDBC(1)23.6.25

JDBC 1&#xff0c;JDBC概述 在开发中我们使用的是java语言&#xff0c;那么势必要通过java语言操作数据库中的数据。这就是接下来要学习的JDBC。 1.1 JDBC概念 JDBC 就是使用Java语言操作关系型数据库的一套API 全称&#xff1a;( Java DataBase Connectivity ) Java 数据库…

vue3-实战-13-管理后台-数据大屏解决方案-顶部组件搭建-实时游客统计

目录 1-数据大屏解决方案vw和vh 2-数据大屏解决方案scale 3-数据大屏原型需求图 4-数据大屏顶部搭建 4.1-顶部原型需求 4.2-顶部模块父组件的结构和逻辑 4.3-顶部模块子组件结构和逻辑 5-数据大屏游客统计 5.1-原型需求图分析 5.2-结构样式逻辑开发 1-数据大屏解决方…

视觉与多模态大模型前沿进展 | 2023智源大会精彩回顾

导读 6 月 9 日下午&#xff0c;智源大会「视觉与多模态大模型」专题论坛如期举行。随着 stable diffusion、midjourney、SAM 等爆火应用相继问世&#xff0c;AIGC 和计算机视觉与大模型的结合成为了新的「风口」。本次研讨会由智源研究院访问首席科学家颜水成和马尔奖获得者曹…

在UE5编辑器环境中使用Python

UE有很多Python方案&#xff0c;本文所讲述的Python为UE5官方内嵌版本方案&#xff0c;并且只能在编辑器环境下使用&#xff0c;使用该功能可以编写编辑器下的辅助工具&#xff0c;提升开发效率。 1.调用Python的几种方式 讲一讲UE5中调用Python的几种方式&#xff0c;首先是…

rust abc(5): 常量

文章目录 1. 目的2. 基本用法2.1 说明2.2 运行结果 3. 不推荐或不正确用法3.1 不推荐用小写字母作为常量名字3.2 常量名称中含有小写字母就会报warning3.3 定义常量时&#xff0c;不指定数据类型会编译报错 4. const 和 immutable 的区别4.1 const 可以在函数外声明&#xff0c…

三、决策树 四、随机森林

三、决策树1.决策树模型的原理1&#xff09;什么是决策树2&#xff09;决策树模型原理3.构建决策树的目的4&#xff09;决策树的优缺点 2.决策树的典型生成算法1&#xff09;常用的特征选择有信息增益、信息增益率、基尼系数2&#xff09;基于信息增益的ID3算法3&#xff09;基…

JAVAWEB 30-

JAVAWEB 30- 快速入门DriverManagerConnectionresultsetPreparedStatement增删改查查询所有添加 修改 MAVEN坐标MyBatis代理开发mybatis查询条件查询添加删除参数传递 快速入门 public static void main(String[] args) throws Exception { /1.注册驱动 Class.forName("co…

【TA100】Bloom算法

一、什么是Bloom算法 1、首先看一下Bloom效果长什么样 2、什么是Bloom ● Bloom&#xff0c;也称辉光&#xff0c;是一种常见的屏幕效果 ● 模拟摄像机的一种图像效果&#xff0c;让画面中较亮的区域“扩散”到周围的区域中&#xff0c;造成一种朦胧的效果 ● 可以让物体具有…

[JVM]再聊 CMS 收集器

题目之所以是再聊,是因为以前聊过: [JVM]聊聊 CMS 收集器 最近又看了下这块的知识,打算把 CMS/标记-清除/GC Roots/引用 这些知识串起来 我依旧可能写的不是很好,降低下期待 GC 算法 CMS 是基于 标记-清除 算法来做的,那我们就先从 GC 算法开始聊 GC 算法有: 标记-清除 标…

一篇博客教会你使用Docker部署Redis哨兵

文章目录 主数据库配置文件启动实例容器虚拟IP 从数据库配置文件启动实例 主从数据库查看主数据库查看从数据库 哨兵配置文件启动哨兵查看哨兵 哨兵机制哨兵选举选举日志重启主数据库 今天我们学习使用 Docker 部署 Redis 的主从复制&#xff0c;并部署 Redis 哨兵&#xff0c;…

Linux学习之grub配置文件介绍

grub配置文件 /etc/default/grub这个文件里边有一些简单的grub配置。 可以看到/etc/default/grub文件里有GRUB_CMDLINE_LINUX"crashkernelauto rhgb quiet idlehalt biosdevname0 net.ifnames0 consoletty0 consolettyS0,115200n8 noibrs nvme_core.io_timeout429496729…

全网独家--【图像色彩增强】方法梳理和问题分析

文章目录 图像增强图像色彩增强问题可视化比较 难点色彩空间大&#xff0c;难以准确表征&#xff1f;不同场景差异大&#xff0c;难以自适应&#xff1f;计算量大&#xff0c;但应用场景往往实时性要求高&#xff1f; 方法传统方法深度学习逐像素预测3D LUT模仿ISP 个人思考批判…