C++数据结构:B树

news2024/12/25 9:19:30

目录

一. 常见的搜索结构

二. B树的概念

三. B树节点的插入和遍历

3.1 插入B树节点

3.2 B树遍历

四. B+树和B*树

4.1 B+树

4.2 B*树

五. B树索引原理

5.1 索引概述

5.2 MyISAM

5.3 InnoDB

六. 总结


一. 常见的搜索结构

表示1为在实际软件开发项目中,常用的查找结构和方法,包括顺序查找、二分查找、二叉搜索树、平衡二叉树、哈希表等,这几种查找方法和数据结构,都适合于内查找(将数据加载到内存中查找)。

搜索结构数据要求时间复杂度
顺序查找无要求O(N)
二分查找顺序排序O(logN)
二叉搜索树无要求O(N)
平衡二叉树无要求O(logN)
哈希表无要求O(1)

如果数据量极大,内存无法存放时,就需要将数据存储在磁盘当中,而CPU访问磁盘的速度要远远低于访问内存的速度,假设O(1)的时间复杂度下要执行2次访问,O(logN)的时间复杂度下要执行30次访问。如果对内存数据进行访问,因为访问内存速度相对较快,所有我们可以认为O(1)和O(logN)时间复杂度算法的性能是一致的。但是如果是对于磁盘上的数据的访问,由于磁盘数据访问的效率较低,因此O(1)和O(logN)差别会很大。

采用二叉搜索树检索磁盘数据的缺陷为:

  • 二叉搜索树查找的时间复杂度为O(logN),磁盘IO效率低,O(logN)的时间复杂度相对于O(1)会很大程度上降低性能。

但是,哈希查找的时间复杂度是O(1),为什么哈希也不适用于对磁盘数据的检索呢?这是因为哈希的有这样的缺陷:

  • 在极端情况下,哈希表中会产生大量的哈希冲突,查找的时间复杂度会接近O(N)。
  • 虽然很多时候当哈希冲突达到一定数量时,在哈希散列中会由挂单链表改为挂红黑树,但红黑树查找的时间复杂度依旧是O(logN)。

为了解决平衡二叉树和哈希表无法很好的应对内存数据查找的情况,B树被创造和出来,B树适用于对磁盘中大量的数据进行检索,当然B树也能够在内存在查找数据,但效果就不如哈希和平衡二叉树。

由于B树/B+树适用于检索磁盘中大量数据的性质,经常被用于作为数据库的底层检索结构。

二. B树的概念

B树是一种适合外查找的平衡多叉树,一颗m阶的B树,是一颗m路的二叉搜索树,一颗B树要么为空树,要么满足如下几个条件:

  1. 根节点至少有两个孩子节点。
  2. 分支节点(非叶子节点)应当有K-1个键值和K个孩子节点,其中ceil(m/2)\leqslant K\leqslant M,其中ceil为向上取整函数。
  3. 所有叶子节点都在同一层。
  4. 每个节点中的键值都是自小到大升序排序的,键值Key表示子树的阈值划分。
  5. 对于任意一个节点,孩子节点的数目总是比键值多一个。

图2.1就是一颗3阶B树,注意观察其根节点,两个键值为50和100,根节点的第一个孩子节点键值全部小于50,第二个孩子节点的键值位于(51,100)之间,第三个孩子节点键值大于100,这就是键值Key的阈值划分功能。

B树检索与二叉搜索树的检索类似,假设我们要在图2.1所示的B树中检索99,先从根节点开始找起,对比待查找的值和键值的大小,发现其位于(50,100)范围内,这样就向下遍历查找p2子树,在p2子树的键值中找到了键值99,检索完成。

图2.1 3阶B树

三. B树节点的插入和遍历

3.1 插入B树节点

由于B树的插入操作过于抽象,因此直接上实例,在示例中讲解B树节点插入的具体操作。假设依次将std::vector<int> v = { 53,139,75,49,145,36,50,47,101}插入到3阶B树中。为了方便插入操作,我们在申请B树节点空间的时候,阶数为M,就为键值申请M个空间,为孩子节点申请M+1个空间,这样做的目的是方便插入时数据挪动,以及后面的分裂操作

① 插入53

53是B树插入的第一个节点,因此直接将其插入到根节点的第一个键值位置处即可。如果3.1所示,插入53后,有一个B树根节点,这个根节点附带有两个孩子节点nullptr。这里的根节点也是叶子节点,注意B树插入新节点一定是向叶子节点插入的。

图3.1 插入B树的第一个节点53

② 插入139

139大于53,且插入后根节点(叶子结点)中键值的数目不超过M-1,因此只需将139至于53的后面,并且带入null子节点即可。

图3.2 插入第二个节点139后的B树结构

③ 插入75

75位于53和139之间,所以第一步要现将75插入到root节点的这两个值之间。但是,插入75后root节点就有了3个键值,这样就不符合B树的结构要求,需要进行分裂。

分裂操作的步骤为:

  1. 取中间位置mid = M/2下标处为分界线,创建一个兄弟节点brother,将下标位于[mid+1,M)的键值及其左右孩子都拷贝到brother节点中去(设下标为键值相同的孩子节点为左孩子,比键值下标大1的孩子节点称为右孩子)。
  2. 将mid处的键值交给其父亲节点,如果没有父亲节点节创建父亲节点,父亲节点的其中两个孩子节点就包含原先发生分裂的节点以及分裂出的节点brother。
图3.3 插入第三个节点75后的结构及B树的分裂操作

④ 插入49

首先检索节点插入的位置,发现49小于根节点root的第一个键值,因此找到n1节点,n1节点为叶子节点可以执行插入操作,49小于第一个节点53,所以应当将53向后移动一位并将49设置为n1节点的第一个键值。

图3.4 插入B树第四个节点49

 ⑤ 插入145

首先检索插入数据的叶子节点,根节点只有一个键值75,145大于75,向n2节点查找,n2为叶子节点可以执行插入操作,将145插入到139后面,插入过后键值个数少于阶数M,不用分裂。

图3.5 插入B树第五个节点49

⑥ 插入36

查找36的插入位置应该为图3.5中的n1节点,将35插入n1后n1有3个键值需要进行分裂,兄弟节点取走53,49向上交给n1的父亲节点,分裂出的兄弟节点要作为root节点的一个孩子节点。

图3.6 插入新节点36及分裂操作

⑦ 插入50 

直接找到n2节点,插入到键值53之前即可。

图3.7 插入新节点53

⑧ 插入47

 插入到n1节点46的后面即可。

图3.8 插入新节点47

⑨ 插入101

先初步执行插入操作,即101插入到n3节点的第一个键值位置处,插入后n3节点的键值数量达到了阶数M,要执行分裂操作。然而分裂后将mid处键值交给父亲节点(root)管理后,root的键值数量也达到了阶数M,需要进一步分裂,更新root。

图3.9 插入新节点101

3.2 B树遍历

B树是一种特殊的搜索树,如果按照中选遍历,那么理应得到升序的一组数据,B树遍历的方法与普通的二叉搜索树中序遍历并没有本质区别,区别在于M路遍历和双路遍历。图3.10为二叉搜索树的遍历流程图,遍历得到升序排序结果。

图3.10 B树前序遍历的递归路径图

代码3.1:插入B树节点和前序遍历B树

#include <iostream>

// B树节点,K为索引数据类型,M为最大阶数
template<class K, size_t M>
struct BTreeNode
{
	// 存储键值和孩子节点的一维数组
	// K _key[M - 1];
	// BTreeNode<K, M> _sub[M];

	// 为了方便后续的分裂和插入操作,多开辟一个空间
	K _key[M];
	BTreeNode<K, M>* _sub[M + 1];
	BTreeNode<K, M>* _parent;   // 父亲节点
	size_t _n;   // 键值个数

	// 构造函数
	BTreeNode()
		: _parent(nullptr)
		, _n(0)
	{
		// 键值全部清零,孩子节点全部为空
		for (size_t i = 0; i < M; ++i)
		{
			_key[i] = K();
			_sub[i] = nullptr;
		}
		_sub[M] = nullptr;
	}
};

template<class K, size_t M>
class BTree
{
	typedef BTreeNode<K, M> Node;   // B树节点类型重定义
public:
	// 插入位置查找函数
	std::pair<Node*, int> Find(const K& key)
	{
		Node* parent = nullptr;
		Node* cur = _root;

		while (cur)
		{
			// 在本层中,查找大于key的键值,如果找到这样的键值或者走到了最后一个键值
			// 那么就到下一层去查找,如果找到与key相同的键值,那么就直接返回该位置对应pair
			size_t i = 0;
			while (i < cur->_n)
			{
				// 键值按照升序排序,逐个向后查找即可
				if (key > cur->_key[i]) 
				{
					++i;
				}
				else if (key < cur->_key[i])
				{
					break;
				}
				else   // 存在相等就直接返回
				{
					return std::make_pair(cur, i);
				}
			}

			parent = cur;
			cur = cur->_sub[i];
		}

		return std::make_pair(parent, -1);
	}

	// 在一个B树节点值插入新键值的函数
	void InsertKey(Node* parent, const K& key, Node* child)
	{
		int end = parent->_n - 1;
		while (end >= 0)
		{
			if (parent->_key[end] > key)
			{
				// 将大于key的键值及其对应的右孩子节点全部向后移动一位
				parent->_key[end + 1] = parent->_key[end];
				parent->_sub[end + 2] = parent->_sub[end + 1];
				--end;
			}
			else
			{
				break;
			}
		}

		// 将新的key值插入到end+1位置处,并引入右孩子节点
		parent->_key[end + 1] = key;
		parent->_sub[end + 2] = child;
		++parent->_n;
	}

	// 新节点(键值)插入函数
	bool Insert(const K& key)
	{
		// 特殊情况:当前B树根节点为空,插入的是第一个节点
		if (_root == nullptr)
		{
			_root = new Node;
			_root->_key[0] = key;
			_root->_n++;
			return true;
		}

		// 查找要插入节点的位置
		std::pair<Node*, int> ret = Find(key);

		// 如果对应ret.second>=0,那说明key已经在B树中存在
		// B树不允许冗余,因此直接返回false
		if (ret.second >= 0)
		{
			return false;
		}
		
		Node* parent = ret.first;
		Node* child = nullptr;
		K newKey = key;

		// 向上插入,满足条件就分裂
		while (true)
		{
			InsertKey(parent, newKey, child);

			if (parent->_n <= M - 1)
			{
				return true;
			}
			else   // 需要进行分裂操作
			{
				size_t mid = M / 2;   // 中间节点

				// 将中间mid之后的键值全部交给新创建的brother节点
				Node* brother = new Node;

				size_t j = 0;
				for (size_t i = mid + 1; i < M; ++i)
				{
					// 将key及其左孩子交给brother节点
					brother->_key[j] = parent->_key[i];
					brother->_sub[j] = parent->_sub[i];

					// 如果左孩子节点不为空,那么就要跟新其父亲为brother
					if (parent->_sub[i] != nullptr)
					{
						parent->_sub[i]->_parent = brother;
					}

					// 将parent节点中被挪走的key和sub清空
					parent->_key[i] = K();
					parent->_sub[i] = nullptr;

					++j;
				}

				// 将最后一个右孩子节点插入到brother节点中
				brother->_sub[j] = parent->_sub[M];
				if (parent->_sub[M] != nullptr)
				{
					parent->_sub[M]->_parent = brother;
				}
				parent->_sub[M] = nullptr;

				// 更新键值个数,这里parent键值个数减去brother->_n + 1,+1是因为要把mid子节点交给父节点
				brother->_n = j;
				parent->_n -= (brother->_n + 1);

				K midKey = parent->_key[mid];
				parent->_key[mid] = K();

				if (parent == _root)
				{
					_root = new Node;
					_root->_key[0] = midKey;
					_root->_sub[0] = parent;
					_root->_sub[1] = brother;

					parent->_parent = _root;
					brother->_parent = _root;
					_root->_n++;

					return true;
				}
				else
				{
					newKey = midKey;
					parent = parent->_parent;
					brother->_parent = parent;
					child = brother;
				}
			}
		}

		return true;
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		// 依次遍历每个节点的左孩子
		for (size_t i = 0; i < root->_n; ++i)
		{
			_InOrder(root->_sub[i]);
			std::cout << root->_key[i] << " ";
		}

		// 遍历最后一个右孩子节点
		_InOrder(root->_sub[root->_n]);
	}

	// 中序函数
	void InOrder()
	{
		// 子函数
		_InOrder(_root);
	}

private:
	Node* _root = nullptr;
};

四. B+树和B*树

4.1 B+树

B+树是在B树上优化了的多路平衡搜索二叉树,相比于B树,B+树进行了以下几点优化:

  1. 每个节点的键值数量和孩子节点数量相同。
  2. 孩子节点指针p[i]指向的子B树键值范围位于 [ k[i], k[i+1] ) 之间。
  3. 所有存储了有效键值的节点都在叶子节点上。
  4. 所有叶子节点都被连接了起来。
图4.1 B+树的结构

B+树的节点插入,与B树类似,同样由于其过于抽象,本文以具体的实例来展示B+树节点插入的过程,假设要将std::vector<int> v = { 53,139,75,49,145,36,101 }插入到阶数M=3的B+树中去,插入的流程及操作如下:

① 插入53

插入的第一个节点,首先创建两层B+树节点,一层为根节点,一层为叶子节点,在根节点和叶子节点的第一个键值位置处,插入元素53。和B树一样,如果阶数为M,就为键值和孩子节点都多开辟一个空间,以方便数据挪到和节点分裂。

图4.2 向B+树中插入第一个数据

② 插入139

首先检索插入位置,发现139大于根节点中唯一一个键值53,因此向下遍历找到n1,将新数据插入到53后面,节点中键值个数尚未达到阶数,不需要分裂。

图4.3 向B+树中插入139

③ 插入75

检索到75应该插入子节点n1中,139向后挪一个单位,75插入到53和139之间,以保证键值升序。

图4.4 向B+树中插入75

④ 插入49

首先查找可以插入49的叶子节点,检索到插入位置为第一个键值位置,因此要更新其父亲节点中对应位置的索引值,这样root的第一个键值就由53变为了49。同时,由于n1中的键值个数已经超过了阶数M,所以要对这个节点执行分裂操作。

图4.5 插入数据49及B+树的分裂

B+树节点分裂操作:

  • 创建兄弟节点brother,将分裂节点中后半部分键值挪动到brother中。
  • 并将brother中首个键值插入到父亲节点中,将brother节点设为父节点的孩子节点。

⑤ 插入145

直接将145插入到节点n2中去,因为插入后键值数量未超过B树的阶数,不需要分裂。

图4.6 向B+树中插入145

⑥ 插入36

将36插入到n1的首个位置处,然后更新器父亲节点对应的键值。(B+树中向叶子节点的首个关键字位置插入数据,一定会更新父亲节点的索引

图4.7 向B+树中插入36

⑦ 插入101

将101插入到n2的键值75和39之间,然后n2分裂。

图4.8 向B+树中插入101

4.2 B*树

相比于B+树,B*树要求每个分支节点的键值利用率达到\frac{2}{3}M,并且每一层节点又要存储指向其兄弟节点的指针,B*树相对于B+树,最大的优化就是节省了空间,能减少空间浪费。

图4.9 B*树的结构

五. B树索引原理

5.1 索引概述

索引,就是通过某些关键信息,让用户可以快速找到某些事物,例如通过目录,我们就可以快速检索到一本书中特定的内容所在的页码。B/B+最普遍的用途,就是做索引

MySQL数据库官方给出的索引定义是:索引(index)是帮助MySQL高效获取数据的数据结构。

当数据量很大的时候,为了方便数据的管理、提高检索效率,通常会将数据保存至数据库。数据库不仅仅要存储数据,还要维护特定的数据结构和一些高效的搜索算法,以帮助用户快速引用到某些数据。这种实现快速查找的数据结构,就是索引。

MySQL是非常流行的开源关系型数据库,不仅免费,而且搜索效率较高,可靠性高,拥有灵活的插件式存储引擎,在MySQL中,索引是属于存储引擎范畴的概念,不同的存储引擎对索引的实现方式是不同的。索引是基于表的而不是基于数据库的。

5.2 MyISAM

在早期的MySQL数据库中,所使用的搜索引擎都是MyISAM,这种搜索引擎不支持事务,支持全文索引,其使用的数据结构是B+树。在MyISAM搜索引擎中,叶子节点中的data域存储的是数据在磁盘中的地址,而不是数据本身。如图5.1所示的学生信息管理数据库,要记录学生的学号(StuId)、年龄(age)以及姓名,B+树用于检索,图5.1中选取的主键为StuId。

在绝大部分数据库中,一般要求加入到数据库中的数据要有一个主键,并且主键是不允许出现重复的。就以图5.1所示的学生信息管理系统为例,选取学号能保证每个学生之间的学号不重复,而姓名和年龄则不可避免的出现重复,那么就应当选取学号作为主键。如果没有一个合适的参数作为主键,那么可以采用自增主键,自增主键实际就是一个常数,第一次插入的数据常数1为主键,第二次插入的数据常数2为主键,以此类推。

图5.1 MyISAM主键索引

以图如果用户通过主键索引查找数据库中的相关信息,那么就会对B树进行检索,直到检索到叶子节点发现匹配项或者确认数据库中没有对应主键即可。如果使用非主键(未建立辅助索引)的参数进行检索,那么进行的操作是全表扫描查找匹配项

对于MySQL数据库,我们处理使用主键建立主索引之外,还可以建立辅助索引,主索引不允许出现重复项,而辅助索引允许出现重复项,如图5.2所示,就是通过学生年龄age建立的学生数据库的辅助索引。

图5.2 已age为主键的MyISAM辅助索引

5.3 InnoDB

现在高版本的MySQL数据库,全部采用InnoDB为搜索引擎,InnoDB是面向在线事务处理的应用,支持B+树索引、哈希索引、全文索引等。但是,InnoDB使用B+树支持索引的实现方式与MyISAM却有着很大的不同。

InnoDB文件本身就是索引文件的一部分。在InnoDB的中,B+树的叶子节点要存放表的全部数据,数据库中的数据,要按照主键从小到大的顺序排列起来。如图5.3所示,InnoDB的叶子节点中要包含所有的数据记录,这种索引叫做聚集索引。由于InnoDB数据文件本身要按照主键来聚集,因此InnoDB必须有主键,而MyISAM则可以没有主键

图5.3 InnoDB主键索引

InnoDB建立B+树辅助索引,叶子节点的数据域中记录的并不是数据数据文件本身的内容,而是对应的主键,如图5.4所示,在InnoDB索引方式下,建立对于name的辅助索引,叶子结点数据域就存储了对应的StdId(学号),使用辅助索引检索时,先拿到对应的主键,再通过主索引查找内容,这样就相当于要检索两次

图5.4 InnoDB辅助索引

六. 总结

  • 常见的搜索结构有哈希、二分、顺序查找、平衡二叉树等,这些数据结构和算法都只适用于内查找。
  • 对于海量数据,内存中无法容纳,应当使用B树/B+树来进行检索,B/B+树是高效的外查找专用数据结构。
  • MySQL数据库的检索主要是通过B+树来进行的,有MyISAM和InnoDB两种检索方式,MyISAM的B+树的叶子节点的数据域中存储的是数据文件在磁盘中的地址,InnoDB的B+树的叶子节点中数据域存放的是数据文件本身。
  • B+树做外查找时,B+树本身存储在磁盘中。

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

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

相关文章

链表【2】

文章目录 &#x1f95d;24. 两两交换链表中的节点&#x1f951;题目&#x1f33d;算法原理&#x1f96c;代码实现 &#x1f34e;143. 重排链表&#x1f352;题目&#x1f345;算法原理&#x1f353;代码实现 &#x1f95d;24. 两两交换链表中的节点 &#x1f951;题目 题目链接…

【超详细】vue项目:Tinymce富文本使用教程以及踩坑总结+功能扩展

【【超详细】vue项目&#xff1a;Tinymce富文本使用教程以及踩坑总结功能扩展 引言&#xff1a;一、 开始二、快速开始1、安装Tinymce 三、封装成Vue组件1、文件结构2、index.vue3、dynamicLoadScript.js4、plugin.js5、toolbar.js 四、使用Tinymce组件五、业务逻辑实现1、添加…

vue中的this.$nextTick().then()

MENU 示例一示例二sortsplicepushrandomfloorMathwhile演示 示例一 let reorganize function (arr){let rest [];while (arr.length > 0) {let random Math.floor(Math.random() * arr.length);// 把获取到的值放到新定义的数组中rest.push(arr[random]);// 这句代码的作…

Leetcode每日一题学习训练——Python3版(从二叉搜索树到更大和树)

版本说明 当前版本号[20231204]。 版本修改说明20231204初版 目录 文章目录 版本说明目录从二叉搜索树到更大和树理解题目代码思路参考代码 原题可以点击此 1038. 从二叉搜索树到更大和树 前去练习。 从二叉搜索树到更大和树 给定一个二叉搜索树 root (BST)&#xff0c;请…

网络安全卫士:上海迅软DSE的员工上网管理策略大揭秘!

在日常办公中&#xff0c;企业员工可能会在互联网上有意或无意的将一些包含内部重要信息的内容发布出去&#xff0c;从而造成不必要的违规及泄密风险&#xff0c;因此对终端用户进行规范的上网行为管理&#xff0c;既能有效预防重要数据泄密&#xff0c;同时也能提高员工办公效…

Java数据结构之《直接插入排序》(难度系数75)

一、前言&#xff1a; 这是怀化学院的&#xff1a;Java数据结构中的一道难度中等的一道编程题(此方法为博主自己研究&#xff0c;问题基本解决&#xff0c;若有bug欢迎下方评论提出意见&#xff0c;我会第一时间改进代码&#xff0c;谢谢&#xff01;) 后面其他编程题只要我写完…

新书推荐——《Copilot和ChatGPT编程体验:挑战24个正则表达式难题》

《Copilot和ChatGPT编程体验&#xff1a;挑战24个正则表达式难题》呈现了两方竞争的格局。一方是专业程序员David Q. Mertz&#xff0c;是网络上最受欢迎的正则表达式教程的作者。另一方则是强大的AI编程工具OpenAI ChatGPT和GitHub Copilot。 比赛规则如下&#xff1a;David编…

OpenResty(nginx+lua+resty-http)实现访问鉴权

OpenResty(nginxluaresty-http)实现访问鉴权 最近用BI框架解决了一些报表需求并生成了公开链接&#xff0c;现在CMS开发人员打算将其嵌入到业务系统中&#xff0c;结果发现公开链接一旦泄露任何人都可以访问&#xff0c;需要实现BI系统报表与业务系统同步的权限控制。但是目前…

7、Qt延时的使用

一、说明 平时用到两种延时方式QThread::sleep()和QTimer::singleShot() 1、QThread::sleep() QThread类中如下三个静态函数&#xff1a; QThread::sleep(n); //延迟n秒 QThread::msleep(n); //延迟n毫秒 QThread::usleep(n); //延迟n微妙 这种方式使用简单&#xff0c;但是会阻…

X540t2关于手动安装intel驱动

首先去intel驱动官网下载&#xff0c;win10和win11驱动一样 https://www.intel.cn/content/www/cn/zh/download/18293/intel-network-adapter-driver-for-windows-10.html 然后下载下来解压 将Wired_driver_28.2_x64.exe修改成Wired_driver_28.2_x64.zip文件再解压 打开设备管…

[Mac软件]HitPaw Video Converter 功能强大的视频格式转换编辑软件激活版

软件介绍&#xff1a; 以令人难以置信的速度将无损视频和音乐转换为1000多种格式&#xff1a;MP4、MOV、AVI、VOB、MKV等。不仅适用于普通编解码器&#xff0c;也适用于高级VP9、ProRes和Opus编码器。这解决了您不支持格式的所有问题&#xff0c;并允许您在任何平台和设备上播…

语义分割 DeepLab V1网络学习笔记 (附代码)

论文地址&#xff1a;https://arxiv.org/abs/1412.7062 代码地址&#xff1a;GitHub - TheLegendAli/DeepLab-Context 1.是什么&#xff1f; DeepLab V1是一种基于VGG模型的语义分割模型&#xff0c;它使用了空洞卷积和全连接条件随机&#xff08;CRF&#xff09;来提高分割…

SQL手工注入漏洞测试(PostgreSQL数据库)-墨者

———靶场专栏——— 声明&#xff1a;文章由作者weoptions学习或练习过程中的步骤及思路&#xff0c;非正式答案&#xff0c;仅供学习和参考。 靶场背景&#xff1a; 来源&#xff1a; 墨者学院 简介&#xff1a; 安全工程师"墨者"最近在练习SQL手工注入漏洞&#…

10、pytest通过assert进行断言

官方实例 # content of test_assert1.pydef f():return 3def test_function():assert f() 4def test_assert_desc():a f()# assert a % 2 0assert a % 2 0, "value was odd, should be even"解读与实操 pytest允许你使用标准python断言来验证测试中的期望和值&…

【工具使用-Audition】如何使用Audition频谱分析

一&#xff0c;简介 本文以Audition 2020为例&#xff0c;介绍如何生成频谱分析的图像。 二&#xff0c;操作步骤 使用快捷键“shift D” 三&#xff0c;总结 本文主要介绍如何查看频谱分析&#xff0c;供参考。

不会代码(零基础)学语音开发(语音控制双色LED)

语音开发板到手后&#xff0c;跟着说明做一遍。例程&#xff1a;语音控制双色LED&#xff01; 首先&#xff0c;进行固件烧录。 资料中已经给了固件&#xff0c;按照手册中的说明进行烧录即可。整体感受还挺简单&#xff0c;认真读手册即可实现。 需要注意的事项&#xff1a…

C语言——交换两个int变量的值,不能使用第三个变量。

交换两个int变量的值&#xff0c;不能使用第三个变量。即 a3,b5,交换之后a5,b3; #include<stdio.h> int main() {int a3;int b5;printf("a%d b%d\n",a,b);aa^b;ba^b;aa^b;printf("a%d b%d\n",a,b); } “^”——按位异或操作符&#xff0c;这里的按…

【ArcGIS Pro微课1000例】0048:深度学习--人群计数

文章目录 一、小学回忆录二、深度学习计算人头数三、案例实现一、小学回忆录 加载配套实验数据包中的图片及训练模型。你还记得当年的小学毕业班有多少同学吗?今天我们就用ArcGIS提供的人工智能工具,重温一下童年记忆。 二、深度学习计算人头数 本案例使用到的是深度学习中…

应急响应-挖矿病毒处理

应急响应-挖矿病毒处理 使用top​命令实时监控占用CPU资源的是哪个进程&#xff0c;结果可以看到是2725这个进程。 ​​ 再使用netstat -anltp命令查看网络连接状态&#xff0c;定位到对应的PID号后&#xff0c;就拿到了远程地址 ​​ 拿到远程IP&#xff0c;结果是VPN入口…

智能指针与动态内存

动态内存 new placement new 是 C 中的一种内存分配方式&#xff0c;它允许在给定的内存地址上构造对象&#xff0c;而不是在默认的堆上分配新的内存。这对于某些特殊的内存管理场景非常有用&#xff0c;例如在特定的内存池中分配对象。 C11 引入了 "new auto" 语法…