【C++】二叉搜索树的模拟实现

前言:今天我们学习相对来说比前面轻松一点的内容,二叉搜索树,在之前我们学习过二叉树今天的内容对于我们就会比较简单一点了,一起加油。

💖 博主CSDN主页:卫卫卫的个人主页 💞
👉 专栏分类:高质量C++学习 👈
💯代码仓库:卫卫周大胖的学习日记💫
💪关注博主和博主一起学习!一起努力!
在这里插入图片描述


目录标题

  • 什么是二叉搜索树
  • 二叉搜索树的常见操作
  • 二叉搜索树的基本框架
  • 二叉树的接口
    • 1. 插入
    • 2.遍历
    • 3. 查找
    • 4. 删除
    • 5.析构函数
    • 整体代码:
  • 二叉搜索树的应用


什么是二叉搜索树

二叉搜索树(Binary Search Tree,BST)是一种二叉树的特殊形式,它满足以下性质:

  1. 每个节点最多有两个子节点,分别称为左子节点和右子节点。
  2. 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值。
  3. 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  4. 对于树中的任意节点,它的左子树中的所有节点的值都小于该节点的值,它的右子树中的所有节点的值都大于该节点的值。
  5. 它的左右子树也分别为二叉搜索树。

这些性质确保了二叉搜索树中的数据按照一定的顺序排列,左子树中的节点值小于父节点,右子树中的节点值大于父节点。这样的性质使得二叉搜索树能够提供高效的查找、插入和删除操作。

二叉搜索树常用于实现有序集合或映射,例如在数据库系统中用于索引数据,或是实现搜索功能等(如下图就是标准的一个二叉搜索树)。

在这里插入图片描述


二叉搜索树的常见操作

在C++中,可以使用以下操作实现二叉搜索树(Binary Search Tree,BST):

  1. 插入(Insertion):将一个新的节点插入到二叉搜索树中,保持树的有序性。
  2. 查找(Search):在二叉搜索树中搜索指定值的节点,返回该节点或者NULL(如果没有找到)。
  3. 删除(Deletion):从二叉搜索树中删除指定值的节点,保持树的有序性。
  4. 中序遍历(Inorder Traversal):按照左子树 -> 根节点 -> 右子树的顺序遍历二叉搜索树,这个遍历方式会输出有序的节点值。

这些操作可以通过递归或迭代的方式来实现,具体的实现方式和算法可以根据具体的需求和编程习惯进行选择。


二叉搜索树的基本框架

template <class T>
struct BSTreeNode//搜索树的基本结构
{
	BSTreeNode<T>* _left;
	BSTreeNode<T>* _right;
	T _key;

	BSTreeNode(const T& key)
		:_left(nullptr)
		,_right(nullptr)
		,_key(key)
	{
		;
	}
};

template <class K>//树类,接口实现区域
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
		//基本接口的实现区域;
private:
		Node* _root = nullptr;
	};
};

说明:

  1. 这里写成两个类,一个struct一个class的原因是为了避免在BSTree这个类中额外的写一个接口去专门获取结点。
  2. 这里当的BSTree主要是用于接口的实现。
  3. 这里自己显示的实现默认构造的原因是编译器的默认构造无法满足我们的需求。

二叉树的接口

1. 插入

代码思路:

  1. 插入该数时,此树中没有元素,就直接在该树中开辟一个结点存放插入的数。
  2. 树中不为空,依据二叉搜索树的特性找到指定的位置插入值:①当插入的值比当前结点的值大,就往当前结点的右结点走,反之就往左结点走。②找到应当插入的位置时,将原本该位置的值的连接到插入的值,要保持搜索树原本的特性不能被修改。
  3. 如果发现插入的值和搜索树中的值相等,那就插入失败,停止插入。
  4. 在提前找到插入的位置时候,我们需要用一个partent记录要插入位置的父亲结点,以防止找不到连接处。
    在这里插入图片描述
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;
}

2.遍历

这里我们提一个小知识:二叉搜索树的中序遍历的结果是一个递增有序序列。因为二叉搜索树的定义,左子树上的所有节点的值都小于根节点的值,右子树上的所有节点的值都大于根节点的值,因此,进行中序遍历时,先遍历左子树,然后遍历根节点,最后遍历右子树,所得到的序列是一个递增有序序列。

代码思路:

  1. 由于_root是私有成员,类外无法访问,因此我们通过一个函数对其进行封装。
  2. 关于中序遍历的遍历过程我们这里就不会在过多叙述了,不懂二叉树部分的内容的可以去看看我之前的博客二叉树的模拟实现
template <class T>
struct BSTreeNode//搜索树的基本结构
{
	BSTreeNode<T>* _left;
	BSTreeNode<T>* _right;
	T _key;

	BSTreeNode(const T& key)
		:_left(nullptr)
		,_right(nullptr)
		,_key(key)
	{
		;
	}
};

template <class K>//树类,接口实现区域
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
		//基本接口的实现区域;
	bool insert(const K& key);//插入
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
private:
	void _InOrder(Node* root)//中序遍历
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}

private:
	Node* _root = nullptr;
};


3. 查找

代码思路:

  1. 查找和插入的思路很类似,我们根据搜索树的基本特性,看看查找的这个元素是比当前结点大或者小,大就去当前结点的右子树找,反之就去左子树找。
  2. 如果找到了就返回当前结点的位置,没找到就返回空即可。
Node* Find(const K& key)//查找一个元素
{
	Node* cur = _root;
	while (cur)
	{
		if (cur->_key < key)//当前结点的值小于key值,就去他的右子树找
		{
			cur = cur->_right;
		}
		else if (cur->_key > key)//当前结点的值大于key值,就去他的左子树找
		{
			cur = cur->_left;
		}
		else//找到了就返回当前结点的值
			return cur;
	}
	return nullptr;//找不到返回空

}

4. 删除

代码思路:
1.删除我们需要分三种情况:①左子树为空,要删除的结点只存在右子树。②右子树为空,要删除的结点只存在左子树。③左右两边都有结点的情况。那么我们来根据下面的图像来逐一分析!
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 (cur == _root)//刚刚好就是极端情况,只存在根结点,和一个右子树
				{
					_root = cur->_right;
				}
				else
				{
					if (cur == parent->_left)//判断当前结点,是原本父亲结点的左节点还是右结点
					{
						parent->_left = cur->_right;
					}
					else
					{
						parent->_right = cur->_right;
					}
				}
				delete cur;//删除当前的结点
			}
			else if (cur->_right == nullptr)//2.右子树为空,要删除的结点只存在左子树 
			{
				if (cur == _root)//判断是否就是极端情况
				{
					_root = cur->_left;
				}
				else
				{
					if (cur == parent->_left)
					{
						parent->_left = cur->_left;
					}
					else
						parent->_right = cur->_left;
				}
				delete cur;
			}
			else//左右两边都有结点的情况,查找到要被删除结点的右子树的最左节点替代删除
			{
				Node* rightmin = cur->_right;
				Node* rightminparent = cur;
				while (rightmin->_left)//当他的左子树为空的时候,就是找到了当前的最小结点
				{
					rightminparent = rightmin;
					rightmin = rightmin->_left;
				}
				swap(cur->_key, rightmin->_key);//将两个结点的值交换
				if (rightminparent->_left == rightmin)//是父亲结点的左孩子
				{
					rightminparent->_left = rightmin->_right;
				}
				else//是父亲结点的右孩子
					rightminparent->_right = rightmin->_right;
				delete rightmin;
			}
			return true;
		}
	}
	return false;
}

5.析构函数

代码思路:这里和前面的中序遍历一样的道理,我们把他封装成一个接口,因为无法直接访问_root,这里你用前序遍历、中序遍历还是后序遍历都可以,不懂如何销毁的可以去看看之前二叉树销毁的博客,一样的玩法。

template <class K>//树类,接口实现区域
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
	~BSTree()//析构函数
	{
		destory(_root);
	}
private:
	void destory(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		destory(root->_left);
		destory(root->_right);
		delete root;
	}
private:
		Node* _root = nullptr;
	};
}

整体代码:

#include <iostream>

using namespace std;

namespace BSTree
{
	template <class T>

	struct BSTreeNode//搜索树的基本结构
	{
		BSTreeNode<T>* _left;
		BSTreeNode<T>* _right;
		T _key;

		BSTreeNode(const T& key)
			:_left(nullptr)
			,_right(nullptr)
			,_key(key)
		{
			;
		}
	};

	template <class K>
	class BSTree
	{
		typedef BSTreeNode<K> Node;
	public:
		BSTree() = default;//编译器自动生成默认构造
		Node* Find(const K& key)//查找一个元素
		{
			Node* cur = _root;
			while (cur)
			{
				if (cur->_key < key)//当前结点的值小于key值,就去他的右子树找
				{
					cur = cur->_right;
				}
				else if (cur->_key > key)//当前结点的值大于key值,就去他的左子树找
				{
					cur = cur->_left;
				}
				else//找到了就返回当前结点的值
					return cur;
			}
			return nullptr;//找不到返回空

		}
		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 Erase(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//找到要删除的值
				{
					//1.如果左子树为空,要删除的结点只存在右子树
					if (cur->_left == nullptr)
					{
						if (cur == _root)//刚刚好就是极端情况,只存在根结点,和一个右子树
						{
							_root = cur->_right;
						}
						else
						{
							if (cur == parent->_left)//判断当前结点,是原本父亲结点的左节点还是右结点
							{
								parent->_left = cur->_right;
							}
							else
							{
								parent->_right = cur->_right;
							}
						}
						delete cur;//删除当前的结点
					}
					else if (cur->_right == nullptr)//2.右子树为空,要删除的结点只存在左子树 
					{
						if (cur == _root)//判断是否就是极端情况
						{
							_root = cur->_left;
						}
						else
						{
							if (cur == parent->_left)
							{
								parent->_left = cur->_left;
							}
							else
								parent->_right = cur->_left;
						}
						delete cur;
					}
					else//左右两边都有结点的情况,查找到要被删除结点的右子树的最左节点替代删除
					{
						Node* rightmin = cur->_right;
						Node* rightminparent = cur;
						while (rightmin->_left)//当他的左子树为空的时候,就是找到了当前的最小结点
						{
							rightminparent = rightmin;
							rightmin = rightmin->_left;
						}
						swap(cur->_key, rightmin->_key);//将两个结点的值交换
						if (rightminparent->_left == rightmin)//是父亲结点的左孩子
						{
							rightminparent->_left = rightmin->_right;
						}
						else//右孩子
							rightminparent->_right = rightmin->_right;
						delete rightmin;
					}
					return true;
				}
			}
			return false;
		}


		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}
		~BSTree()//析构
		{
			destory(_root);
		}
	private:
		void destory(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}
			destory(root->_left);
			destory(root->_right);
			delete root;
		}
		void _InOrder(Node* root)//中序遍历
		{
			if (root == nullptr)
			{
				return;
			}
			_InOrder(root->_left);
			cout << root->_key << " ";
			_InOrder(root->_right);
		}
		BSTree(const BSTree<K>& t)
		{
			_root = _Copy(t._root);
		}
		BSTree<K>& operator=(BSTree<K> t)
		{
			swap(_root, t._root);//和之前一样的现代写法

		}

	protected:
		Node* _Copy(const Node* root)
		{
			if (root == nullptr)
				return nullptr;

			Node* newRoot = new Node(root->_key);
			newRoot->_left = _Copy(root->_left);
			newRoot->_right = _Copy(root->_right);

			return newRoot;
		}
	private:
		Node* _root = nullptr;
	};

二叉搜索树的应用

  1. K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
    比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
    以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
  2. KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:
    比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。

源码实现:

namespace key_value
{
	template<class K, class V>
	struct BSTreeNode
	{
		BSTreeNode<K, V>* _left;
		BSTreeNode<K, V>* _right;
		K _key;
		V _value;

		// pair<K, V> _kv;

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

	template<class K, class V>
	class BSTree
	{
		typedef BSTreeNode<K, V> Node;
	public:
		// logN
		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;
				}
				else
				{
					return false;
				}
			}

			cur = new Node(key, value);
			if (parent->_key < key)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}

			return true;
		}

		Node* Find(const K& key)
		{
			Node* cur = _root;
			while (cur)
			{
				if (cur->_key < key)
				{
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					cur = cur->_left;
				}
				else
				{
					return cur;
				}
			}

			return cur;
		}

		bool Erase(const K& key)
		{
			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;
				}
				else
				{
					// 删除
					// 左为空,父亲指向我的右
					if (cur->_left == nullptr)
					{
						//if(parent == nullptr)
						if (cur == _root)
						{
							_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)
						if (cur == _root)
						{
							_root = cur->_left;
						}
						else
						{
							// 右为空,父亲指向我的左
							if (cur == parent->_left)
							{
								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;
						}

						swap(cur->_key, rightMin->_key);

						if (rightMinParent->_left == rightMin)
							rightMinParent->_left = rightMin->_right;
						else
							rightMinParent->_right = rightMin->_right;

						delete rightMin;
					}

					return true;
				}
			}

			return false;
		}

		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}
	private:
		void _InOrder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}

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

好啦,今天的内容就到这里啦,下期内容预告map与set的使用.


结语:进阶的内容有点繁杂,大家一起加油呐!。


🌏🗺️ 这里祝各位接下来的每一天好运连连 💞💞

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

从零开始学量化~Ptrade使用教程——安装与登录

PTrade交易系统是一款高净值和机构投资者专业投资软件&#xff0c;为用户提供普通交易、篮子交易、日内回转交易、算法交易、量化投研/回测/实盘等各种交易工具&#xff0c;满足用户的各种交易需求和交易场景&#xff0c;帮助用户提高交易效率。 运行环境及安装 操作系统&…

计算机的错误计算(二十一)

摘要 两个不相等数相减&#xff0c;差为0&#xff1a; ? 在计算机的错误计算&#xff08;十九&#xff09;中&#xff0c;高中生小明发现本应为0的算式结果不为0. 今天他又发现对本不为0的算式&#xff0c;计算机的输出为0. 在 Python 中计算 &#xff1a; 则输出为0. 若用 C…

必备的 Adobe XD 辅助工具

想要高效便捷的使用 Adobe XD&#xff0c; Adobe XD 插件是必不可少的&#xff0c; Adobe XD 的插件非常多&#xff0c;但 90%都是英文&#xff0c;并且良莠不齐。在这儿挑选 9 个好用的 Adobe XD 插件给大家&#xff0c;这里是我整理的一些实用 Adobe XD 插件&#xff0c;让你…

3-2 梯度与反向传播

3-2 梯度与反向传播 主目录点这里 梯度的含义 可以看到红色区域的变化率较大&#xff0c;梯度较大&#xff1b;绿色区域的变化率较小&#xff0c;梯度较小。 在二维情况下&#xff0c;梯度向量的方向指向函数增长最快的方向&#xff0c;而其大小表示增长的速率。 梯度的计算 …

UE5 07-给物体添加一个拖尾粒子

添加一个(旧版粒子系统)cascade粒子系统组件 ,在模板中选择一个开发学习初始包里的粒子

Elasticsearch:Ingest architectures - 摄取架构

我们提供各种采集架构&#xff0c;以满足各种用例和网络配置的需求。 要将数据采集到 Elasticsearch&#xff0c;请使用最符合你的需求和用例的选项。对于许多用户和用例来说&#xff0c;最简单的方法是使用 Elastic Agent 采集数据并将其发送到 Elasticsearch。Elastic Agent…

Mybatis框架的集成使用

1_框架概述 框架是一个半成品&#xff0c;已经对基础的代码进行了封装并提供相应的API&#xff0c;开发者在使用框架时直接调用封装好的api可以省去很多代码编写&#xff0c;从而提高工作效率和开发速度,框架是一种经过校验、具有一定功能的半成品软件. 经过校验&#xff1a;指…

零基础学习MySQL---库的相关操作

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、创建数据库 1.语法 CREATE DATABASE [IF NOT EXISTS] db_name [create_specification [, create_specification] .…

使用selenium定位input标签下的下拉框

先来看一下页面效果&#xff1a;是一个可输入的下拉列表 再来看一下下拉框的实现方式&#xff1a; 是用<ul>和<li>方式来实现的下拉框&#xff0c;不是select类型的&#xff0c;所以不能用传统的select定位方法。 在着手定位元素前一定一定要先弄清楚下拉列表…

CocoaPodsCmake

https://juejin.cn/post/7257048145233838141?searchId20240531171431E5868B41DC7B7016CCBA https://guides.cocoapods.org CocoaPods CocoaPods的作用 帮助程序员通过命令管理第三方库及更新&#xff0c;以达到扩展项目的目的。 CocoaPods的使用 在已有的工程目录下新增…

JAVA:文件防重设计指南

1、简述 在现代应用程序中&#xff0c;处理文件上传是一个常见的需求。为了保证文件存储的高效性和一致性&#xff0c;避免重复存储相同的文件是一个重要的优化点。本文将介绍一种基于哈希值的文件防重设计&#xff0c;并详细列出实现步骤。 2、设计原理 文件防重的基本思路…

智能家居安防系统教学解决方案

前言 随着科技的不断进步和智能家居概念的深入人心&#xff0c;智能家居安防系统作为智能家居领域的重要组成部分&#xff0c;其重要性日益凸显。智能家居安防系统不仅能够提供环境和人员的监测功能&#xff0c;还能够采取措施降低或避免人员伤亡及财产损失。因此&#xff0c;…

leetcode216.组合总和III、40.组合总和II、39.组合总和

216.组合总和III 找出所有相加之和为 n 的 k 个数的组合&#xff0c;且满足下列条件&#xff1a; 只使用数字1到9 每个数字 最多使用一次 返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次&#xff0c;组合可以以任何顺序返回。 示例 1: 输入: k 3, n 7 输出…

百日筑基第十一天-看看SpringBoot

百日筑基第十一天-看看SpringBoot 创建项目 Spring 官方提供了 Spring Initializr 的方式来创建 Spring Boot 项目。网址如下&#xff1a; https://start.spring.io/ 打开后的界面如下&#xff1a; 可以将 Spring Initializr 看作是 Spring Boot 项目的初始化向导&#xff…

实训学习错误总结2

1、 "timestamp": "2024-07-04T08:43:07.15400:00", "status": 405, "error": "Method Not Allowed", "path": "/wuzi/insert" 简单的来说就是使用的方法与注释不匹配。 规定的是&#xff1a;Get&a…

第20章 Mac+VSCode配置C++环境

1. 下载VSCode VSCode下载地址在mac终端里输入xcode- select --install命令,根据提示安装xcode工具。2. 安装插件(4个) 打开VScode,点击应用右侧菜单栏 C/C++(必装) Code Runner(必装) CodeLLDB(代码调试),不安装这个插件程序调试时,无法在vscode自带的终端里输入参…

redis学习(002 安装redis和客户端)

黑马程序员Redis入门到实战教程&#xff0c;深度透析redis底层原理redis分布式锁企业解决方案黑马点评实战项目 总时长 42:48:00 共175P 此文章包含第5p-第p7的内容 文章目录 安装redis启动启动方式1&#xff1a;可执行文件启动启动方式2 基于配置文件启动修改redis配置文件 …

第四十七章 解决 IRIS 中的 SOAP 问题 - Web 网关中的 HTTP 跟踪

文章目录 第四十七章 解决 IRIS 中的 SOAP 问题 - Web 网关中的 HTTP 跟踪Web 网关中的 HTTP 跟踪第三方追踪工具 第四十七章 解决 IRIS 中的 SOAP 问题 - Web 网关中的 HTTP 跟踪 Web 网关中的 HTTP 跟踪 Web 网关管理页面可让跟踪 HTTP 请求和响应。请参阅使用 HTTP 跟踪工…

项目管理所需资料【资料分享】

项目管理基础知识 项目管理可分为五大过程组&#xff08;启动、规划执行、监控、收尾&#xff09;十大知识领域&#xff0c;其中包含49个子过程 项目十大知识领域分为&#xff1a;项目整合管理、项目范围管理、项目进度管理、项目成本管理、项目质量管理、项目资源管理、项目…

【BUUCTF-PWN】11-ciscn_2019_c_1

64位&#xff0c;开启了NX保护 执行效果如下&#xff1a; main函数 encrypt()函数 gets()函数存在栈溢出&#xff0c;但是中间部分代码会对传入的字符串做加密处理 中间的部分是对字符串进行处理&#xff0c;strlen的作用是得知字符串的长度&#xff0c;但是遇到’\0‘就…