【数据结构】搜索二叉树(C++实现)

news2024/11/28 16:45:52

目录

一、二叉搜索树的概念

二、二叉搜索树的实现

2.1 节点的定义及构造

2.2 树的结构及功能展示

2.3 树的 Insert

2.4 树的中序遍历

2.4 树的 Find

2.5 树的 Erase

2.6 拷贝构造、赋值运算符重载、析构函数

三、递归实现树的增删查 

3.1 递归实现 FindR

3.2 递归实现 InsertR

3.3 递归实现 EraseR

四、二叉树搜索树的应用

4.1 key 模型

4.2 key-value 模型

五、二叉树搜索树的性能分析

六、二叉搜索树(key、key-value)代码


一、二叉搜索树的概念

二叉搜索树又称二叉排序树,它或者是一棵空树 ,或者是具有以下性质的二叉树:
  • 若它的左子树不为空,则左子树上所有的节点的值都小于根节点的值。
  • 若它的右子树不为空,则右子树上所有的节点的值都大于根节点的值。
  • 它的左右子树也分别为二叉搜索树。

二、二叉搜索树的实现

2.1 节点的定义及构造

创建树之前必然要先定义好节点,跟之前普通链式二叉树没有什么区别。

注意:

①我们会不断的通过 key 创建树节点, new 出的对象会调用带参的构造函数。所以,我们定义好节点中的成员变量后还要书写好构造函数。

②因为树节点会频繁访问成员变量,所以我们要将其置为公有成员(public),如果觉得麻烦,可以直接使用 struct 定义节点。

template <class K>
class BSTreeNode
{	
public:
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
	K _key;

	//用 key 构造一个树节点
	BSTreeNode(const K& key)
		:_left(nullptr)
		,_right(nullptr)
		,_key(key)
	{}
};

2.2 树的结构及功能展示

接下来看看 BSTree 类中的我们要实现的成员函数及变量定义。

template <class K>
	class BSTree
	{
		typedef BSTreeNode<K> Node;

	private:
		Node* _root = nullptr;

	public:
        //默认成员函数
        BSTree ();
        BSTree (const K& key);
        BSTree& operator=(BSTree Tree);
        ~BSTree();
        
		bool Insert(const K& key);
		void Inorder();
		bool find(const K& key);
		bool Erase(const K& key);
        
		//递归实现   
		bool FindR(const K& key);
		bool InsertR(const K& key);
		bool EraseR(const K& key);
    }

2.3 树的 Insert

首先,我们实现树的插入。我们要明确,这个插入要符合二叉搜索树的特性,即左子树的值小于根节点的值,右节点的值都大于根节点的值。

共分为以下几种情况和步骤:

  1. 传入空树直接 new 一个节点,将其置为 root 。
  2. 找到 key 值该在的位置,如果 key 大于 当前节点的 _key,则往右走,小于则往左走。
  3. 如果 key 等于当前节点的 _key,直接返回 false。
  4. 直到 cur 走到空,此时 cur 指向的便是 key 应当存放的地方。
  5. 创建一个节点并链接到树中(链接到 parent 节点的左或右)
	bool Insert(const K& key)
		{
			//如果当前树为空
			if (_root == nullptr)
			{
				_root = new Node(key);
				return true;
			}
			Node* parent = nullptr;
			Node* cur = _root;
			//直到 cur 指向 nullptr
			while (cur)
			{
				//cur->_key 小于 key 走右子树
				if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				//cur->_key 小于  走左子树
				else if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				//cur->_key == key 不允许插入
				else
				{
					return false;
				}
			}
			//此时cur处于正确的位置。
			cur = new Node(key);
			//判断 key 应该在 parent 的左边还是右边
			if (parent->_key > key)
				parent->_left = cur;
			else
				parent->_right = cur;
			return true;
		}

2.4 树的中序遍历

插入完成后,接下来我们就测试一下我们的代码。

因为搜索树的规律为,左子树<根节点<右子树。所以说我们只要先打印左子树,在打印根节点,最后打印右子树,就可以按顺序输出树中存放的 key 值。

而这个顺序正好对应我们二叉树中的中序遍历。接下来我们就来实现一个中序遍历吧。

类中的递归函数并不容易被调用。如果我们直接使用 root 作为函数的 Insert 的参数,就不得不将_root 变为 公有成员

代码检测:

2.4 树的 Find

find 和 Insert 核心代码完全相同。

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

效果检测:

2.5 树的 Erase

Erase 删除分为以下三种情况:

  1. 删除节点为叶子节点
  2. 删除节点有一个子节点
  3. 删除节点有两个子节点

情况一和情况二非常好解决,其本质都属于左或右节点为空,当该节点只有一个孩子或无孩子时,直接让 parent 指向该节点子节点,然后将此节点移除出树。 

我们先来解决前两种情况。其中有这几个点需要我们注意:

1.删除的是根节点

如果parent指向的是nullptr,则直接让_root后移动即可。

2. 链接时,应该链接到父亲的左还是右。

如果parent的左边是待删节点,即parent->left==cur,则将cur的右边链接到parent的左边

如果parent的右边是待删节点,即parent->right==cur,将cur的右边链接到parent的右边

代码如下(情况1、2的解决方法):

bool _Erase(const K& key)
{
	Node* parent = nullptr;
	Node* cur = _root;
	//找key
	while (cur)
	{
		if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		//找到存放 key的节点
		else
		{
			//key的左子树为空 所以父节点链接右子树
			if (cur->_left == nullptr)
			{
				//如果删除的是根节点,此时父节点指向为nullptr
				if (parent == nullptr)
				{
					//直接让_root指向下一个节点
					_root = _root->_right;
				}
				else
				{
					//判断应该链接到父节点的左还是右
					if (cur == parent->_left)
						parent->_left = cur->_right;
					else
						parent->_right = cur->_right;
				}
				delete cur;
			}
			//key的右子树为空 所以父节点链接左子树
			else if (cur->_right == nullptr)
			{
				//删除的为根节点的情况
				if (parent == nullptr)
				{
					_root = _root->_left;
				}
				else
				{
					//判断应当链接到父节点的左还是右
					if (cur == parent->_left)
					{
						parent->_left = cur->_left;
					}
					else
					{
						parent->_right = cur->_left;
					}
				}
				delete cur;
			}
			//删除节点左、右都不为空
			else
			{
				
			}
			return true;
		}
	}
	return false;
}

情况三采用的是替换法删除,我们观察下图,删除 3 的情况。 

为什么会这样呢?我们结合搜索树的性质和中序遍历,中序遍历中打印根节点的上一个节点是左子树的最右节点,根节点的下一个节点是右子树的最左节点,这两个节点的值于根节点的值最相近,所以,这两个节点是替换根节点的最好节点,替换后能不破坏搜索树的结构。 

我们把 3 看作根为一棵搜索树

  • 左子树的最大节点——左子树的最右节点,即 2
  • 右子树的最小节点——右子树的最左节点,即 4

所以这里我们找右子树的最小节点进行替换。

此时我们开始编写代码,不考虑一些特殊情况。

//删除节点左、右都不为空
	else
    {
		Node* min = cur->_right;
		while (cur->_right == nullptr)
		{
			min = min->_left;
		}
		swap(cur->_key, cur->_key);
		delete cur;
	}

 好的,接下来我们分析上面的代码会造成什么问题。

1. 此时 6 节点的_left 仍然指向原来的 4 节点,出现野指针的问题。并且如果 4 节点还有右子树呢?如图:

2. 无法删除根节点(8)

解决问题1,我们的方法是仍要记录下min的父节点,让父节点指向 min->right,此时无论min->right有子树还是min->right==nullptr,都可以很好的解决该问题,代码如下:

解决问题2:

删除8节点,此时 min 指向了cur->right,min ->left 为空,没有进入循环,导致minparent 为空指针,指向 minparent->_left = min->right;出现非法访问。

所以我们要将minparent初始化为cur。如果删除8节点,min节点往下找右子树的最左节点,再让 parent 指向右子树的最左节点的右子树,此时就会破坏树的结构,如图:

 所以,我们还是要判断,如果 min 在 minparent 的左子树,就改变minparent的左子树;如果 min在minparent的右子树,就改变minparent的右子树。

 

 好的,这两个棘手的问题我们就顺利解决了,我们来看看整体的 Erase 代码。

//删除
bool _Erase(const K& key)
{
	Node* parent = nullptr;
	Node* cur = _root;
	//找key
	while (cur)
	{
		if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		//找到存放 key的节点
		else
		{
			//key的左子树为空 所以父节点链接右子树
			if (cur->_left == nullptr)
			{
				//如果删除的是根节点,此时父节点指向为nullptr
				if (parent == nullptr)
				{
					//直接让_root指向下一个节点
					_root = _root->_right;
				}
				else
				{
					//判断应该链接到父节点的左还是右
					if (cur == parent->_left)
						parent->_left = cur->_right;
					else
						parent->_right = cur->_right;
				}
				delete cur;
			}
			//key的右子树为空 所以父节点链接左子树
			else if (cur->_right == nullptr)
			{
				//删除的为根节点的情况
				if (parent == nullptr)
				{
					_root = _root->_left;
				}
				else
				{
					//判断应当链接到父节点的左还是右
					if (cur == parent->_left)
					{
						parent->_left = cur->_left;
					}
					else
					{
						parent->_right = cur->_left;
					}
				}
				delete cur;
			}
			//删除节点左、右都不为空
			else
			{
				//指向cur,防止非法访问
				Node* minparent = cur;
				Node* min = cur->_right;
				while (min->_left != nullptr)
				{
					minparent = min;
					min = min->_left;
				}
				swap(cur->_key, min->_key);
				//解决野指针或min->right有子树的情况
				//minparent->_left = min->_right;
				//判断min在minparent的左或右
				if (minparent->_left == min)
					minparent->_left = min->_right;
				else
					minparent->_right = min->_right;
				delete min;
			}
			return true;
		}
	}
	return false;
}

好的,我们来测试一下代码。

2.6 拷贝构造、赋值运算符重载、析构函数

析构函数

与普通的二叉树Destory代码几乎一样。

构造与拷贝构造函数函数

拷贝构造函数就是根据前序构造出一棵树,如下:

注意,如果写了默认拷贝构造函数编译器就不会默认生成构造函数了,所以这里我们也要提供一个默认构造函数或者强制编译器为我们默认生成一个构造函数。如下:

 

赋值运算符重载

因为我们已经实现了拷贝构造函数,所以我们可以套用拷贝构造函数来实现赋值运算符重载。

 测试:

三、递归实现树的增删查 

3.1 递归实现 FindR

实现递归版本的 Find ,总共分4步:

  1. 如果 root 指向为 nullptr ,说明未找到 key 值
  2. 如果 key 大于 root->key,说明 key 在 root 的右边,使用root->right继续递归。
  3. 如果 key 小于 root->key,说明 key 在 root 的左边,使用root->left继续递归。
  4. 最后就是 key==root->key 的情况,返回 true 。

代码如下:

//Find的递归版本
bool FindR(const K& key)
{
	return _FindR(_root, key);
}

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

3.2 递归实现 InsertR

在实现了FindR之后,实现出InsertR应该不难,代码大致如下:


bool InsertR(const K& key)
{
	return _InsertR(_root, key)
}

bool _InsertR(const 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
	{
		//已存在 key 不允许插入
		return false;
	}
}

可是,发现一个问题,这样的写法是无法修改外部实参的,即无法链入搜索树中,所以我们要采用引用传参或二级指针传参,这样才能实质修改外部的变量。

root == nullptr 就将 key 链入树中,此时 root 为最后一个节点左或右子树的别名

 

3.3 递归实现 EraseR

递归删除的主题逻辑与上面大致相同。

步骤如下:

1.root == nullptr,则返回false,即未找到 key,删除失败

2.如果root->_key 小于 key,递归走右子树,

3.如果root->_key 大于 key,递归走左子树

4.最后就是 root->key == key,则开始删除节点。

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);
	}
	//找到key,删除
	else
	{
	}
}

当已经找到key值,进行删除时

1.如果左子树为空,则让root指向其右子树。如图:

 2.如果右子树为空,则让root指向左子树,如图:

3.当左子树、右子树都不为空时,采用替换法删除,交换 key 值,然后删除被替换的节点。

 交换过后,我们要删除 key 节点此时要使用root->right再次调用 _EraseR,如果直接使用  _EraseR(key),则会删除失败,因为树的结构已经被破坏。

代码如下:

bool EraseR(const K& key)
{
	return _EraseR(root, key);
}
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);
	}
	//找到key,删除
	else
	{
        //情况1、2要记录待删除的节点。
		Node* del = root;
		if (root->_left == nullptr)
		{
			root = root->_right;
		}
		else if (root->_right == nullptr)
		{
			root = root->_left;
		}
		//左右都有孩子
		else
		{
			Node* min = root->_right;
			while (min->_left)
			{
				min = min->_left;
			}
			swap(root->_key, min->_key);
			//这里不能直接调用erase,交换后,树的结构已经破坏,显示找不到key值
			//return EraseR(key); 
			return _EraseR(root->_right, key);
		}
		delete del;
		return true;
	}
}

四、二叉树搜索树的应用

1.K模型:K模型即只有 key 作为关键码,结构中只需要存储 key 即可,关键码即为需要搜索到的值。

比如: 给一个单词 word,判断该单词是否拼写正确,具体方法如下:

  • 以词库中所有单词集合中的每个单词作为key,构造一棵二叉搜索树
  • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

2.KV模型:每一个关键码 key ,都有与之对应的值 value,即<key,value>的键值对。该种方法式在现实生活中非常常见:

  • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word,chinese>就构成一种键值对;
  • 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现的次数就是<world,count>,就够成一种键值对。

4.1 key 模型

现在我们将二叉搜索树的K模型套入实际案例中,顺便练习编程能力。

将要拼写的单词插入到二叉搜索树中,用户开始拼写单词,如果用户输入的单词在词库中并拼写正确,则输出"拼写正确",否则输出"拼写错误"。代码如下:

4.2 key-value 模型

现在我们将二叉搜索树的 key-value 模型套入实际案例中,顺便练习编程能力。

我们现在创建一个字典,用户输入英文,程序打印出中文。

首先我们要创建 key-value 模型,然后将值插入,通过查找key,然后输出其value值。注意,如果是k-value模型,find的返回值就应为节点的指针。

我们看看代码是如何书写的,并且尝试运行一下:

 通过key-value模型我们还可以实现统计次数的程序。

例如我们往树中插入字符串,如果该字符串已存在,则++该字符串的计数。最后使用 Inorder 打印树中的元素,注意,要将Inorder中的输出语句带上value进行输出噢~

代码及结果如下:

五、二叉树搜索树的性能分析

在实现完功能之后,我们来对二叉搜索树进行性能分析。

问:搜索二叉树增删查的时间复杂度为多少?

答:最坏的情况下为 O(h)  —— h为高度。

为什么不是lgN,而是O(h) ,我们来看看如果树的形状是以下这几种情况的呢?

对有 n 个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度函数,即结点越深,比较次数越多。

但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

最优情况下:二叉搜索树为完全二叉树(或接近完全二叉树),其平均比较次数为 lgN.

最差情况下:二叉搜索树退化为单支树(或者类似单支),其平均比较次数为 N/2. 

所以说,二叉搜索树的效率在这种情况下跟O(N)几乎没有区别。其本质是不平衡的,所以后面我们要学习AVL树和红黑树来保持平衡。这样,搜索的效率就会极高。

六、二叉搜索树(key、key-value)代码

Key模型。

//节点的定义
template <class K>
class BSTreeNode
{
public:
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
	K _key;

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

};

template <class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
	//C++11用法,作用:强制编译器生成默认的构造
	BSTree() = default;


	bool Insert(const K& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);
			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);
		if (parent->_key > key)
			parent->_left = cur;
		else
			parent->_right = cur;
		return true;
	}

	void Inorder()
	{
		_Inorder(_root);
		cout << endl;
	}

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

	bool _Erase(const K& key)
	{
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				if (cur->_left == nullptr)
				{
					if (parent == nullptr)
					{
						_root = _root->_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)
					{
						_root = _root->_left;
					}
					else
					{
						if (cur == parent->_left)
						{
							parent->_left = cur->_left;
						}
						else
						{
							parent->_right = cur->_left;
						}
					}
					delete cur;
				}
				else
				{
					Node* minparent = cur;
					Node* min = cur->_right;
					while (min->_left != nullptr)
					{
						minparent = min;
						min = min->_left;
					}
					swap(cur->_key, min->_key);
					if (minparent->_left == min)
						minparent->_left = min->_right;
					else
						minparent->_right = min->_right;
					delete min;
				}
				return true;
			}
		}
		return false;
	}
	bool FindR(const K& key)
	{
		return _FindR(_root, key);
	}

	bool InsertR(const K& key)
	{
		return _InsertR(_root, key);
	}

	bool EraseR(const K& key)
	{
		return _EraseR(_root, key);
	}
	~BSTree()
	{
		_Destory(_root);
	}

	BSTree(const BSTree<K>& t)
	{
		_root = _Copy(t._root);
	}

	BSTree<K>& operator=(BSTree<K> t)
	{
		swap(t._root, _root);
		return *this;
	}


private:
	Node* _Copy(Node* root)
	{
		if (root == nullptr)
		{
			return nullptr;
		}
		Node* copyRoot = new Node(root->_key);
		copyRoot->_left = _Copy(root->_left);
		copyRoot->_right = _Copy(root->_right);
		return copyRoot;
	}

	void _Destory(Node* root)
	{
		if (root)
		{
			_Destory(root->_left);
			_Destory(root->_right);
			delete root;
		}
	}
	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);
		}
		//找到key,删除
		else
		{
			Node* del = root;
			if (root->_left == nullptr)
			{
				root = root->_right;
			}
			else if (root->_right == nullptr)
			{
				root = root->_left;
			}
			//左右都有孩子
			else
			{
				Node* min = root->_right;
				while (min->_left)
				{
					min = min->_left;
				}
				swap(root->_key, min->_key);
				//这里不能直接调用erase,交换后,树的结构已经破坏,显示找不到key值
				return _EraseR(root->_right, key);
			}
			delete del;
			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
		{
			//已存在 key 不允许插入
			return false;
		}
	}


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

	}

	void _Inorder(Node* root)
	{
		if (root)
		{
			_Inorder(root->_left);
			cout << root->_key << " ";
			_Inorder(root->_right);
		}
	}

	Node* _root = nullptr;
};

Key-Value模型,Find、Insert函数做出相应修改,并删除了部分函数

class BSTreeNode
{
public:
	BSTreeNode<K, Value>* _left;
	BSTreeNode<K, Value>* _right;
	K _key;
	Value _value;
	BSTreeNode(const K& key, const Value& value)
		:_left(nullptr)
		, _right(nullptr)
		, _key(key)
		, _value(value)
	{}

};

template <class K, class V>
class BSTree
{
	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 (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->_left = cur;
		else
			parent->_right = cur;
		return true;
	}


	void Inorder()
	{
		_Inorder(_root);
		cout << endl;
	}

	Node* Find(const K& key)
	{
		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;
	}

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

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

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

相关文章

Vue | Vue.js Composition API(二)

&#x1f5a5;️ Vue.js专栏&#xff1a;Vue.js 初级知识 Composition API(二) &#x1f9d1;‍&#x1f4bc; 个人简介&#xff1a;一个不甘平庸的平凡人&#x1f36c; ✨ 个人主页&#xff1a;CoderHing的个人主页 &#x1f340; 格言: ☀️ 路漫漫其修远兮,吾将上下而求索☀…

嘿,朋友,其实 CSS 动画超简单的 - 时间函数篇(贝塞尔曲线、steps,看完还不懂算我输)

分配内存 - new 官方定义&#xff1a;new是一个分配内存的内置函数&#xff0c;第一个参数是类型&#xff0c;而不是值&#xff0c;返回的值是指向该类型新分配的零值的指针。 func new(Type) *Type 我们平常在使用指针的时候是需要分配内存空间的&#xff0c;未分配内存空间…

Java自定义注解

目录 一、什么是自定义注解 1&#xff09;Java注解简介 2&#xff09;Java注解分类 JDK基本注解 JDK元注解 自定义注解 如何自定义注解&#xff1f; 二、自定义注解 1&#xff09;获取类上注解值 2&#xff09;获取类属性上的注解属性值 3&#xff09;获取方法上的注…

WireShark 常用协议分析

WireShark 常用协议分析 1.3 实战&#xff1a;使用 WireShark 对常用协议抓包并分析原理 协议分析的时候 我们 关闭混淆模式&#xff0c; 避免一些干扰的数据包存在。 1.3.1 常用协议分析 - ARP 协议 地址解析协议 &#xff08;英语&#xff1a;Address Resolution Protocol&…

从内核角度看网络包发送流程

一、前置知识 1、RingBuffer结构详解 关于RingBuffer网上有很多说法&#xff0c;有的人说RingBuffer是系统启动时就预先申请好的一个环形数组&#xff0c;有的人说RingBuffer是在接收或发送数据时才动态申请的一个环形数组&#xff0c;那么到底RingBuffer的结构是怎么样的呢&…

《吉师作业》(2)之迟来的答案

前言 &#x1f340;作者简介&#xff1a;吉师散养学生&#xff0c;为挣钱努力拼搏的一名小学生。 &#x1f341;个人主页&#xff1a;吉师职业混子的博客_CSDN博客-python学习,HTML学习,清览题库--C语言程序设计第五版编程题解析领域博主 &#x1fad2;文章目的&#xff1a;我不…

初识C++(二)

简述 &#xff1a;本篇就缺省参数 和 函数重载 方面进行初步学习 &#xff0c;对比C语言学习C这两个语法&#xff0c;从而感受C在此方面对C语言进行的补充。 目录 缺省参数 什么是缺省参数 缺省参数的分类 缺省参数的应用 函数重载 什么是函数重载 函数重载的三种情况 支…

【JavaSE】函数or方法?方法的重载讲解

文章目录什么是方法如何定义方法方法的调用过程形参与实参的关系方法的重载为什么要重载重载的概念方法签名递归什么是方法 在C语言的学习中我们学习到了一个概念叫做函数&#xff0c;那么在Java的语法中有没有类似函数的东西的&#xff0c;答案是有的&#xff0c;但是在Java的…

strimzi实战之一:简介和准备

欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码)&#xff1a;https://github.com/zq2599/blog_demos 关于strimzi strimzi是一个开源项目&#xff0c;已加入了CNCF&#xff0c;官网地址&#xff1a;https://strimzi.io/借助strimzi&#xff0c;既能快速部署ka…

【生日快乐】搜索技术【深度优先搜索】 - 回溯法

搜索技术【深度优先搜索】 - 回溯法 回溯法是一种选优搜索法&#xff0c;按照选优条件深度优先搜索&#xff0c;以达到目标。当搜索到某一步时&#xff0c;发现原先的选择并不是最优或达不到目标&#xff0c;就退回一步重新选择&#xff0c;这种走不通就退回再走的技术被称为回…

如何用 Elasticsearch 实现 Word、PDF,TXT 文件的全文内容检索?

简单介绍一下需求 能支持文件的上传&#xff0c;下载 要能根据关键字&#xff0c;搜索出文件&#xff0c;要求要能搜索到文件里的文字&#xff0c;文件类型要支持 word&#xff0c;pdf&#xff0c;txt 文件上传&#xff0c;下载比较简单&#xff0c;要能检索到文件里的文字&am…

2022-ISCTF-部分MISC和PWN

misc 两层编码 第一层 sha256掩码爆破 第二层 base64解码找到key import string,sys from hashlib import sha256 from multiprocessing import Process from Crypto.Util.number import * from pwn import * import base64 from primefac import * context(log_leveldebug)…

【STL】容器 - set和map的使用

目录 前言 一.键值对 1.在SGI - STL中对键值对的定义: 2.make_pair 二.set 1.set的概念与注意事项 2.set的使用(常用接口) <1>.构造函数 <2>.迭代器与范围for <3>.插入和查找 <4>.删除erase <5>.计数count 三.map 1.map的概念与注…

洛谷千题详解 | P1012 [NOIP1998 提高组] 拼数【C++、Java语言】

博主主页&#xff1a;Yu仙笙 专栏地址&#xff1a;洛谷千题详解 目录 题目描述 输入格式 输出格式 输入输出样例 解析&#xff1a; C源码&#xff1a; C源码2&#xff1a; C源码3&#xff1a; Java源码&#xff1a; ---------------------------------------------------------…

element-ui upload图片上传组件使用

图片上传前端收集 数据 再调用接口发送到后端 组件标签内的参数&#xff1a; 参数说明类型可选值默认值action必选参数&#xff0c;上传的地址string——headers设置上传的请求头部object——multiple是否支持多选文件boolean——data上传时附带的额外参数object——name上传…

【数据结构】链表OJ第一篇 —— 移除链表元素 反转链表 合并两个有序链表

文章目录0. 前言1. 移除链表元素2. 反转链表3. 合并两个有序链表4. 结语0. 前言 上篇博客中&#xff0c;我们学习了实现了单链表。但是仅仅实现并不算掌握&#xff0c;所以我们需要做些题目来练习巩固。而从今天开始的几期&#xff0c;anduin 都会为大家带来链表OJ题&#xff…

在Linux环境下VScode中配置ROS、PCL和OpenCV开发环境记录

一.安装必要的插件 打开VScode&#xff0c;在开展中安装CMake、CMake Tools&#xff0c;ROS和catkin-tools插件&#xff0c;截图如下&#xff0c;安装后重新打开VScode插件生效。 二.创建ROS工作空间 在选择的路径下&#xff0c;打开终端创建工作空间&#xff0c;具体命令如下…

【概率论笔记】正态分布专题

文章目录一维正态分布多维正态分布n维正态分布二维正态分布一维正态分布 设X~N(μ,σ2)X\text{\large\textasciitilde}N(\mu,\sigma^2)X~N(μ,σ2)&#xff0c;则XXX的概率密度为f(x)12πσe−(x−μ)22σ2f(x)\frac{1}{\sqrt{2\pi}\sigma}e^{-\frac{(x-\mu)^2}{2\sigma^2}}f(…

WXML模板语法

文章目录1、数据绑定1.1 数据绑定的基本原则1.2在data中定义页面的数据1.3 Mustache语法的格式1.4 Mustache语法的应用场景1.5 算数运算2、事件绑定2.1 小程序常用的事件2.2事件对象的属性列表2.3 target和currentTarget的区别2.4 <font colorred>bindtap的语法格式2.5 在…

狗厂员工来面试本想难为一下,结果被虐得连console.log也不敢写了

这次说到的面试题是关于node服务端内存溢出的问题&#xff0c;狗厂员工来面试本想难为一下&#xff0c;现在我连console.log也不敢写了 关于这道node内存溢出的问题&#xff0c;大哥从以下几个方面讲的&#xff0c;讲完我觉得自己得到了升华&#xff0c;现在搞得连代码也快不敢…