二叉树进阶--二叉搜索树

news2024/9/21 16:34:32

目录

1.二叉搜索树

1.1 二叉搜索树概念

1.2 二叉搜索树操作

1.3 二叉搜索树的实现

1.4 二叉搜索树的应用

 1.5 二叉搜索树的性能分析

2.二叉树进阶经典题:


1.二叉搜索树

1.1 二叉搜索树概念

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

若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树
二叉搜索树又称二叉排序树,因为根据其性质我们可以知道其中序遍历是有序的

1.2 二叉搜索树操作

1. 二叉搜索树的查找
a 、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
b 、最多查找高度次,走到到空,还没找到,这个值不存在。
2. 二叉搜索树的插入
插入的具体过程如下:
a. 树为空,则直接新增节点,赋值给 root 指针
b. 树不空,按二叉搜索树性质查找插入位置,插入新节点
3.二叉搜索树的删除
首先查找元素是否在二叉搜索树中,如果不存在,则返回 , 否则要删除的结点可能分下面四种情 况:
a. 要删除的结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点
删除方法:
看起来有待删除节点有 4 中情况,实际情况 a 可以与情况 b 或者 c 合并起来,因此真正的删除过程如下:
情况 b :删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点 -- 直接删除
情况 c :删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点 -- 直接删除
情况 d :在它的 右子树中寻找中序下的第一个结点(关键码最小) ,用它的值填补到被删除节点
中,再来处理该结点的删除问题 -- 替换法删除

1.3 二叉搜索树的实现

这里使用递归版本和非递归版本进行实现,需要注意的是为了保证封装性,这里大多采用子函数的形式来防止封装性被破坏。其中删除的过程最复杂

//节点类
template <class K>
struct BSTreeNode
{
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
	K _key;

	BSTreeNode(const K& key)
		:_key(key)
		,_left(nullptr)
		,_right(nullptr)
	{}
};
//二叉搜索树
template <class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
	BSTree()
		:_root(nullptr)
	{}
	//为了不破坏封装性,采用子函数的形式不暴露根
	BSTree(const BSTree<K>& t)
	{
		_root = CopyTree(t._root);
	}
	BSTree<K>& operator=(BSTree<K> t)
	{
		swap(_root,t._root);
	    return *this;
	}
	~BSTree()
	{
		Destroy(_root);
		_root = nullptr;
	}
	//插入
	bool Insert(const K& key)
	{
		//开始插入第一个的情况
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		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;
	}
	//查找
	bool Find(const K& key)
	{
		if (_root == nullptr)
			return false;
		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* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				//找到了
				//1.可能是左为空
				//2.右为空
				//两边都不为空
				if(cur->_left == nullptr)
				{
					//有可能删除根节点
					if (cur == _root)
					{
						_root = _root->_right;
					}
					else
					{
						if (parent->_left == cur)
						{
							parent->_left = cur->_right;
						}
						else
						{
							parent->_right = cur->_right;
						}
					}
					delete cur;
				
				}
				else if (cur->_right == nullptr)
				{
					if (cur == _root)
					{
						_root = _root->_right;
					}
					else
					{
						if (parent->_left == cur)
						{
							parent->_left = cur->_left;
						}
						else
						{
							parent->_right = cur->_left;
						}
					}
					delete cur;
				}
				else
				{
					//都不为空
					//从当前节点的右子树开始找最小的值
					Node* minright = cur->_right;
					Node* parent = cur;
					while (minright->_left)
					{
						parent = minright;
						minright = minright->_left;
					}
					cur->_key = minright->_key;

					//将最小的值的节点剩下的节点链接给parent
					if (parent->_left == minright)
					{
						parent->_left = minright->_right;
					}
					else
					{
						parent->_right = minright->_right;
					}
					delete minright;
				}
				return true;
			}
		}
		return false;
	}

	//打印,为了保证其封装性,可以使用子函数,采用中序遍历
	void Print()
	{
		PrintHelper(_root);
	}

	//递归版本的插入
	bool InsertR(const K& key)
	{
		return _InsertR(_root, key);
	}
	//递归版本的查找
	bool FindR(const K& key)
	{
		return _FindR(_root, key);
	}
	//递归版本的删除
	bool EraseR(const K& key)
	{
		return _EraseR(_root, key);
	}
private:
	void Destroy(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		Destroy(root->_left);
		Destroy(root->_right);
		delete root;
	}
	Node* CopyTree(Node* root)
	{
		//前序建树即可
		if (root == nullptr)
		{
			return true;
		}
		Node* newRoot = new Node(root->_key);
		newRoot->_left = CopyTree(root->_left);
		newRoot->_right = CopyTree(root->_right);
		return newRoot;
	}

	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
		{
			//找到,删除
			Node* del = root;
			//还是分3种情况
			if (root->_left == nullptr)
			{
				root = root->_right;
			}
			else if (root->_right == nullptr)
			{
				root = root->_left;
			}
			else
			{
				//在当前节点的右子树找到最小值,然后交换
				Node* minright = root->_right;
				while (minright->_left)
				{
					minright = minright->_left;
				}
				//交换
				swap(minright->_key, root->_key);
				//在右子树中找到要删除的值
				return _EraseR(root->_right, key);
			}
			delete del;
			return true;
		}
	}

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

	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
		{
			//相同
			return false;
		}
	}
	void PrintHelper(const Node* _root)
	{
		//中序遍历
		if (_root == nullptr)
			return;
		PrintHelper(_root->_left);
		cout << _root->_key << " ";
		PrintHelper(_root->_right);
	}
	Node* _root = nullptr;
};

1.4 二叉搜索树的应用

1. K 模型: K 模型即只有 key 作为关键码,结构中只需要存储 Key 即可,关键码即为需要搜索到 的值
比如: 给一个单词 word ,判断该单词是否拼写正确 ,具体方式如下:
以词库中所有单词集合中的每个单词作为 key ,构建一棵二叉搜索树
在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
2. KV模型 :每一个关键码 key ,都有与之对应的值 Value ,即 <Key, Value> 的键值对 。该种方式在现实生活中非常 常见
比如 英汉词典就是英文与中文的对应关系 ,通过英文可以快速找到与其对应的中文,英
文单词与其对应的中文 <word, chinese> 就构成一种键值对;
再比如 统计单词次数 ,统计成功后,给定单词就可快速找到其出现的次数, 单词与其出
现次数就是 <word, count> 就构成一种键值对
KV模型代码变形:(这里只修改了插入和查找,因为这个使用的多,而且到后面的map和set会深入学习)
//改造二叉搜索树变为KV模型
namespace KV
{
	template <class K, class V>
	struct BSTreeNode
	{
		BSTreeNode<K,V>* _left;
		BSTreeNode<K,V>* _right;
		K _key;
		V _val;

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

	template <class K, class V>
	class BSTree
	{
		typedef BSTreeNode<K, V> Node;
	public:
		//插入
		bool Insert(const K& key, const V& val)
		{
			//开始插入第一个的情况
			if (_root == nullptr)
			{
				_root = new Node(key,val);
				return true;
			}
			Node* cur = _root;
			Node* parent = nullptr;
			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,val);
			//判断链接的左右
			if (parent->_key < key)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}
			return true;
		}
		//查找
		Node* Find(const K& key)
		{
			if (_root == nullptr)
				return nullptr;
			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;
		}
	private:
		Node* _root = nullptr;
	};
}

下面就是KV模型的两个例子:

1.在字典中查找你写的单词是否存在:
void TestBSTree1()
	{
		BSTree<string, string> dict;
		dict.Insert("string", "字符串");
		dict.Insert("tree", "树");
		dict.Insert("left", "左边、剩余");
		dict.Insert("right", "右边");
		dict.Insert("sort", "排序");
		string str;
		while (cin >> str)
		{
			//在字典中查找
			BSTreeNode<string,string>* ret = dict.Find(str);
			if (ret)
			{
				cout << ret->_val << endl;
			}
			else
			{
				cout << "不存在" << endl;
			}
		}
	}

看看结果:

 2.统计次数:(常用):

void TestBSTree2()
	{
		// 统计水果出现的次数
		string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
	   "苹果", "香蕉", "苹果", "香蕉" };
		BSTree<string, int> countTree;
		for (const auto& e : arr)
		{
			//将数据插入到二叉搜索树中
			auto ret = countTree.Find(e);
			if (ret == nullptr)
			{
				//树中没有该水果
				countTree.Insert(e, 1);
			}
			else
			{
				ret->_val++;
			}
		}
		countTree.Print();
	}

结果:

 1.5 二叉搜索树的性能分析

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

 

最优情况下 ,二叉搜索树为完全二叉树 ( 或者接近完全二叉树 ) ,其平均比较次数为: logN
最差情况下 ,二叉搜索树退化为单支树 ( 或者类似单支 ) ,其平均比较次数为: N
后面我们学习了AVL树以及红黑树就可以使二叉搜索树的效率达到最高了。

2.二叉树进阶经典题:


1.根据二叉树创建字符串

思路:根据前序遍历,我们可以通过根左子树右子树的顺序进行递归,但是递归子树的时候需要注意条件,如果左子树是空,但是有右子树就需要保留空括号,如果左子树不为空,但右子树为空,就不需要保留空括号。

class Solution {
public:
    void _tree2str(TreeNode* root,string& result)
    {
        if(root == nullptr)
        {
            result += "";
            return;
        }
        result += to_string(root->val);
        if(root->left || root->right)
        {
            result += "(";
            _tree2str(root->left,result);
            result += ")";
        }
        if(root->right)
        {
            result += "(";
            _tree2str(root->right,result);
            result += ")";
        }
    }
    string tree2str(TreeNode* root) {
        string result;
        _tree2str(root,result);
        return result;
    }
};

2.二叉树的层序遍历

思路:我们可以通过队列来模拟层序遍历:一次输入一层的节点,然后把队列中当层的元素全部弹出,同时进入下一层元素,我们可以通过size来控制当层元素的个数。然后把当层元素放入结果集中

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> result;
        if(root == nullptr)
            return result;
        queue<TreeNode*> q;
        q.push(root);
        while(!q.empty())
        {
             vector<int> tmp;
             int size = q.size();
            while(size--)
            {
                TreeNode* frontnode = q.front();
                q.pop();
                tmp.push_back(frontnode->val);
                //放入左右节点
            if(frontnode->left)
            {
                q.push(frontnode->left);
            }
            if(frontnode->right)
            {
                q.push(frontnode->right);
            }
            }
            result.push_back(tmp);
        }
        return result;
    }
};

3.二叉树的最近公共祖先

思路:这题可以使用回溯法来解决,我们可以分别将p和q的路径存放在栈中,然后通过对栈的弹出操作,找到他们相同的节点。其中找路径问题就是回溯问题,我们可以把每次递归的结果先保存起来,如果找到就返回真,就可以结束递归,如果没有找到我们就继续递归,当子树递归到了nullptr时,我们就需要回退,回退的本质就是将栈顶元素pop。

class Solution {
public:
    bool _lowestCommonAncestor(TreeNode* root,TreeNode* p,stack<TreeNode*>& st)
    {
        if(root == nullptr)
        {
            return false;
        }
        st.push(root);
        if(root == p)
        {
            return true;
        }
        if(_lowestCommonAncestor(root->left,p,st))
        {
            return true;
        }
        if(_lowestCommonAncestor(root->right,p,st))
        {
            return true;
        }
        //回退
        st.pop();
        return false;
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        //回溯法
        stack<TreeNode*> pv;
        stack<TreeNode*> qv;
        _lowestCommonAncestor(root,p,pv);
        _lowestCommonAncestor(root,q,qv);
        while(pv.size() != qv.size())
        {
            if(pv.size() > qv.size())
            {
                pv.pop();
            }
            else
                qv.pop();
        }
        while(pv.top() != qv.top())
        {
            pv.pop();
            qv.pop();
        }
        return pv.top();
    }
};

4.二叉搜索树与双向链表

思路:因为二叉搜索树的中序是有序的,我们可以先递归到最小的节点,然后通过中序改变它们之间的链接关系。

class Solution {
public:
	void _Convert(TreeNode* cur,TreeNode*& prev)
	{
		if(cur == nullptr)
		{
			return;
		}
		//中序走到最小
		_Convert(cur->left,prev);
		//建立链接关系
		//这里是防止第一次prev为空的情况
		if(prev)
		{
			prev->right = cur;
		}
		cur->left = prev;
		prev = cur;
		_Convert(cur->right,prev);
	}
    TreeNode* Convert(TreeNode* root) {
		if(root == nullptr)
			return nullptr;
        TreeNode* prev = nullptr;
		_Convert(root,prev);
		//返回根
		while(root->left)
		{
			root = root->left;
		}
		return root;
    }
};

5.从前序与中序遍历序列构造二叉树

思路:因为前序是可以确定根的,所以我们可以在中序中找到根,然后划分左右子树的区间,根据前序的顺序,先递归左子树,再递归右子树,当区间不存在时即可回退。

class Solution {
public:
    TreeNode* _buildTree(vector<int>& preorder, vector<int>& inorder,int begin,int end,int& i)
    {
        if(begin > end)
            return nullptr;
        //从中序中找子树区间
        int j = begin;
        for(;j<=end;++j)
        {
            if(inorder[j] == preorder[i])
                break;
        }
        TreeNode* root = new TreeNode(preorder[i++]);
        //[begin,j-1] j [j+1,end]
        root->left = _buildTree(preorder,inorder,begin,j-1,i);
        root->right = _buildTree(preorder,inorder,j+1,end,i);
        return root;
    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        //先序找根,中序找子树
        //采用闭区间
        int i = 0;
        return  _buildTree(preorder,inorder,0,inorder.size()-1,i);
    }
};

6.使用非递归实现二叉树的前序遍历

思路:一般递归可以实现的代码,使用非递归都需要使用到数据结构的栈,我们可以将树分成左路节点和右树,我们先迭代左路节点到空,其中把每个值存放在栈中,并保存到结果集中,然后取栈顶元素再走右树即可。而中序遍历所需要保存的结果刚好是前序遍历栈弹出的结果,代码与这个类似。

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        //分成两部分,左路节点和右子树
        TreeNode* cur = root;
        stack<TreeNode*> st;
        vector<int> v;
        while(!st.empty() || cur)
        {
            while(cur)
            {
                v.push_back(cur->val);
                st.push(cur);
                cur = cur->left;
            }
            //走右子树
            TreeNode* tmp = st.top();
            st.pop();
            
            cur = tmp->right;
        }
        return v;
    }
};

7.使用非递归实现二叉树的后序遍历

思路:这个和前序以及中序有所不同,就是在确定根的时候,我们需要确定两次,第一次是拿到根并走其右子树,第二次拿到根的时候就可以将根从栈中弹出了。我们也可以使用结构体存放每个节点和节点被取出的次数。当然还有更巧妙的方法:当我们走到右子树的最右端时,我们就可以使用一个指针记录下来,在当前节点回退的时候必然存在cur->right  == prev(这个就是用来记录的节点),然后我们再把这个标记节点更新到当前节点,这样就可以不断回退了。具体看代码理解:

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> v;
        stack<TreeNode*> st;
        TreeNode* cur = root;
        TreeNode* prev = nullptr;//用来记录根的右子树是否被访问
        while(!st.empty() || cur)
        {
            while(cur)
            {
                st.push(cur);
                cur = cur->left;
            }
            TreeNode* top = st.top();
            //如果右子树为空或者到最右端返回的时候就回收结果
            if(top->right == nullptr || top->right == prev)
            {
                st.pop();
                v.push_back(top->val);
                prev = top;//从最右端回来的时候起重要作用
            }
            else
            {
                //这时候要往右迭代
                cur = top->right;
            }
        }
        return v;
    } 
};

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

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

相关文章

144. 二叉树的前序遍历

144. 二叉树的前序遍历 Java代码&#xff1a;递归 public List<Integer> preorderTraversal(TreeNode root) {List<Integer> res new ArrayList<>();preOrder(root, res);return res; } private void preOrder(TreeNode root, List<Integer> res) {i…

【沐风老师】3DMAX几何投影插件Geometry Projection使用详解

【几何投影插件】 描述 3DMAX几何投影插件Geometry Projection&#xff0c;将一个或多个对象或它的顶点选择沿全局或局部 x、y 或 z 轴投影到另一个对象上。 适用版本 3dMax2013或更高版本 安装设置 插件的安装非常简单&#xff0c;解压后把插件脚本 “geometry_projectio…

SmartSoftHelp 数据库优化辅助软件

SmartSoftHelp 数据库高并发优化&#xff0c;数据库连接字符串优化&#xff0c;SQL耗时优化&#xff0c;SQL格式化&#xff0c;美化&#xff1f; 这些问题你都知道&#xff1f; 这不仅仅是一个软件&#xff0c;而是很多种解决方案&#xff01; 支持Netframework ,netcore后台…

移动设备安全管理基础指南

什么是移动安全管理 &#xff08;MSM&#xff09; 移动安全管理是指为保护企业中的移动设备和企业数据而采取的行动。这些操作可以进一步被归类为反应性的或主动的&#xff0c;基于该操作是在数据和设备被破坏之前还是之后执行的。除了管理移动设备外&#xff0c;大多数MDM解决…

验收测试分类

α测试 Alpha 是内测版本&#xff0c;即现在所说的CB。 此版本表示该软件仅仅是一个初步完成品, 通常只在软件开发者内部交流, 也有很少一部分发布给专业测试人员。 一般而言, 该版本软件的bug 较多, 普通用户最好不要安装。 β测试 Beta是公测版本&#xff0c;是对所有用户…

【算法】高精度

作者&#xff1a;指针不指南吗 专栏&#xff1a;算法篇 &#x1f43e;不能只会思路&#xff0c;必须落实到代码上&#x1f43e; 文章目录前言一、高精度加法二、高精度减法三、高精度乘法四、高精度除法前言 ​ 高精度即很大很大的数&#xff0c;超过了 long long 的范围&…

html标签手册

完整的HTML页面&#x1f4d1; ①基础标签&#x1f4d1;&#x1f4d1;&#x1f4d1; HTML <!DOCTYPE> 声明 !DOCTYPE声明必须是 HTML 文档的第一行&#xff0c;位于 html标签之前。 !DOCTYPE 声明不是 HTML 标签&#xff1b;它是指示 web 浏览器关于页面使用哪个 HTML 版…

硬件设计 之 CAN通信-DSView逻辑分析仪使用-CAN波形测试

CAN总线讲解 1.基本概念&#xff1a; **CAN&#xff0c;Controller Area Network&#xff0c;控制器局域网是用于解决汽车众多控制部件之间的数据交换而开发的一种串行数据通信总线。 ** 2.CAN总线电平&#xff1a; can总线采用差分电压传输数据&#xff0c;分别是CANH和CA…

QCon演讲实录(上):多云环境下应用管理与交付实践

作者&#xff1a;阿里云大数据基础工程技术团队——郭耀星 大家上午好&#xff01;我是来自阿里云大数据基础工程技术团队的郭耀星&#xff0c;花名雪尧。今天我很高兴能够来到QCon&#xff0c;与大家分享我的经验和心得。在当前的多云环境中&#xff0c;作为运维支撑团队&…

GPDB中的HASH JOIN解析

GPDB中的HASH JOIN机制Hash Join是利用hash函数来实现和加速数据库中JOIN操作的一类算法。主要优势是hash函数可以只通过一次运算就将键值映射到固定大小的hash值&#xff0c;仅用作等值join中。由于HASH JOIN的算法复杂度在平均情况下是O(n)&#xff0c;所以通常在大规模数据时…

【K3s】第15篇 使用containerd容器部署k3s集群

目录 1、文件准备 2、安装master节点 3、安装agent节点 4、查看详情信息 5、遇到问题 1、文件准备 安装docker yum install -y dockersystemctl status docker 增加docker国内镜像源 vi /etc/docker/daemon.json 添加内容为&#xff1a; { "registry-mirrors":…

MySQL:为什么说应该优先选择普通索引,尽量避免使用唯一索引

前言 在使用MySQL的过程中&#xff0c;随着表数据的逐渐增多&#xff0c;为了更快的查询我们需要的数据&#xff0c;我们会在表中建立不同类型的索引。 今天我们来聊一聊&#xff0c;普通索引和唯一索引的使用场景&#xff0c; 以及为什么说推荐大家优先使用普通索引&#xf…

面试题(二十四)数据结构与算法

9.1哈希 请谈一谈&#xff0c;hashCode() 和equals() 方法的重要性体现在什么地方&#xff1f; 考察点&#xff1a;JAVA哈希表 参考回答&#xff1a; Java中的HashMap使用hashCode()和equals()方法来确定键值对的索引&#xff0c;当根据键获取值的时候也会用到这两个方法。…

【OJ】本土化

&#x1f4da;Description: 激动人心的时刻到了&#xff01;我们开发的记账软件即将在欧美地区发布&#xff0c;但我希望能把本土化工作做得更好。所谓的本土化可不仅仅是把软件界面语言翻译成英语&#xff0c;还要根据当地的习俗来调整软件的使用方法。 比如我国在书写数字…

【yolov5】首次尝试目标检测利用prompt

1、打开prompt 2、切换到pytorch所在环境 conda activate freezing我的环境名是freezing&#xff0c;这里根据自己环境名去激活切换 3、进入到yolov5项目所在路径 激活完环境后立即执行指令当然是无效的&#xff0c;首先要进入到你的项目目录 首先看一下自己的项目在那个位…

MongoDB分片集群部署及实践

文章目录MongoDB分片集群部署及实践一、前言二、了解分片集群1.简介2.分片集群组件3.副本集模式三.安装部署1.分片集群环境2.安装1&#xff09;CPU 检测2&#xff09;修改机器名称3&#xff09;配置 hosts 文件4&#xff09;配置防火墙5&#xff09;创建目录6&#xff09;安装 …

PowerBI的使用和基本效果

1. 什么是PowerBI Power BI 是一套商业分析工具&#xff0c;用于在组织中提供见解。可连接数百个数据源、简化数据准备并提供即席分析。生成美观的报表并进行发布&#xff0c;供组织在 Web 和移动设备上使用。每个人都可创建个性化仪表板&#xff0c;获取针对其业务的全方位独…

加一-力扣66-java高效方案

一、题目描述给定一个由 整数 组成的 非空 数组所表示的非负整数&#xff0c;在该数的基础上加一。最高位数字存放在数组的首位&#xff0c; 数组中每个元素只存储单个数字。你可以假设除了整数 0 之外&#xff0c;这个整数不会以零开头。示例 1&#xff1a;输入&#xff1a;di…

PostgreSQL的下载安装教程(macOS、Windows)

postgresql是GIS服务端几乎不可避免要打交道的数据库。因为mysql的空间扩展真是不尽人意。所以想要学会GIS服务端知识,postgresql(下文简称pg)你是必须要会的。 首先要知道,pg是一个空间数据库,和普通数据库不同的是pg支持空间数据的存储与操作。这里所谓的空间数据一般指…

1、为什么要经常对数据做特征归一化

前言&#xff1a; feature scaling 即为特征归一化、标准化&#xff0c;是数据预处理中的重要技术。在实际运用中&#xff0c;使用梯度下降算法的模型都需要归一化&#xff0c;包括线性回归、逻辑回归、支持向量机、神经网络等模型。但对决策树模型并不适用&#xff0c;因为信…