[算法]二叉搜索树(BST)

news2024/9/21 12:25:57

           二叉搜索树(Binary Search Tree),也称二叉排序树二叉查找树。

一、二叉搜索树的性质

        二叉搜索树是一棵二叉树,可以为空。

当二叉搜索树不为空时:

1、非空左子树的所有键值小于其根结点的键值。

2、非空右子树的所有键值大于其根结点的键值。

3、左、右子树都是二叉搜索树。

假如以序列8, 3, 1, 10, 6, 4, 7, 14, 13依次插入到二叉搜索树中,其二叉树的树状图为:

二、二叉搜索树的抽象数据类型定义

二叉树:二叉搜索树(Binary Search Tree)
二叉搜索树 (BST) 是一种特殊类型的二叉树,其中每个顶点最多可以有两个子项。此结构遵循 BST 属性规定给定顶点的左侧子树中的每个结点的值必须小于给定顶点的值,并且右侧子树中的每个结点的值必须大于给定顶点的值。

模板类:template<class T>

二叉树结点:BSTNode

模板类:template<class T>

搜索二叉树:BSTree

二叉树节点:typedef BSTNode<T> Node;

二叉搜索树操作:

构造:BSTree();

析构:~BSTree();

查找:Node* find(const T& key);

插入:bool insert(const T& x);

删除:bool erase(const T& key);

中序遍历结点并打印关键值:void inOrder();

抽象数据类型定义的具体代码: 

//二叉搜索树的结点
template<class T>
struct BSTNode
{
	T _val; //结点存储的数据
	BSTNode<T>* _pLeft; //指向左孩子的指针
	BSTNode<T>* _pRight; //指向右孩子的指针

	//构造函数
	BSTNode(const T& val = T()):_val(val),_pLeft(nullptr),_pRight(nullptr){}
};

//二叉搜索树
template<class T>
class BSTree
{
private:
	typedef BSTNode<T> Node;
	Node* _root;//根结点
public:
	//构造
	BSTree(); 

	//查找
	Node* find(const T& key);

	//插入
	bool insert(const T& x);

	//删除
	bool erase(const T& key);

	//中序遍历节点并打印val
	void inOrder();

	//析构
	~BSTree();
};

             二叉搜索树的构造、析构、以及中序遍历结点的实现直接给出,不再赘述。

//二叉搜索树
template<class T>
class BSTree
{
private:
	typedef BSTNode<T> Node;
	Node* _root;//根结点

	//中序递归遍历
	void _inOrder(BSTNode<T>* root)
	{
		if (!root)
			return;

		_inOrder(root->_pLeft);
		cout << root->_val << " ";
		_inOrder(root->_pRight);
	}

	//后序遍历释放结点
	void destroy(BSTNode<T>* root)
	{
		if (!root)
			return;

		destroy(root->_pLeft);
		destroy(root->_pRight);
		delete root;
	}

public:
	//构造
	BSTree():_root(nullptr){}

	//查找
	Node* find(const T& key);

	//插入
	bool insert(const T& x);

	//删除
	bool erase(const T& key);

	//中序遍历节点并打印val
	void inOrder()
	{
		_inOrder(_root);
	}

	//析构
	~BSTree()
	{
		destroy(_root);
		_root = nullptr;
	}
};

三、二叉搜索树各操作的实现

(一)、查找

            根据二叉搜索树的性质,查找某个关键值时,将关键值和顶点的值作比较,如果比顶点的值小,那么去到左子树继续进行查找,如果比顶点的值大,去到右子树继续进行查找,如此重复......如果关键值和顶点的值相等,那么该结点即为我们所要找的结点,返回该结点的指针,如果遍历到了叶子结点之后还不匹配,那么查找结束,该二叉搜索树不存在待查找的值,返回空。

以下面这棵树为例,演示一下查找4的过程。

比较4和8,4小于8,所以在左边搜索。

比较4和3,4大于3,所以在右边搜索。

比较4和6,4小于6,所以在左边搜索。

找到值4。

 代码实现:

//查找
Node* find(const T& key)
{
	Node* pCur = _root;

	while (pCur)
	{
		if (key > pCur->_val) //key比val大,去右边查找
			pCur = pCur->_pRight;
		else if (key < pCur->_val) //key比val小,去左边查找
			pCur = pCur->_pLeft;
		else //key和val相等
			return pCur; //返回该节点的指针
	}

	//找不到
	return nullptr;
}

  查找的时间复杂度分析

         由于二叉树的性质,二叉搜索树的查找次数最多为该二叉树的高度次,但是其时间复杂度并不为O(logn),只有当二叉搜索树近似一棵完全二叉树时,其查找的时间复杂度才为O(logn),如果二叉树退化为一棵斜二叉树,那么其查找的时间复杂度为O(n)。从平均性能上来看,普通的搜索二叉树的查找时间复杂度为O(n)

(二)、插入 

        插入操作和上面的查找操作差不多,只要找到合适的空位插入即可,当待插入的值小于当前顶点的值时,往左走,当待插入的值大于顶点的值时,往右走,如此重复,当遍历到空时,这个时候的位置就是待插入结点所应该插入的位置,将待插入的结点和该位置的双亲结点连接起来即可。

下面是在二叉搜索树中插入一个5的过程:

比较5和8,5小于8,往左走。

比较5和3,5大于3,往右走。

比较5和6,5小于6,往左走。

比较5和4,5大于4,往右走。由于走到了尽头,则将5插入到4的右边。

 代码的具体实现:

//插入
bool insert(const T& x)
{
	//如果为空树,则待插入的结点即为根节点
	if (!_root)
	{
		_root = new Node(x);
		return true;
	}

	Node* pParent = nullptr;	//记录pCur的双亲节点
	Node* pCur = _root;	

	//找到待插入结点该插入的位置
	while (pCur)
	{
		if (x > pCur->_val) //x比val大,去右边
		{
			pParent = pCur;
			pCur = pCur->_pRight;
		}
		else if (x < pCur->_val) //x比val小,去左边
		{
			pParent = pCur;
			pCur = pCur->_pLeft;
		}	
		else //x和val相等,结点已经存在,插入失败
			return false;
	}

	//连接待插入的结点和双亲结点
	if (x > pParent->_val) //x的值大于双亲的值,插入到右边去
		pParent->_pRight = new Node(x);
	else //x的值小于双亲的值,插入到左边去
		pParent->_pLeft = new Node(x);
	
	return true;
}

代码实现需要注意的细节: 

1、特殊处理空树的情况,如果是空树,那么待插入的结点即为根节点。

2、在往下遍历二叉树时,需要额外声明定义一个指向双亲结点的指针,用于保存双亲结点的位置以连接待插入的结点。

下面是对二叉搜索树进行阶段性测试的代码: 

void test1()
{
	BSTree<int> t1;
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };

	//遍历数组插入数据,创建一棵二叉搜索树
	for (auto e : a)
	{
		t1.insert(e);
	}

	//中序遍历结点打印数据
	t1.inOrder();
	
	cout << endl;

	//查找结点
	BSTNode<int>* ret;
	ret = t1.find(4);

	if (ret)
		cout << ret->_val << endl;
}

运行结果:

1 3 4 6 7 8 10 13 14
4

        因为左子树的值小于根结点的值,右子树的值大于根结点的值,所以按照中序来遍历二叉搜索树的所有结点就能得到一个有序的序列,所以二叉搜索树又叫二叉排序树。

(三)、删除

        搜索二叉树的删除操作比较复杂,需要分情况讨论。

情况1:删除的结点是叶子节点

        这种情况删除比较简单,直接删除掉叶子结点,并将其双亲结点中原本指向该结点的指针置空即可。

情况2:删除的结点有且只有一个孩子(即有一个子树)

        将其双亲结点中原本指向待删除的结点的指针指向待删除的结点的孩子结点,然后将待删除的结点删除,结果相当于待删除的结点将孩子托付给了双亲照顾。

 

        这种情况还有一种比较特殊的情况需要处理:当待删除的结点是根结点时,待删除的根节点没有双亲结点可以托付自己的孩子。这个时候只需要将其待删除的根结点直接删除,让其孩子成为新的根结点即可。    

        另外,情况一和情况二可以合为一种情况,因为情况一中相当于把叶子结点的孩子(nullptr)托付给了双亲结点,达到了删除叶子结点并将其双亲中原本指向待删除的结点的指针置为空的目的。 

情况3:删除的结点具有两个孩子 (左子树和右子树都存在)

        这种情况下直接原地删除待删除的结点比较麻烦,我们可以将其转化为情况二来解决。我们可以从待删除的结点的右子树中找最小的值(或者左子树中找最大的值)和待删除的结点的值进行交换,交换后,除了待删除的值,其结果仍然满足二叉搜索树的结构。

        要找到右子树的最小值,只需要从其右子树的根节点开始,一直往左走,走到不能再走为止,该尽头处的结点就是右子树的最小值的结点了,这个结点的位置称为右子树中最小值的结点位置,这个结点一定没有左孩子(右孩子可能有也可能没有),所以删除它的话就相当于情况2一样:当右子树中最小结点和待删除的结点的值交换后,我们需要将右子树的最小值位置的结点的右孩子托付给其双亲结点,再删除右子树中最小值所在位置的结点,这样就达到了删除我们想要删除的结点的目的了。

下面是删除二叉树中的3的示意图:

需要注意的是:不一定都是把双亲结点的左指针与右子树最小值位置的结点的右孩子相连,还可能是双亲结点的右指针与右子树最小值位置的结点的右孩子进行相连。比如说下面这种情况删除3,是将其与双亲节点的右指针相连。

        

代码实现:

//删除
bool erase(const T& key)
{
	Node* pParent = nullptr;	//记录pCur的双亲节点
	Node* pCur = _root;

	//先找到待删除的结点和其双亲结点
	while (pCur)
	{
		if (key > pCur->_val)
		{
			pParent = pCur;
			pCur = pCur->_pRight;
		}
		else if (key < pCur->_val)
		{
			pParent = pCur;
			pCur = pCur->_pLeft;
		}
		else
			break;
	}

	//如果待删除的结点不存在或搜索二叉树为空
	if (!pCur)
		return false;

	//情况一和情况二:删除的结点只有一个孩子
	if (pCur->_pLeft == nullptr)	//左孩子为空,待删除的结点只有右孩子
	{
		//情况二的特殊情况处理:如果删除的结点是根结点
		if (pCur == _root)
		{
			_root = pCur->_pRight; //右孩子成为新的结点
			delete pCur;
			return true;
		}

		if (key > pParent->_val) //待删除的结点比双亲结点的值大
		{
			//双亲的右指针指向待删除结点的右孩子
			pParent->_pRight = pCur->_pRight;
			delete pCur;
			return true;
		}
		else //待删除的结点比双亲结点的值小
		{
			//双亲的左指针指向待删除结点的右孩子
			pParent->_pLeft = pCur->_pRight;
			delete pCur;
			return true;
		}
	}
	else if (pCur->_pRight == nullptr) //右孩子为空,待删除的结点只有左孩子
	{
		//情况二的特殊情况处理:如果删除的结点是根结点
		if (pCur == _root)
		{
			_root = pCur->_pLeft; //左孩子成为新的结点
			delete pCur;
			return true;
		}

		if (key > pParent->_val) //待删除的结点比双亲结点的值大
		{
			//双亲的右指针指向待删除结点的左孩子
			pParent->_pRight = pCur->_pLeft;
			delete pCur;
			return true;
		}
		else //待删除的结点比双亲结点的值小
		{
			//双亲的左指针指向待删除结点的左孩子
			pParent->_pLeft = pCur->_pLeft;
			delete pCur;
			return true;
		}
	}

	//情况三,待删除的结点有左孩子和右孩子
	Node* pRightMinParent = pCur;	//记录右子树中最小值结点的双亲节点
	Node* pRightMin = pCur->_pRight;	//指向右子树中最小值的结点

	//不断往左走,直到走到尽头为止
	while (pRightMin->_pLeft)
	{
		pRightMinParent = pRightMin;
		pRightMin = pRightMin->_pLeft;
	}

	//将待删除的结点的值和右子树中最小值的结点交换
	pCur->_val = pRightMin->_val;

	
	if (pRightMinParent->_pLeft == pRightMin) //双亲结点的左指针指向最小值结点
	{
		pRightMinParent->_pLeft = pRightMin->_pRight;	//双亲结点左指针指向待删除结点的右孩子
		delete pRightMin;
	}
	else //双亲结点的右指针指向最小值结点
	{
		pRightMinParent->_pRight = pRightMin->_pRight;	//双亲结点右指针指向待删除结点的右孩子
		delete pRightMin;
	}

	return true;
}

测试代码:

void test2()
{
	BSTree<int> t1;
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };

	//遍历数组插入数据,创建一棵二叉搜索树
	for (auto e : a)
	{
		t1.insert(e);
	}

	t1.inOrder();
	cout << endl; 

	//测试删除结点
	for (auto e : a)
	{
		t1.erase(e);
		t1.inOrder();
		cout << endl;
	}
}

运行结果:

1 3 4 6 7 8 10 13 14
1 3 4 6 7 10 13 14
1 4 6 7 10 13 14
4 6 7 10 13 14
4 6 7 13 14
4 7 13 14
7 13 14
13 14
13

 四、二叉搜索树的应用

1、K模型:K模型即只有key作为关键码,结点的结构中只需要存储Key即可,关键码即为需要搜索到的值。例如上面的代码展示的就是K模型,测试代码存储的关键码为整形数据。
        应用:存储任意数据类型进行查找或排序。
2. KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。
        应用:英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。
3、具有去重、查找、顺带排序的功能。
        去重:因为二叉搜索树不会将已存在的关键值插入到二叉树中,所以将一组序列组成二叉树搜索树可以达到去重的目的。
        查找:由于二叉搜索树的特殊性质,当二叉树没有退化成斜树或接近完全二叉树时的查找效率高。
        排序:由于二叉树的特殊性质,当中序遍历二叉树的每个结点可以得到有序的序列。

下面是修改成键值对后的代码:

#pragma once
#include <iostream>
using namespace std;

//二叉搜索树的结点
template<class K,class V>
struct BSTNode
{
	K _key; //关键值
	V _val; //关键值所对应的值
	BSTNode<K,V>* _pLeft; //指向左孩子的指针
	BSTNode<K,V>* _pRight; //指向右孩子的指针

	//构造函数
	BSTNode(const K& key = K(),const V& val = V()):_key(key), _val(val), _pLeft(nullptr), _pRight(nullptr) {}
};

//二叉搜索树
template<class K, class V>
class BSTree
{
private:
	typedef BSTNode<K,V> Node;
	Node* _root;//根结点

	//中序递归遍历
	void _inOrder(Node* root)
	{
		if (!root)
			return;

		_inOrder(root->_pLeft);
		cout << root->_val << " ";
		_inOrder(root->_pRight);
	}

	//后序遍历释放结点
	void destroy(Node* root)
	{
		if (!root)
			return;

		destroy(root->_pLeft);
		destroy(root->_pRight);
		delete root;
	}

public:
	//构造
	BSTree():_root(nullptr){}

	//查找
	Node* find(const K& key)
	{
		Node* pCur = _root;

		while (pCur)
		{
			if (key > pCur->_key) //key比key大,去右边查找
				pCur = pCur->_pRight;
			else if (key < pCur->_key) //key比key小,去左边查找
				pCur = pCur->_pLeft;
			else //key和val相等
				return pCur; //返回该节点的指针
		}

		//找不到
		return nullptr;
	}


	//插入
	bool insert(const K& key = K(), const V& val = V())
	{
		//如果为空树,则待插入的结点即为根节点
		if (!_root)
		{
			_root = new Node(key,val);
			return true;
		}

		Node* pParent = nullptr;	//记录pCur的双亲节点
		Node* pCur = _root;	

		//找到待插入结点该插入的位置
		while (pCur)
		{
			if (key > pCur->_key) //x比key大,去右边
			{
				pParent = pCur;
				pCur = pCur->_pRight;
			}
			else if (key < pCur->_key) //x比key小,去左边
			{
				pParent = pCur;
				pCur = pCur->_pLeft;
			}	
			else //x和val相等,结点已经存在,插入失败
				return false;
		}

		//连接待插入的结点和双亲结点
		if (key > pParent->_key) //x的值大于双亲的值,插入到右边去
			pParent->_pRight = new Node(key, val);
		else //x的值小于双亲的值,插入到左边去
			pParent->_pLeft = new Node(key,val);
		
		return true;
	}

	//删除
	bool erase(const K& key)
	{
		Node* pParent = nullptr;	//记录pCur的双亲节点
		Node* pCur = _root;

		//先找到待删除的结点和其双亲结点
		while (pCur)
		{
			if (key > pCur->_key)
			{
				pParent = pCur;
				pCur = pCur->_pRight;
			}
			else if (key < pCur->_key)
			{
				pParent = pCur;
				pCur = pCur->_pLeft;
			}
			else
				break;
		}

		//如果待删除的结点不存在或搜索二叉树为空
		if (!pCur)
			return false;

		//情况一和情况二:删除的结点只有一个孩子
		if (pCur->_pLeft == nullptr)	//左孩子为空,待删除的结点只有右孩子
		{
			//情况二的特殊情况处理:如果删除的结点是根结点
			if (pCur == _root)
			{
				_root = pCur->_pRight; //右孩子成为新的结点
				delete pCur;
				return true;
			}

			if (key > pParent->_key) //待删除的结点比双亲结点的值大
			{
				//双亲的右指针指向待删除结点的右孩子
				pParent->_pRight = pCur->_pRight;
				delete pCur;
				return true;
			}
			else //待删除的结点比双亲结点的值小
			{
				//双亲的左指针指向待删除结点的右孩子
				pParent->_pLeft = pCur->_pRight;
				delete pCur;
				return true;
			}
		}
		else if (pCur->_pRight == nullptr) //右孩子为空,待删除的结点只有左孩子
		{
			//情况二的特殊情况处理:如果删除的结点是根结点
			if (pCur == _root)
			{
				_root = pCur->_pLeft; //左孩子成为新的结点
				delete pCur;
				return true;
			}

			if (key > pParent->_key) //待删除的结点比双亲结点的值大
			{
				//双亲的右指针指向待删除结点的左孩子
				pParent->_pRight = pCur->_pLeft;
				delete pCur;
				return true;
			}
			else //待删除的结点比双亲结点的值小
			{
				//双亲的左指针指向待删除结点的左孩子
				pParent->_pLeft = pCur->_pLeft;
				delete pCur;
				return true;
			}
		}

		//情况三,待删除的结点有左孩子和右孩子
		Node* pRightMinParent = pCur;	//记录右子树中最小值结点的双亲节点
		Node* pRightMin = pCur->_pRight;	//指向右子树中最小值的结点

		//不断往左走,直到走到叶子结点为止
		while (pRightMin->_pLeft)
		{
			pRightMinParent = pRightMin;
			pRightMin = pRightMin->_pLeft;
		}

		//将待删除的结点的值和右子树中最小值的结点交换
		pCur->_key = pRightMin->_key;

		
		if (pRightMinParent->_pLeft == pRightMin) //双亲结点的左指针指向最小值结点
		{
			pRightMinParent->_pLeft = pRightMin->_pRight;	//双亲结点左指针指向待删除结点的右孩子
			delete pRightMin;
		}
		else //双亲结点的右指针指向最小值结点
		{
			pRightMinParent->_pRight = pRightMin->_pRight;	//双亲结点右指针指向待删除结点的右孩子
			delete pRightMin;
		}

		return true;
	}

	//中序遍历节点并打印val
	void inOrder()
	{
		_inOrder(_root);
	}

	//析构
	~BSTree()
	{
		destroy(_root);
		_root = nullptr;
	}
};

 测试代码:

void test3()
{
	// 输入单词,查找单词对应的中文翻译
	BSTree<string, string> dict;
	dict.insert("string", "字符串");
	dict.insert("tree", "树");
	dict.insert("left", "左边、剩余");
	dict.insert("right", "右边");
	dict.insert("sort", "排序");
	// 插入词库中所有单词
	string str;
	while (cin >> str)
	{
		BSTNode<string, string>* ret = dict.find(str);
		if (ret == nullptr)
		{
			cout << "单词拼写错误,词库中没有这个单词:" << str << endl;
		}
		else
		{
			cout << str << "中文翻译:" << ret->_val << endl;
		}
	}
}

void test4()
{
	// 统计水果出现的次数
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
   "苹果", "香蕉", "苹果", "香蕉" };
	BSTree<string, int> countTree;
	for (const auto& str : arr)
	{
		// 先查找水果在不在搜索树中
		// 1、不在,说明水果第一次出现,则插入<水果, 1>
		// 2、在,则查找到的节点中水果对应的次数++
		BSTNode<string, int>* ret = countTree.find(str);
		if (ret == nullptr)
		{
			countTree.insert(str, 1);
		}
		else
		{
			ret->_val++;
		}
	}
	countTree.inOrder();
}

运行结果:

test3:

 man
单词拼写错误,词库中没有这个单词:man
string
string中文翻译:字符串
tree
tree中文翻译:树
left
left中文翻译:左边、剩余
right
right中文翻译:右边
sort
sort中文翻译:排序

test4:

6 3 2

 

        

         

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

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

相关文章

linux开发板配置-双网卡桥接-问题记录

韦东山i.mx6ull开发板 使用usb网卡连接电脑&#xff0c;ubuntu进行双网卡配置 问题&#xff1a; mount: mounting 192.168.96.131:/home/book/nfs_rootfs on /mnt failed: No route to host 乱七八糟各种配置&#xff0c;能ping通&#xff0c;但是nfs挂载不上 解决&#xff1a…

推荐系统与搜索系统架构

一、推荐系统逻辑 推荐的本质就是为了解决信息过载造成的“选择困难症”&#xff0c;便于用户能够在自己选物之前&#xff0c;系统已经帮用户筛选到了最想要的信息。 以下是我按照用户打开APP进入推荐页面时&#xff0c;推荐系统返回给该用户推荐列表的整体流程&#xff1a; …

Xshell安装图文

1.下载 通过百度网盘分享的文件&#xff1a;Xshell安装图文 链接&#xff1a;https://pan.baidu.com/s/1k4ShbhUVQmdxpM9H8UYOSQ 提取码&#xff1a;kdxz --来自百度网盘超级会员V3的分享 2.安装 3.连接与使用 见下载

vector 简单模拟实现

目录 一. vector成员变量 二. vector的构造函数和析构函数 三. vector的成员函数 1. 容量操作函数 扩容操作 (1). finish更新问题 (2). 扩容深浅拷贝问题 resize与尾插、尾删与判空 insert与erase与clear 2. 函数重载 (1). 赋值运算符重载 (2). [ ]重载进行访问 四. …

phpstudy搭建sqlilabs本地靶场

请先在网上解决好前置条件的下载和安装&#xff1a;phpstudy、vscode、navicat premium(非必要)、sqlilab的压缩包/文件夹 phpstudy--安装sqlilabs 1.打开phpstudy后&#xff0c;我们会用到MySQL5.7.26和Nginx1.15.11 #mysql5.7.26是因为sqlilabs靶场不支持高版本MySQL 2.在软…

练习实践 web中间件httpd-id:2-编译安装-web登录认证设置

参考来源&#xff1a; 用编译的方式安装apache httpd服务 编译安装过程记录 1.下载准备环境&#xff1a; yum -y install gcc gcc-c make pcre pcre-devel gd-devel openssl-devel zlib zlib-devel apr-*根据之前的操作文档和实际安装经验&#xff0c;提前将所需依赖项安装…

sqli-labs注入练习1,2关

sqli-labs第一关 判断是否存在sql注入 可见&#xff0c;根据输入数字的不同&#xff0c;结果也不同 判断sql语句是否是拼接&#xff0c;且是字符型还是数字型 由上可见&#xff0c;这关指定是字符型且存在sql注入漏洞 使用联合注入 第一步&#xff1a;首先知道表格有几列&…

PyCharm 2024.1 最新变化

文章目录 PyCharm 2024.1 最新变化一、新的 AI Assistant 功能 PyCharm Professional1、一键创建包含生成代码的文件2、生成架构感知型 SQL 查询 二、缩小整个 IDE 的选项三、新终端 Beta PyCharm 2024.1 最新变化 pycharm是什么 作为 JetBrains 旗下的一款专为 Python 开发者设…

SQL注入之sqli-labs靶场第二关

手工注入less-2 1.找注入点 通过 ?idsdfsdf 报错发现注入点 经过尝试没有闭合发现是数字型注入 2.猜解字段数量 发现字段数量为3 3.通过union联合查询判断回显点 发现回显点2&#xff0c;3&#xff0c; 4&#xff0c;进行信息收集 数据库版本号&#xff1a;5.7.26 数据库…

【MongoDB】1.MongoDB下载与安装

目录 一、下载 二、安装 三、安装MongoDB Compass 四、连接 一、下载 官网地址&#xff1a; https://www.mongodb.com/download-center/community 二、安装 详细的安装教程可参考&#xff1a; MongoDB安装&#xff08;超详细&#xff09;_安装mongodb-CSDN博客 注意事项1&…

亚马逊爬虫(Amazonbot)IP地址,真实采集数据

一、数据来源&#xff1a; 1、这批亚马逊爬虫&#xff08;Amazonbot&#xff09;IP来源于尚贤达猎头公司网站采集数据&#xff1b; ​ 2、数据采集时间段&#xff1a;2023年10月-2024年7月&#xff1b; 3、判断标准&#xff1a;主要根据用户代理是否包含“Amazonbot”和IP核…

C# Unity 面向对象补全计划 七大原则 之 依赖倒置原则 (DIP)难度:☆☆ 总结:多抽象,多接口,少耦合

本文仅作学习笔记与交流&#xff0c;不作任何商业用途&#xff0c;作者能力有限&#xff0c;如有不足还请斧正 本系列作为七大原则和设计模式的进阶知识&#xff0c;看不懂没关系 请看专栏&#xff1a;http://t.csdnimg.cn/mIitr&#xff0c;查漏补缺 1.依赖倒置原则 (DIP) 这…

【算法题】无重复字符的最长子串(滑动窗口)

目录 一、题目描述 二、解题思路 1、什么是滑动窗口算法&#xff1f; 2、滑动窗口一般解题模板 三、参考答案 一、题目描述 无重复字符的最长子串 给定一个字符串s &#xff0c;请你找出其中不含有重复字符的最长子串的长度。 示例 1: 输入: s "abcabcbb"…

【Linux】vim(工具篇)

文章目录 什么是vimvim的使用普通模式&#xff08;Normal Mode&#xff09; 命令模式&#xff08;Command Mode&#xff09;批量化注释/批量化去注释 vim的配置 什么是vim Vim 是一种高度可配置的文本编辑器&#xff0c;最初由 Bram Moolenaar 在 1991 年基于 vi 编辑器创建。V…

力扣——238.移动零

题目 思路 利用双指针&#xff0c;先找到第一个为0的地方指向&#xff0c;指针2指向下一个&#xff0c;指针1之前是已经处理好的数据&#xff0c;指针2进行遍历&#xff0c;遇到非零则与指针1数据交换&#xff0c;然后指针1。 代码 class Solution { public:void moveZeroes(…

OpenAI not returning a result?

题意&#xff1a;OpenAI 没有返回结果吗&#xff1f; 问题背景&#xff1a; Im trying to use the OpenAI beta but I cant seem to get a result. Im accessing the API via an NPM package (openai-api - npm). I have that setup and working but when I make a request th…

Unity强化工程 之 Mask SortingGroup

本文仅作笔记学习和分享&#xff0c;不用做任何商业用途 本文包括但不限于unity官方手册&#xff0c;unity唐老狮等教程知识&#xff0c;如有不足还请斧正 1.Mask 遮罩故名思意就是起到遮挡作用的罩子:精灵遮罩 - Unity 手册 如果我想让sprite与遮罩发生交互&#xff0c;那么我…

深入理解接口测试:实用指南与最佳实践(三)API文档解析及编写测试用例

​ ​ 您好&#xff0c;我是程序员小羊&#xff01; 前言 这一阶段是接口测试的学习&#xff0c;我们接下来的讲解都是使用Postman这款工具&#xff0c;当然呢Postman是现在一款非常流行的接口调试工具&#xff0c;它使用简单&#xff0c;而且功能也很强大。不仅测试人员会使用…

人工智能大模型 | 通俗讲解AI基础概念

LLM LLM&#xff08;Large Language Models&#xff09;指的是大型语言模型。这些模型是自然语言处理&#xff08;NLP&#xff09;技术的一部分&#xff0c;使用深度学习训练来理解、生成、翻译文本&#xff0c;甚至执行特定的语言相关任务&#xff0c;如问答、文本摘要、编程…

技术方案、实施例和图纸应该怎么写?

技术方案、实施例和图纸应该怎么写&#xff1f;