C++ 二叉树进阶:二叉搜索树

news2024/10/24 10:27:23

目录

二叉搜索树的概念

二叉搜索树的实现

基本结构

插入

1,当树是空树的时候

2,当树不为空的时候

3,纠正后的代码

查找

删除

1,左为空或右为空

 2,左右都不为空

3,删除的完整代码:

二叉搜索树的完整代码

BSTree.h

test.cpp

二叉搜索树的应用

Key 模型

Key-Value 模型

改造二叉搜索树为KV结构

BSTree.h

test.cpp

二叉搜索树的性能分析


二叉搜索树的概念

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

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

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

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

注意:二叉搜索树key值不能相同。

二叉搜索树中序遍历是有序的,因为二叉搜索树的定义决定了左子树节点值小于根节点值、右子树节点值大于等于根节点值(每一颗子树也满足),而中序遍历先左子树、再根节点、后右子树的方式使得遍历结果自然有序。 


二叉搜索树的实现

基本结构

二叉搜索树中的每个节点包含两个指针,分别指向左子树和右子树,以及一个存储关键值(key 值)的数据域。这种结构使得二叉搜索树能够以二叉树的形式组织数据,并通过比较节点的关键值来进行高效的查找、插入和删除操作。

二叉搜索树不能修改里面的key值,如果修改了就会破坏二叉搜索树的结构。

//节点的定义
template<class K>
struct BSTreeNode
{
	BSTreeNode<K>* _left;   //左节点
	BSTreeNode<K>* _right;  //右节点
	K _key;      //存储 key 值
	BSTreeNode(const K& key)  //构造函数完成初始化
		:_left(nullptr)
		,_right(nullptr)
		,_key(key)
	{}
};

template <class K>  //key 关键字,进行比较
class BSTree  //Binary Search Tree
{
	typedef BSTreeNode<K> Node;
private:
	Node* _root = nullptr;  //在类内进行成员初始化
};

插入

1,当树是空树的时候

直接定义一个节点把该节点给 _root。

2,当树不为空的时候

不是空树,去找这个需要插入的位置,插入一定是找一个空的位置,不可能替代某个位置。

如果插入时是相同的元素,则插入失败,因为二叉搜索树不允许出现相同的 key 值。

#pragma once
//节点的定义
template<class K>
struct BSTreeNode
{
	BSTreeNode<K>* _left;   //左节点
	BSTreeNode<K>* _right;  //右节点
	K _key;      //存储 key 值
	BSTreeNode(const K& key)  //构造函数完成初始化
		:_left(nullptr)
		,_right(nullptr)
		,_key(key)
	{}
};

template <class K>  //key 关键字,进行比较
class BSTree  //Binary Search Tree
{
	typedef BSTreeNode<K> Node;
public:
	bool Insert(const K& key)
	{
		//1,根为空的时候
		if (_root == nullptr)
		{
			_root = new Node(key);
		}
		//2,根不为空的时候
		Node* cur = _root;
		while (cur)
		{
			if (key > cur->_key)   //插入的key比当前节点大就往右边走
			{
				cur = cur->_right;   
			}
			else if (key < cur->_key) //插入的key比当前节点小就往左边走
			{
				cur = cur->_left;
			}
			else                     
			{
				return false; //插入的key和当前节点相等,就插入失败
			}
		}
		cur = new Node(key);
		return true;
	}
	//中序遍历
	void _InOrder(Node* root) 
	{
		if (root == nullptr)
			return;
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}
	void InOrder() 
	{
		_InOrder(_root);
		cout << endl;
	}
private:
	Node* _root = nullptr;  //在类内进行成员初始化
};
void Test() 
{
	BSTree<int> t;
	int a[] = { 5,3,4,1,7,8,2,6,0,9 };
	for (auto e : a)
	{
		t.Insert(e);
	}
	t.InOrder();  
}

通过测试我们会发现,这里只有 5 插入成功了,也就是根节点插入成功,那么这段代码存在一定的问题,如何解决呢???

问题:在循环中,只是不断地更新 cur 指针,让它指向树中的不同节点,但没有记录下新节点应该连接的父节点。
    当找到空位置并创建新节点 cur = new Node(key) 后,新节点与树中的其他节点没有任何连接,导致新节点成为一个孤立的节点,没有真正插入到树中。

改进:

  1. 添加一个 parent 指针来记录新节点的父节点。在循环中,当更新 cur 指针时,也同时更新 parent 指针。
  2. 在找到插入位置后,根据 keyparent->_key 的大小关系,将新节点连接到父节点的左子树或右子树。

3,纠正后的代码

#pragma once
//节点的定义
template<class K>
struct BSTreeNode
{
	BSTreeNode<K>* _left;   //左节点
	BSTreeNode<K>* _right;  //右节点
	K _key;      //存储 key 值
	BSTreeNode(const K& key)  //构造函数完成初始化
		:_left(nullptr)
		,_right(nullptr)
		,_key(key)
	{}
};

template <class K>  //key 关键字,进行比较
class BSTree  //Binary Search Tree
{
	typedef BSTreeNode<K> Node;
public:
	bool Insert(const K& key)
	{
		//当树是空树的时候
		if (_root == nullptr) 
		{
			_root = new Node(key);
			return true;
		}
		//树不为空的时候
		Node* parent = nullptr;  //用一个节点来记录cur的父亲
		Node* cur = _root;
		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;
	}

	//中序遍历
	void _InOrder(Node* root) 
	{
		if (root == nullptr)
			return;
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}
	void InOrder() 
	{
		_InOrder(_root);
		cout << endl;
	}
private:
	Node* _root = nullptr;  //在类内进行成员初始化
};
void Test() 
{
	BSTree<int> t;
	int a[] = { 5,3,4,1,7,8,2,6,0,9 };
	for (auto e : a)
	{
		t.Insert(e);
	}
	t.InOrder();  
}

查找

查找和插入类似,如果比当前节点小就往左边找,如何比当前节点大就往右边找,不断更新cur,直到找返回 true,如果没有找到,返回 false。

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

删除

二叉搜索树重点在于删除操作,也比较简单。

在实现Erase的时候我们不能使用Find,因为我们还需用到它的父亲,所以这里还是使用双指针。

当我们试删除这些节点,我们可以发现可以存在这些情况:

1,删除2最好删,把2删除之后,还需要把2的右置成nullpt,不然就是野指针了。

2,  当前节点左为空,父亲指向我的右,当前节点右为空,父亲指向我的左边。

3,叶子结点也可以归类到这种左为空或者右为空,让父亲指向左/右。

3,当左右都不为空的时候不能直接删除,用替换法删除
可以找左子树的最大节点(最右节点)或者右子树的最小节点最左节点)替代它

综上所述:

① 左为空

② 右为空

③ 左右都不为空

1,左为空或右为空

我们不能单单只看cur的左右是否为空,然后直接用parent去指向cur的左右,而是我们需要去观察 cur 属于parent 左边还是右边,如果在左边就用 parent 的左边去指向cur的左或者右,如果在右边就用 parent 的右边去指向cur的左或者右。

右为空和左为空同理。

bool Erase(const K& key)
	{
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				//找到了
                //1,左边为空
				if (cur->_left == nullptr)
				{
					if (parent->_right == cur)
						parent->_right = cur->_right;
					else
						parent->_left = cur->_right;
					delete cur;
				}
                //2,右边为空
				else if (cur->_right == nullptr) 
				{
					if (parent->_left = cur)
						parent->_left = cur->_left;
					else
						parent->_right = cur->_left;
					delete cur;
				}
				else  //3,左右都不为空
				{
					
				}
				return true;
			}
		}
		return false;
	}

 2,左右都不为空

左右都不为空:找左树的最大节点,或者右树的最小节点,也就是左子树的最右节点,或者右子树的最左节点

bool Erase(const K& key)
	{
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				//找到了
                //1,左边为空
				if (cur->_left == nullptr)
				{
					if (parent->_right == cur)
						parent->_right = cur->_right;
					else
						parent->_left = cur->_right;
					delete cur;
				}
                //2,右边为空
				else if (cur->_right == nullptr) 
				{
					if (parent->_left = cur)
						parent->_left = cur->_left;
					else
						parent->_right = cur->_left;
					delete cur;
				}
				else  //3,左右都不为空
				{
					Node* rightMinParent = nullptr;
                    Node* rightMin = cur->_right;
                    while (rightMin->_left) 
                    {
	                    rightMinParent = rightMin;
	                    rightMin = rightMin->_left;
                    }
                    //替代
                    cur->_key = rightMin->_key;
                    //转换成删除rightMin 
	                rightMinParent->_left = rightMin->_right;
                    delete rightMin;
				}
				return true;
			}
		}
		return false;
	}

假设我一上来就删除 7 这棵树存在问题

纠正后的代码:

Node* rightMinParent = cur;
Node* rightMin = cur->_right;
while (rightMin->_left) 
{
	rightMinParent = rightMin;
	rightMin = rightMin->_left;
}
//替代
cur->_key = rightMin->_key;
//转换成删除rightMin (rightMin是左为空,父亲指向它的右边)
if (rightMin == rightMinParent->_left) 
	rightMinParent->_left = rightMin->_right;
else
	rightMinParent->_right = rightMin->_right;
delete rightMin;

如果把这棵树删空也会存在问题 

纠正后的代码: 

if (cur->_left == nullptr)
{
	if (cur == _root)  //当删除的是根节点的时候
	{
		_root = cur->_right;
	}
	else 
	{
		if (parent->_right == cur)
			parent->_right = cur->_right;
		else
			parent->_left = cur->_right;
	}
	delete cur;
}
else if (cur->_right == nullptr) 
{
	if (cur == _root)   //当删除的是根节点的时候
	{
		_root = cur->_left;  
	}
	else 
	{
		if (parent->_left == cur)
			parent->_left = cur->_left;
		else
			parent->_right = cur->_left;
	}
	delete cur;
}

3,删除的完整代码:

//删除
bool Erase(const K& key) 
{
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (key > cur->_key) 
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (key < cur->_key) 
		{
			parent = cur;
			cur = cur->_left;
		}
		else 
		{
			//找到了,开始删除
			// 1、左为空
			// 2、右为空
			// 3、左右都不为空
			if (cur->_left == nullptr) 
			{
				if (cur == _root) 
				{
					_root = cur->_right;
				}
				else 
				{
					if (parent->_right == cur)
						parent->_right = cur->_right;
					else
						parent->_left = cur->_right;
				}
				delete cur;
			}
			else if (cur->_right == nullptr) 
			{
				if (cur == _root)
				{
					_root = cur->_left;
				}
				else 
				{
					if (parent->_left == cur)
						parent->_left = cur->_left;
					else
						parent->_right = cur->_left;
				}
				delete cur;
			}
			else 
			{
				Node* rightMinParent = cur;
				Node* rightMin = cur->_right;
				while (rightMin->_left) 
				{
					rightMinParent = rightMin;
					rightMin = rightMin->_left;
				}
				//替代
				cur->_key = rightMin->_key;
				//转换成删除rightMin (rightMin是左为空,父亲指向它的右边)
				if (rightMin == rightMinParent->_left) 
					rightMinParent->_left = rightMin->_right;
				else 
					rightMinParent->_right = rightMin->_right;
				delete rightMin;
			}
			return true;
		}
	}
	return false;
}

二叉搜索树的完整代码

BSTree.h

#pragma once
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>  //key 关键字,进行比较
class BSTree  //Binary Search Tree
{
	typedef BSTreeNode<K> Node;
public:
	//插入
	bool Insert(const K& key) 
	{
		//当树是空树的时候
		if (_root == nullptr) 
		{
			_root = new Node(key);
			return true;
		}
		//树不为空的时候
		Node* parent = nullptr;
		Node* cur = _root;
		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) 
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (key < cur->_key) 
			{
				parent = cur;
				cur = cur->_left;
			}
			else 
			{
				//找到了,开始删除
				// 1、左为空
				// 2、右为空
				// 3、左右都不为空
				if (cur->_left == nullptr) 
				{
					if (cur == _root) 
					{
						_root = cur->_right;
					}
					else 
					{
						if (parent->_right == cur)
							parent->_right = cur->_right;
						else
							parent->_left = cur->_right;
					}
					delete cur;
				}
				else if (cur->_right == nullptr) 
				{
					if (cur == _root)
					{
						_root = cur->_left;
					}
					else 
					{
						if (parent->_left == cur)
							parent->_left = cur->_left;
						else
							parent->_right = cur->_left;
					}
					delete cur;
				}
				else 
				{
					Node* rightMinParent = cur;
					Node* rightMin = cur->_right;
					while (rightMin->_left) 
					{
						rightMinParent = rightMin;
						rightMin = rightMin->_left;
					}
					//替代
					cur->_key = rightMin->_key;
					//转换成删除rightMin (rightMin是左为空,父亲指向它的右边)
					if (rightMin == rightMinParent->_left) 
						rightMinParent->_left = rightMin->_right;
					else 
						rightMinParent->_right = rightMin->_right;
					delete rightMin;
				}
				return 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;
	}
private:
	Node* _root = nullptr;
};

void TestBSTree() 
{
	BSTree<int> t;
	int a[] = { 5,3,4,1,7,8,2,6,0,9 };
	for (auto e : a) 
	{
		t.Insert(e);
	}
	t.InOrder();
	//1.上来我就删除7,有问题
	t.Erase(7);
	t.InOrder();
	t.Erase(8);
	t.InOrder();
	//2.把这棵树删空,也会存在问题
	/*for (auto e : a) 
	{
		t.Erase(e);
	}
	t.InOrder();*/
	叶子
	t.Erase(2);
	t.InOrder();
	左为空或者右为空
	t.Erase(8);
	t.Erase(1);
	t.InOrder();
	左右都不为空
	t.Erase(5);
	t.InOrder();
}

test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include<string>
#include "BSTree.h"
int main()
{
	TestBSTree();
	return 0;
}

二叉搜索树的应用

Key 模型

  • 在 Key 模型中,数据主要围绕一个关键标识符(Key)来组织。通常这个 Key 是一个唯一的标识,用于快速检索数据。例如,在一个简单的学生信息系统中,学生的学号可以作为 Key。系统可以根据学号快速查找对应的学生信息,但是可能存储的信息相对比较单一,主要就是和这个 Key 直接相关的内容。
  • 它类似于一个索引,重点在于通过这个唯一的标识来定位某个特定的数据项。
  • 以上二叉搜索树的实现使用的就是key模型

Key-Value 模型

  • Key - Value 模型则是由一个 Key 和一个与之对应的 Value 组成的键值对。Key 仍然用于检索,但是 Value 可以是各种各样的数据结构,如字符串、数字、对象、数组等。比如在一个缓存系统中,Key 可以是一个 URL,Value 则是这个 URL 对应的网页内容。
  • 这种模型更强调数据的关联性,Key 和 Value 共同构成了一个完整的数据单元,Value 的内容可以非常丰富,并且 Key 和 Value 之间存在一种明确的对应关系。
  • Key 通常是设计为唯一的标识符,用于精确地定位和区分不同的键值对。而 Value 可以是相同的。例如,在一个记录用户购物偏好的系统中,Key 可以是用户的唯一标识(如用户 ID),Value 是用户喜欢的商品类别。多个用户(不同的 Key)可能都喜欢相同的商品类别(相同的 Value)。
  • 实际中 Key-Value模型应用广泛。

改造二叉搜索树为KV结构

BSTree.h
#pragma once
// Key-Value 模型
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>  //key 关键字,进行比较
class BSTree  //Binary Search Tree
{
	typedef BSTreeNode<K, V> Node;
public:
	//插入
	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 (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, 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* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				//找到了,开始删除
				// 1、左为空
				// 2、右为空
				// 3、左右都不为空
				if (cur->_left == nullptr)
				{
					if (cur == _root)
					{
						_root = cur->_right;
					}
					else
					{
						if (parent->_right == cur)
							parent->_right = cur->_right;
						else
							parent->_left = cur->_right;
					}
					delete cur;
				}
				else if (cur->_right == nullptr)
				{
					if (cur == _root)
					{
						_root = cur->_left;
					}
					else
					{
						if (parent->_left == cur)
							parent->_left = cur->_left;
						else
							parent->_right = cur->_left;
					}
					delete cur;
				}
				else
				{
					Node* rightMinParent = cur;
					Node* rightMin = cur->_right;
					while (rightMin->_left)
					{
						rightMinParent = rightMin;
						rightMin = rightMin->_left;
					}
					//替代
					cur->_key = rightMin->_key;
					//转换成删除rightMin (rightMin是左为空,父亲指向它的右边)
					if (rightMin == rightMinParent->_left)
						rightMinParent->_left = rightMin->_right;
					else
						rightMinParent->_right = rightMin->_right;
					delete rightMin;
				}
				return true;
			}
		}
		return false;
	}
	//中序遍历
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;
		_InOrder(root->_left);
		cout << root->_key << ":" << root->_value << endl;
		_InOrder(root->_right);
	}
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
private:
	Node* _root = nullptr;
};

void TestBSTree()
{
    //输入单词,查找单词对应的中文翻译
	/*BSTree<string, string> dict;
	dict.Insert("sort", "排序");
	dict.Insert("string", "字符串");
	dict.Insert("tree", "树");
	dict.Insert("insert", "插入");
	string str;
	while (cin >> str)
	{
		BSTreeNode<string, string>* ret = dict.Find(str);
		if (ret)
		{
			cout << ret->_value << endl;
		}
		else
		{
			cout << "无此单词" << endl;
		}
	}*/

	//以后很常用,统计水果的个数
	string strArr[] = { "西瓜","西瓜" ,"樱桃","苹果","香蕉","西瓜" ,"西瓜","哈密瓜" ,"西瓜" ,"西瓜" };
	BSTree<string, int> countTree;
	for (auto str : strArr)
	{
		BSTreeNode<string, int>* ret = countTree.Find(str);
		if (ret == nullptr)
		{
			countTree.Insert(str, 1);
		}
		else
		{
			ret->_value++;
		}

	}
	countTree.InOrder();
}
test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include<string>
#include "BSTree.h"
int main()
{
	TestBSTree();
	return 0;
}

二叉搜索树的性能分析

最好情况

  • 对于平衡的二叉搜索树,插入操作首先需要找到插入位置。因为树是平衡的,这个查找过程类似于查找操作,时间复杂度为 O(logN)。
  • 找到位置后,插入新节点的操作本身时间复杂度为O(1) (只需要修改指针来连接新节点)。所以,整体插入操作在最好情况下的时间复杂度为O(logN)。

最坏情况

  • 当二叉搜索树退化为链表时,插入操作需要先遍历链表找到合适的插入位置。例如,若按照从小到大的顺序插入节点,要插入一个新的最大值,需要遍历到链表的末尾。此时,插入操作的时间复杂度为 O(N)。

问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优?那么就有我们后续学习的AVL树和红黑树。

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

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

相关文章

hadoop-Zookeeper安装

hadoop-Zookeeper安装 Ububtu18.04安装Zookeeper3.7.1 环境与版本 这里采用的ubuntu18.04环境的基本配置为&#xff1a; hostname 为master 用户名为hadoop 静态IP为 192.168.100.3 网关为 192.168.100.2 防火墙已经关闭 /etc/hosts已经配置全版本下载地址&#xff1a; htt…

Director3D: Real-world Camera Trajectory and 3DScene Generation from Text 论文解读

目录 一、概述 二、相关工作 1、文本到3D生成 2、3DGS 三、Director3D 1、Cinematographer 2、Decorator 3、Detailer 4、Loss 一、概述 该论文提出利用真实世界数据集&#xff0c;设计一个从文本生成真实世界3D场景和自适应相机轨迹的强大的开放世界文本到3D生成框架…

Git使用GUI界面实现任意历史版本对比

首先进入版本历史查看界面 标记某次提交 选择某次提交并和标记的提交对比 可以查看比较结果了&#xff0c;具体到每一个文件每一行代码

鸿蒙HarmonyOS NEXT 5.0开发(2)—— ArkUI布局组件

文章目录 布局Column&#xff1a;从上往下的布局Row&#xff1a;从左往右的布局Stack&#xff1a;堆叠布局Flex&#xff1a;自动换行或列 组件Swiper各种选择组件 华为官方教程B站视频教程 布局 主轴和交叉轴的概念&#xff1a; 对于Column布局而言&#xff0c;主轴是垂直方…

cnn做整图匹配

好像还没有人把cnn在工业机器视觉中使用&#xff0c;我们打破界限&#xff0c;试一试&#xff01; 工业上有很多需求&#xff0c;判断加工产品有还是没有&#xff0c;从前基本上都是使用找斑的方法来判断。 我们可以用cnn代替试试&#xff01; 我们前头cnn最好成绩是&#x…

STM32(二十一):看门狗

WDG&#xff08;Watchdog&#xff09;看门狗&#xff0c;手动重装寄存器的操作就是喂狗。 看门狗可以监控程序的运行状态&#xff0c;当程序因为设计漏洞、硬件故障、电磁干扰等原因&#xff0c;出现卡死或跑飞现象时&#xff0c;看门狗能及时复位程序&#xff0c;避免程序陷入…

免费送源码:Node.JS+Express+MySQL Express 流浪动物救助系统 计算机毕业设计原创定制

摘 要 随着互联网大趋势的到来&#xff0c;社会的方方面面&#xff0c;各行各业都在考虑利用互联网作为媒介将自己的信息更及时有效地推广出去&#xff0c;而其中最好的方式就是建立网络管理系统&#xff0c;并对其进行信息管理。由于现在网络的发达&#xff0c;流浪动物救助系…

python基础综合案例(数据可视化—折线图可视化)

可视化案例的学习目标&#xff1a; 通过案例&#xff0c;回忆巩固python基础的语法 锻炼编程能力&#xff0c;熟练语法的使用 1.json数据格式 两种不同的语言由于数据格式不同&#xff0c;所以没有办法直接沟通&#xff0c;就比如我们可以将python 的数据格式转成json&…

VirtualBox虚拟机桥接模式固定ip详解

VirtualBox虚拟机桥接模式固定ip详解 VirtualBox 桥接设置Ubuntu 24.04使用固定IP问题记录 VirtualBox 桥接设置 为什么设置桥接模式&#xff1f;桥接模式可以实现物理机和虚拟机互相通信&#xff0c;虚拟机也可以访问互联网&#xff08;推荐万金油&#xff09;&#xff0c;物…

STM32通信协议-I2C

目录 一&#xff0c;IC2的协议规则 I2C总线是PHILIPS公司开发的两线式串行总线&#xff0c;I2C总线主要解决了单片机一对多通信的问题 两根通信线&#xff1a;SCL,SDA&#xff0c;同步&#xff0c;半双工通信&#xff0c;支持数据应答机制&#xff0c;支持总线挂载多设备。 …

Verilog:参数(parameter)的使用

相关阅读 Verilog基础https://blog.csdn.net/weixin_45791458/category_12263729.html?spm1001.2014.3001.5482 参数(parameter)一般用于定义常数&#xff0c;常用于进行可配置的参数化设计中&#xff0c;本文将对参数的使用进行详细介绍。 首先来看看参数的BNF范式&#xff…

Hadoop 安装教程——单节点模式和分布式模式配置

文章目录 一、预备知识1.1 Hadoop 发行版本1.2 部署方式 二、预备条件2.1 环境准备2.2 创建新用户(可选)2.3 配置 SSH 无密码登录2.4 下载 Hadoop2.5 编辑 hadoop-env.sh 脚本2.6 编辑 dfs 和 yarn 脚本 三、单节点模式部署3.1 官方使用案例3.2 查看运行结果 四、伪分布模式部署…

用哪种建站程序做谷歌SEO更容易?

做网站很容易&#xff0c;但做一个能带来流量和订单的网站就没那么简单了。尤其是在谷歌SEO优化方面&#xff0c;不同的建站程序对SEO的支持程度也不同。在这方面&#xff0c;WordPress和Shopify无疑是最佳选择。 WordPress作为一个内容管理系统&#xff08;CMS&#xff09;&am…

关键词提取技术:TF-IDF 详解

1. 什么是TF-IDF&#xff1f; TF-IDF&#xff08;Term Frequency-Inverse Document Frequency&#xff09; 是一种统计方法&#xff0c;用于评估单词在文档集或语料库中的重要性。它是自然语言处理和信息检索中的核心技术之一。 TF-IDF主要基于以下两个概念&#xff1a; TF&a…

Java毕业设计 基于SSM jsp餐厅卫生安全系统

Java毕业设计 基于SSM jsp餐厅卫生安全系统 这篇博文将介绍一个基于SSM框架和jsp开发的餐厅卫生安全系统&#xff0c;适合用于Java毕业设计。 功能介绍 餐厅人员: 注册 登录 首页 图片轮播 窗口信息 窗口详情 文明窗口 差评窗口 系统公告 个人中心 管理员&#xff1a;…

亚信安全DeepSecurity中标知名寿险机构云主机安全项目

近日&#xff0c;亚信安全DeepSecurity成功中标国内知名寿险机构的云主机安全项目。亚信安全凭借在云主机安全防护领域的突出技术优势&#xff0c;结合安全运营的能力&#xff0c;以“实战化”为指导&#xff0c;为用户提供无惧威胁攻击、无忧安全运营的一站式云安全体系&#…

Anaconda和Pycharm超详细安装教程(2024版本+Win11)

一、安装Anaconda 1.1 下载Anaconda 在官方网站(Free Download | Anaconda)上下载适用于你的操作系统的 Anaconda 安装包。(这里以windows为例) 1.2 安装Anaconda 打开下载的安装包,并按照安装向导的指示进行安装。在安装过程中,你可以选择默认的安装选项,也可以根据…

Visual Studio安装图文详解教程

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 教程说明 本教程旨在详细介绍 Visual Studio 社区版的安装过程及其注意事项。 Visual Studio简介 Visual Studio 社区版功能完备且可扩展的免费 IDE&#xff0c;可用于创…

NVR接入录像回放平台EasyCVR视频融合平台语音对讲配置

国标GB28181视频平台EasyCVR视频融合平台可拓展性强、视频能力灵活&#xff0c;平台可提供视频监控直播、云端录像、云存储、录像检索与回看、智能告警、平台级联、云台控制、语音对讲、智能分析接入等功能。其中&#xff0c;在语音对讲方面&#xff0c;NVR接入录像回放平台目前…

4种鼓励创业创新的方法

随着市场趋于饱和&#xff0c;许多企业&#xff0c;尤其是初创企业&#xff0c;很难在竞争中保持领先地位。技术为企业彻底改变其营销和管理策略铺平了道路。另一个经过实践检验的成功渗透特定市场的方法是在办公室内部激发创新&#xff0c;从员工到品牌皆如此。 那么究竟如何…