二叉搜索树(Binary Search Tree)的模拟实现

news2024/11/23 21:11:25

前言

        为什么要学习二叉搜索树呢?因为set 和 map的底层实际上就是一颗二叉搜索树,只不过是被进行了一些特殊的处理,所有了解二叉搜索树的底层实现有利于我们更好的理解的map和set的原理。二叉搜索树又叫二叉排序树,它或者是一颗空树,或者具有以下的性质:1.若它的左子树不为空,则左子树上所有的节点都小于根,若它的右子树不为空则它的右子树上的所有节点都大于根。它的左右子树也是二叉搜索树。让我们一起来看看吧!

目录

 Binary Search Tree的实现代码

1.二叉搜索树的操作 

        1.1二叉搜索树的查找

        1.2二叉搜索树的插入

        1.3二叉搜索树的删除

        1.4Inorder

3.二叉搜索树的应用

        3.1K模型

        3.2KV模型

4.二叉搜索树的缺点

        它的实现比较简单,我相信你看一看这篇博客就能学会 

二叉搜索树图: 

 

 Binary Search Tree的实现代码

namespace qyy
{
	template<class T>
	struct BSTNode//二叉搜索树的节点
	{
		typedef BSTNode<T> Node;
		BSTNode(const T&key = T())//构造函数初始化二叉搜索树节点,
			:_left(nullptr)
			,_right(nullptr)
			,_key(key)
		{ }
	public:
		Node* _left;
		Node* _right;
		T _key;
	};
	template<class T>
	class BSTree
	{
		typedef BSTNode<T> Node;
	public:
		BSTree(const T& key = T())//构造函数只需要将根节点初始化为空就好了
		{
			_root = nullptr;
		}
		//无需析构函数
		bool Insert(const T&key)//插入key值
		{
			if (_root == nullptr)//如果为空树
			{
				_root = new Node(key);//申请新的节点
				_root->_left = _root->_right = nullptr;//初始化
				return true;
			}
			Node* cur = _root;
			Node* parent = cur;//保存cur的父亲节点
			//这里通过双指法进行插入
			while (cur)//遍历搜索树找要插入的位置
			{
				if (cur->_key > key)//key的值小于当前节点的值,去节点的左边子树找位置
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_key < key)//key的值大于当前节点的值,去节点的右边子树找位置
				{
					parent = cur;
					cur = cur->_right;
				}
				else//key的值与当前节点的值相同无法插入,结束插入,因为二叉搜索树额key值不能是相同的,这是由它的性质决定的
				{
					return false;
				}
			}
			Node* tmp = new Node(key);//申请新的节点
			if (parent->_key > key)//通过比较节点的值和插入的key值来确认插入位置
				parent->_left = tmp;
			else
				parent->_right = tmp;
			return true;
		}
		bool Find(const T& key)
		{
			if (_root == nullptr)//如果根节点为空,找到key值
				return false;
			Node* cur = _root;
			while (cur)//在二叉搜索树中对应的节点的值
			{
				if (cur->_key > key)//key小于当前节点的值,到左子树中找
					cur = cur->_left;
				else if (cur->_key < key)//key大于当前节点的值,到右子树中找
					cur = cur->_right;
				else
					return true;//key的值等于当前节点的值,找到了返回true
			}
			return false;
		}
		void _Inorder(Node*root)//二叉搜索树的中序遍历
		{
			if (root == nullptr)
				return;
			_Inorder(root->_left);
			cout << root->_key<<" ";
			_Inorder(root->_right);
		}
		void Inorder()
		{
			_Inorder(_root);//调用中序遍历,进行遍历因为如果直接写在类的外面获取不到根节点的值就无法进行传参,所以这要写一个中序遍历的子函数,传参调用进行中序遍历
			cout << endl;
		}
		bool Erase(const T&key)//查找并删除key值
		{
			if (_root == nullptr)//如果根节点为空不需要查找
				return false;

			Node* cur = _root;
			Node* parent = cur;//用来保存cur的父亲节点的值
			if (cur->_key == key && cur->_left == nullptr && cur->_right == nullptr)//左右子树为空才相等,且要key的值等于当前节点的值。
			{
				delete cur;//删除根节点
				_root = nullptr;
				return true;
			}
			//查找并删除采用双指针的方法
			while (cur)//遍历查找并删除val值的节点
			{
				if (cur->_key > key)//在左子树中找
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_key < key)//在右子树中查找
				{
					parent = cur;
					cur = cur->_right;
				}
				else//找到含key值的节点
				{
					//删除节点
					if (cur->_left == nullptr  )//如果左子树为空
					{
						if (parent->_key > cur->_key)//判断父亲节点和要删除的节点的关系,
							parent->_left = cur->_right;//断开节点的链接
						else
							parent->_right = cur->_right;
						
						delete cur;//删除节点
						cur = nullptr;
						return true;
					}
					else if (cur->_right == nullptr )//如果右子树都为空
					{
						if (parent->_key > cur->_key)//判断父亲节点和要删除的节点的关系,
							parent->_left = cur->_left;//断开节点的链接
						else
							parent->_right = cur->_left;
						delete cur;//删除节点
						cur = nullptr;
						return true;
					}
					else //左右孩子都存在
					{
						Node* tmp = cur->_right;//如果要删除的节点的左右孩子都存在的话,就要考虑,找到这个节点的左子树中val值做大的节点
						//或者右子树中val值最小的节点,交换它们的值,然后删除左子树中这个最大的值的节点,或者右子树中最小的值的这个节点
						//采用双指针法来保存右子树中的最小值节点的父节点
						Node* tmpParent = nullptr;
						while (tmp->_left)//在节点的右子树中找最左节点,也就是右子树中节点值最小的节点
						{
							tmpParent = tmp;//保存父亲节点
							tmp = tmp->_left;//右子树中的最左孩子
						}
						cur->_key = tmp->_key;//用右子树中节点的最小值来替换要删除的节点
						if (tmpParent == nullptr)//说明要删除的节点的右节点就是要替换的节点
						{
							cur->_right = tmp->_right;//最小值所在的节点的右节点不为空链接cur和最小节点的右节点
						}
						else
						{
							if (tmpParent->_key > tmp->_key)//判断最小节点和它父节点的关系,它在父节点的左边还是右边
								tmpParent->_left = nullptr;
							else
								tmpParent->_right = nullptr;
						}
						delete tmp;//删除这个替换的节点,也就是右子树中最小的节点
						tmp = nullptr;
						return true;
					}
				}
			}
			return false;
		}
	private:
		Node* _root;
	};
}

         测试代码:


#include<iostream>
using namespace std;
#include"BSTree.hpp"
void Test1()
{
	qyy::BSTree<int> b1;
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	for (auto& e : a)
		b1.Insert(e);
	b1.Inorder();
	cout<< b1.Find(20);
}
void Test2()
{
	qyy::BSTree<int> b2;
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	for (auto& e : a)
		b2.Insert(e);
	
	for (auto& e : a)
		b2.Erase(e);
	b2.Inorder();
}
int main()
{
	//Test1();
	Test2();
	return 0;
}

1.二叉搜索树的操作 

        1.1二叉搜索树的查找

        从根节点开始查找,如果比根节点的值小往左子树中查找,如果比根节点的值大往右子树中查找 。最多查找高度次,走到空还没有找到这个值为空。

                代码: 

	bool Find(const T& key)
	{
		if (_root == nullptr)//如果根节点为空,找到key值
			return false;
		Node* cur = _root;
		while (cur)//在二叉搜索树中对应的节点的值
		{
			if (cur->_key > key)//key小于当前节点的值,到左子树中找
				cur = cur->_left;
			else if (cur->_key < key)//key大于当前节点的值,到右子树中找
				cur = cur->_right;
			else
				return true;//key的值等于当前节点的值,找到了返回true
		}
		return false;//没有找到
	}

        1.2二叉搜索树的插入

        插入的过程:如果树为空,则直接新增节点,赋值给root指针

        树不为空按二叉搜索树的性质查找到插入的位置,插入新节点,如图: 

 

        注意只能插入二叉搜索树中没有的key值,这是由二叉搜索树的性质决定的。插入树中的地方都是空的位置。

        代码:

        bool Insert(const T&key)//插入key值
		{
			if (_root == nullptr)//如果为空树
			{
				_root = new Node(key);//申请新的节点
				_root->_left = _root->_right = nullptr;//初始化
				return true;
			}
			Node* cur = _root;
			Node* parent = cur;//保存cur的父亲节点
			//这里通过双指法进行插入
			while (cur)//遍历搜索树找要插入的位置
			{
				if (cur->_key > key)//key的值小于当前节点的值,去节点的左边子树找位置
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_key < key)//key的值大于当前节点的值,去节点的右边子树找位置
				{
					parent = cur;
					cur = cur->_right;
				}
				else//key的值与当前节点的值相同无法插入,结束插入,因为二叉搜索树额key值不能是相同的,这是由它的性质决定的
				{
					return false;
				}
			}
			Node* tmp = new Node(key);//申请新的节点
			if (parent->_key > key)//通过比较节点的值和插入的key值来确认插入位置
				parent->_left = tmp;
			else
				parent->_right = tmp;
			return true;
		}

 这里采用双指针法来插入,用parent指针来保存cur的父节点,最后通过判断插入key值。

        1.3二叉搜索树的删除

        二叉搜索树的删除要分几种情况进行,如果只剩下根节点,删除根节点,然后将根节点置空就好了。如图: 

        如果删除的是叶子节点,判断它在父节点的左边还是右边,将父节点的左边或者右边置空就行了。然后进行删除。

 

        如果删除的节点它的左子树为空,只需要判断父节点和它的关系然后将它的右子树链接到父节点的左边或者右边,再删除它就行了。

 

         如果删除的节点它的右子树为空,只需要判断父节点和它的关系然后将它的左子树链接到父节点的左边或者右边,再删除它就行了。(同上面一样这里就不做过多解释)

        如果左右节点都不为空,就需要找它的右边子树的最小节点替换它的值然后进行删除就行了,但是需要注意如果最小节点左子树如果为空,就需要判断一下,然后将最小节点的父节点与最小节点的父亲节点链接起来,再删除最小节点。

        

 

         代码:

 

bool Erase(const T& key)//查找并删除key值
{
	if (_root == nullptr)//如果根节点为空不需要查找
		return false;

	Node* cur = _root;
	Node* parent = cur;//用来保存cur的父亲节点的值
	if (cur->_key == key && cur->_left == nullptr && cur->_right == nullptr)//左右子树为空才相等,且要key的值等于当前节点的值。
	{
		delete cur;//删除根节点
		_root = nullptr;
		return true;
	}
	//查找并删除采用双指针的方法
	while (cur)//遍历查找并删除val值的节点
	{
		if (cur->_key > key)//在左子树中找
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_key < key)//在右子树中查找
		{
			parent = cur;
			cur = cur->_right;
		}
		else//找到含key值的节点
		{
			//删除节点
			if (cur->_left == nullptr)//如果左子树为空
			{
				if (parent->_key > cur->_key)//判断父亲节点和要删除的节点的关系,
					parent->_left = cur->_right;//断开节点的链接
				else
					parent->_right = cur->_right;

				delete cur;//删除节点
				cur = nullptr;
				return true;
			}
			else if (cur->_right == nullptr)//如果右子树都为空
			{
				if (parent->_key > cur->_key)//判断父亲节点和要删除的节点的关系,
					parent->_left = cur->_left;//断开节点的链接
				else
					parent->_right = cur->_left;
				delete cur;//删除节点
				cur = nullptr;
				return true;
			}
			else //左右孩子都存在
			{
				Node* tmp = cur->_right;//如果要删除的节点的左右孩子都存在的话,就要考虑,找到这个节点的左子树中val值做大的节点
				//或者右子树中val值最小的节点,交换它们的值,然后删除左子树中这个最大的值的节点,或者右子树中最小的值的这个节点
				//采用双指针法来保存右子树中的最小值节点的父节点
				Node* tmpParent = nullptr;
				while (tmp->_left)//在节点的右子树中找最左节点,也就是右子树中节点值最小的节点
				{
					tmpParent = tmp;//保存父亲节点
					tmp = tmp->_left;//右子树中的最左孩子
				}
				cur->_key = tmp->_key;//用右子树中节点的最小值来替换要删除的节点
				if (tmpParent == nullptr)//说明要删除的节点的右节点就是要替换的节点
				{
					cur->_right = tmp->_right;//最小值所在的节点的右节点不为空链接cur和最小节点的右节点
				}
				else
				{
					if (tmpParent->_key > tmp->_key)//判断最小节点和它父节点的关系,它在父节点的左边还是右边
						tmpParent->_left = nullptr;
					else
						tmpParent->_right = nullptr;
				}
				delete tmp;//删除这个替换的节点,也就是右子树中最小的节点
				tmp = nullptr;
				return true;
			}
		}
	}
	return false;
}

        1.4Inorder

        二叉搜索树的中序遍历不能直接实现,需要借助,一个子函数来调用实现,因为在类的外面是无法将根节点传给函数的(根节点是类中的私有成员)。

        代码:

        void _Inorder(Node*root)//二叉搜索树的中序遍历
		{
			if (root == nullptr)
				return;
			_Inorder(root->_left);
			cout << root->_key<<" ";
			_Inorder(root->_right);
		}
		void Inorder()
		{
			_Inorder(_root);//调用中序遍历,进行遍历因为如果直接写在类的外面获取不到根节点的值就无法进行传参,所以这要写一个中序遍历的子函数,传参调用进行中序遍历
			cout << endl;
		}

3.二叉搜索树的应用

        3.1K模型 

        K模型即只有Key作为关键码,结构中只需要存储K就行了,关键码即为要搜索的值。

        比如火车站进站时刷身份证就是判断你是不是今天在这个车站乘车。

        具体方法如下:通过某天车站的购票身份证号建立一颗二叉搜索树。

        在二叉搜索树中检索身份证号是否存在来判断当日是否在这里乘车。

        3.2KV模型

        每个关键码K都有其对应的值Value ,即<Key,Value>的键值对。该种方法在现实中非常常见。比如:在图书馆中通过图书编号检索图书的信息。通过图书的编号就可以快速找到所需要的的图书的信息了。图书检索中图书编号和对应的图书信息就构成键值对。

        在比如英汉词典通过英文去查询汉语意思,英文单词和对应的中文就构成一种键值对。

        例如:

//改造的二叉搜索树 KV模型
#include<string>
#include<vector>
#include<iostream>
using namespace std;
template<class K,class V>
struct BSTNode//节点的定义
{
	BSTNode(const K& key = K(),const V&value =V())//构造函数初始化节点
		:_left(nullptr)
		, _right(nullptr)
		, _key(key)
		,_value(value)
	{ }
	BSTNode<K,V>* _left;
	BSTNode<K,V>* _right;//
	K _key;
	V _value;
};
template<class K,class V>
class BSTree
{
public:
	typedef BSTNode<K,V> node;
	BSTree()//构造函数初始化根节点为空
		:_root(nullptr)
	{ }
	bool Insert(const K& key,const V&value)//插入节点
	{
		if (_root == nullptr)//树为空
		{
			//初始化根节点
			_root = new node(key,value);
			return true;
		}
		//树不为空,插入节点,按照左小右大
		node* cur = _root;
		node* parent = cur;//用来保存上一层的节点,便于插入
		while (cur)
		{
			if (cur->_key > key)//走左边
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)//走右边
			{
				parent = cur;
				cur = cur->_right;
			}
			else//key值重复插入失败直接退出
			{
				return false;
			}
		}
		//判断key值应该插在左边还是右边
		node* newnode = new node(key,value);
		if (parent->_key > key)//插在左边
			parent->_left = newnode;
		else
			parent->_right = newnode;
		return true;
	}
	//删除节点与节点的位置有很大的关系
	//如果是删除末尾的节点直接删除就好
	//如果删除的节点缺少左子树或者右子树,只需要记录另外一个子树的连接然后删除节点之后重新连接就好了
	//如果删除的节点左右子树都存在,就要找到一个能够替换它的节点,然后交换数据删除替换的节点就好了
	bool erase(const K& key)//删除节点
	{
		if (_root == nullptr)//树为空
			return false;
		node* cur = _root;
		node* parent = cur;
		if (cur->_left == nullptr && cur->_right == nullptr)//左右子树为空才相等
		{
			delete cur;//删除根节点
			_root = nullptr;
			return true;
		}
		while (cur)//查找并删除key节点的值
		{
			if (cur->_key > key)//走左边
			{
				parent = cur;//记录cur的上一层节点
				cur = cur->_left;
			}
			else if (cur->_key < key)//走右边找
			{
				parent = cur;
				cur = cur->_right;
			}
			else//找到节点
			{
				//删除节点
				if (cur->_left == nullptr)//左节点为空
				{

					if (cur->_key > parent->_key)
						parent->_right = cur->_right;//cur是parent的右子树
					else
						parent->_left = cur->_right;;//链接cur的右子树	
					delete cur;
				}
				else if (cur->_right == nullptr)//右子树为空
				{
					if (cur->_key > parent->_key)//cur是parent的右子树
						parent->_right = cur->_left;
					else
						parent->_left = cur->_left;
					delete cur;
				}
				else//左右子树都不为空
				{
					//找到右子树的最左节点然后交换 key 值
					node* tmp = cur->_right;
					node* parentTmp = cur;
					while (tmp->_left)
					{
						parentTmp = tmp;//保存tmp的上一层
						tmp = tmp->_left;
					}
					cur->_key = tmp->_key;
					if (cur->_right == tmp && tmp->_right == nullptr)//说明cur的右子树仅仅只有一个节点
					{
						cur->_right = nullptr;
					}
					else
					{
						if (tmp->_key >= parentTmp->_key)//parentTmp的右节点置空
							parentTmp->_right = tmp->_right;
						else
							parentTmp->_left = tmp->_right;//左节点置空
					}
					delete tmp;//删除tmp
				}
				return true;
			}
		}
		return false;
	}
	node* Find(const K& key)
	{
		if (_root == nullptr)//树为空
			return nullptr;
		node* cur = _root;
		while (cur)
		{
			if (cur->_key > key)//去左边找
				cur = cur->_left;
			else if (cur->_key < key)//去右边找
				cur = cur->_right;
			else//找到了
				return cur;
		}
		return nullptr;
	}
	void _InOrder(node* root)//中序遍历
	{
		//node* root = _root;
		if (root == nullptr)
			return;//递归结束
		_InOrder(root->_left);//左子树
		cout << root->_key << "  ";
		_InOrder(root->_right);//右子树
	}
	void InOrder()//中序遍历无法直接遍历节点,因为this指针是隐含的无法显示调用必须借助于其他子函数
	{
		_InOrder(_root);
		cout << endl;
	}

private:
	node* _root;//根节点
};
void Test()//英汉词典模型
{
	BSTree<string, string> Bt;
	Bt.Insert("sort", "排序");
	Bt.Insert("function", "函数");
	Bt.Insert("English", "英文");
	Bt.Insert("china", "中国");
	BSTNode<string,string>*p=Bt.Find("sort");
	if (p)//指针不为空
		cout<< p->_value<<endl;
	//Bt.InOrder();
}
void Test1()//统计物品出现的次数
{
	BSTree<string,int> Bt;
	string s1[] = { "西瓜" ,"黄瓜" , "西瓜" , "黄瓜" , "西瓜" , "香蕉" , "西瓜" , "苹果" , "草莓" , "西瓜" , "冬瓜" , "西瓜" , "西瓜" , "冬瓜" };
	for (auto& e : s1)
	{
		BSTNode<string, int>* p =Bt.Find(e);
		if (p==nullptr)
		{
			Bt.Insert(e,1);//如果第一次插入Value值等于1
		}
		else
		{
			p->_value++;//不是第一次插入Value值++
		}
	}
	BSTNode<string, int>* p = Bt.Find("西瓜");
	if (p)
		cout <<"西瓜:"<< p->_value;
}

4.二叉搜索树的缺点

        如果插入的数据是有序或者接近有序的,那么二叉搜索树就是一条直线或者近似是直线,它就只有右子树或者左子树,这时候它的查找效率就会很慢是O(N),如果是无序的数据插入到二叉搜索树中,它查找的效率就会很高,只需要做多它的高度次,也就是O(logN);所以它也是有一定的缺陷的,而mapset就是通过解决它的缺陷从而实现高效率的查找的。

        写的不好的地方希望指正。

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

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

相关文章

数据库系统概述——第二章 关系数据库(知识点复习+练习题)

&#x1f31f;博主&#xff1a;命运之光 &#x1f984;专栏&#xff1a;离散数学考前复习&#xff08;知识点题&#xff09; &#x1f353;专栏&#xff1a;概率论期末速成&#xff08;一套卷&#xff09; &#x1f433;专栏&#xff1a;数字电路考前复习 &#x1f99a;专栏&am…

SpringBoots利用redis实现防止接口幂等性重复提交

目录 什么是幂等性&#xff1f; 应用场景分析 解决办法 实际使用 什么是幂等性&#xff1f; 接口的幂等性就是用户对于同一个操作发起的一次请求或者多次请求的结果都是一致的&#xff0c;不会因为多次点击而产生副作用&#xff0c;比如说经典的支付场景&#xff1a;用户购…

一款超级给力的弱网测试神器—Qnet(上)

一、APP弱网测试背景 App在使用的过程中&#xff0c;难免会遇到不同的弱网络环境&#xff0c;像在公车上、在地铁、地下车库等。在这种情况下&#xff0c;手机常常会出现网络抖动、上行或下行超时&#xff0c;导致APP应用中出现丢包延迟&#xff0c;从而影响用户体验。 作为软…

推荐10款测试员常用的单元测试工具

前言 随着DevOp的不断流行&#xff0c;自动化测试慢慢成为Java开发者的关注点。因此&#xff0c;本文将分享10款优秀的单元测试框架和库&#xff0c;它们可以帮助Java开发人员在其Java项目上编写单元测试和集成测试。 1. JUnit 我绝对JUnit不需要太多的介绍了。即使您是Java…

Spring Security OAuth2.0认证授权 --- 高级篇

六、OAuth2.0 6.1、OAuth2.0介绍 OAuth&#xff08;开放授权&#xff09;是一个开放标准&#xff0c;允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息&#xff0c;而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容。OAuth2.0是OAuth协议的延续…

基于Python接口自动化测试框架(初级篇)附源码

目录 引言 框架设计思路 框架结构 运行程序 总结 总结&#xff1a; 引言 很多人都知道&#xff0c;目前市场上很多自动化测试工具&#xff0c;比如&#xff1a;Jmeter&#xff0c;Postman&#xff0c;TestLink等&#xff0c;还有一些自动化测试平台&#xff0c;那为啥还要…

【Unity Shader】从入门到感慨万千(2)用C#画一个立方体

文章目录 一、构成一个立方需要多少个顶点?二、定义三角面的索引数组:三、定义UV坐标数组:四、最后构建Mesh:五、完整代码:一、构成一个立方需要多少个顶点? 这个问题是面试经常被问到的题。如上图,我们知道在几何中立方体有6个面,8个顶点。但在图形学中,顶点指的是模…

vulhub-struts2-S2-007 远程代码执行漏洞复现

漏洞描述 影响版本: 2.0.0 - 2.2.3 原理 当配置了验证规则 <ActionName>-validation.xml 时&#xff0c;若类型验证转换出错&#xff0c;后端默认会将用户提交的表单值通过字符串拼接&#xff0c;然后执行一次 OGNL 表达式解析并返回。例如这里有一个 UserAction&…

Vue中如何进行表单手机号验证与手机号归属地查询

Vue中如何进行表单手机号验证与手机号归属地查询 在Vue中&#xff0c;表单验证和数据处理是非常重要的功能&#xff0c;它可以帮助我们保证用户输入的数据的正确性和完整性。手机号验证和手机号归属地查询是常见的表单验证需求&#xff0c;本文将介绍如何在Vue中实现这两个功能…

13.推荐系统

例如一个电影推荐系统&#xff0c;一共有n个用户&#xff0c;m个电影&#xff0c;每部电影都有一定的特征&#xff0c;例如爱情片的比例、动作片的比例。n个用户对看过的电影进行评分&#xff0c;推荐系统如何给用户推荐新电影&#xff0c;预测用户对新电影的评分&#xff1f; …

三、IK分词器

目录 1、IK分词器下载 2、下载完毕后解压&#xff0c;放入到elasticsearch的plugins下即可 3、重启elasticsearch&#xff0c;可以看到ik分词器被加载了 4、也可以通过elasticsearch-plugin这个命令来查看加载进来的插件 5、使用kibana测试ik分词器 6、扩展配置ik分词器词典…

linux下安装rabbitmq及踩坑总结

下载erlang mq 下载地址 https://github.com/rabbitmq/erlang-rpm/releases?page7 https://github.com/rabbitmq/rabbitmq-server/tags?afterv3.8.12-beta.1 版本对应 1.官网地址 https://www.rabbitmq.com/download.html ** 2.文件上传 上传到/usr/local/software 目录…

STC15 Proteus仿真DHT11环境湿度采集报警系统STC15W4K32S4-0043

STC15 Proteus仿真DHT11环境湿度采集报警系统STC15W4K32S4-0043 Proteus仿真小实验&#xff1a; STM32 Proteus仿真DHT11环境湿度采集报警系统STC15W4K32S4-0043 功能&#xff1a; Protues版本&#xff1a;8.9 硬件组成&#xff1a;STC15W4K32S4单片机 LCD1602显示器DHT11…

UG\NX 二次开发 获取实体面的面积,测量面积

文章作者:里海 来源网站:https://blog.csdn.net/WangPaiFeiXingYuan 简介: 获取实体面的面积 UF_MODL_ask_mass_props_3d 效果: 代码: #include "me.hpp" //测量面积 double GetFaceArea(tag_t face) {// 抽取片体tag_t solid = NULL_TAG;UF_MODL_extract_face…

前端架构是什么?

文章目录 什么是前端架构前端架构的好处什么项目用前端架构必须要用前端架构嘛&#xff1f; 什么是前端架构 前端架构是指在前端开发中&#xff0c;设计和组织应用程序的基本结构和组件之间的关系的方法和原则。它涉及到如何组织代码、管理数据、处理业务逻辑以及实现用户界面…

基于matlab各种天线阵列几何形状进行建模和可视化(附源码)

一、前言 本示例说明如何使用相控阵系统工具箱对各种天线阵列几何形状进行建模和可视化。这些几何形状还可用于对其他类型的阵列进行建模&#xff0c;例如水听器阵列和麦克风阵列。您可以查看每个绘图的代码&#xff0c;并在您自己的项目中使用它。 二、线性阵列 线性天线阵列在…

【C数据结构】栈_Stack

目录 栈_Stack 【1】栈的概念及结构 【2】栈的实现 【1.1】栈数据结构的接口 【1.2】栈的初始化 【1.3】栈的释放 【1.4】入栈 【1.5】出栈 【1.6】获取栈顶数据 【1.8】获取栈中的有效元素个数 【1.9】检测栈是否为空 栈_Stack 【1】栈的概念及结构 栈&#xff1…

程序员自学能找到工作吗?

程序员是一个非常热门的职业&#xff0c;很多人都想成为一名优秀的程序员。但是&#xff0c;要成为一名程序员&#xff0c;需要学习哪些知识和技能呢&#xff1f;是否一定要上大学或者参加培训班才能学习编程呢&#xff1f;自学编程是否可行呢&#xff1f;自学编程的人能否找到…

山东泰安电力学校,华为ensp考试

文章目录 一、考试要求二、作者的拓扑图&#xff0c;作者的x27&#xff0c;y5三、每个设备的代码&#xff08;可直接复制粘贴运行&#xff0c;端口和连线要一样&#xff09;SW1SW2R0R1R2 四、每个部分的有运行截图SW1SW2R0R1R2 五、运行成功截图 一、考试要求 考试初始化文件下…

马克思期末复习 第一章

目录 第一节 1.物质和意识 2.主观能动性和客观规律 3.运动与静止 第二节 第一节 1.物质和意识 总括&#xff1a;物质决定意识&#xff0c;任何事情都要从实际出发&#xff0c;实事求是 意识的能动作用&#xff1a; 1.意识反作用于物质&#xff0c;好的意识推动物质发展&am…