[数据结构基础]二叉树——堆的概念、结构、接口函数及经典的Topk问题和堆排序问题

news2025/2/24 17:45:37

目录

一. 堆的概念及结构

1.1 堆的概念

1.2 堆的结构及在内存中的存储

二. 堆的主要接口函数 

2.1 堆初始化函数HeapInit

2.2 堆销毁函数HeapDestroy

2.3 向堆中插入数据函数HeapPush(以小堆为例)

2.4 删除堆根节点数据函数HeapPop(小堆为例)

2.5 获取堆中数据个数函数HeapSize

2.6 获取堆的根节点数据函数HeapTop

2.7 判断堆中是否有数据函数HeapEmpty

三. 经典Topk问题

3.1 问题描述

3.2 Topk问题的三种解决方案(以筛选最大的前k个值为例)

四. 堆排序问题

4.1 问题描述

4.2 解决方法(排升序为例)

五. 建堆操作和向上向下调整操作的时间复杂度证明

5.1 建堆操作的时间复杂度证明

5.1.1 通过向下调整法建堆的时间复杂度证明

5.1.2 通过向上调整法建堆的时间复杂度证明

5.2 向上(向下)调整操作的时间复杂度证明


一. 堆的概念及结构

1.1 堆的概念

  • 如果有一个关键码的集合K=\{K_1,K_2,...,K_n\},把这个集合中的所有元素都按照完全二叉树的存储规则存储在一个数组中,若满足:K_i<=2*K_i+1K_i<=2*K_i+2(或K_i>=2*K_i+1K_i>=2*K_i+2),则把这样的二叉树称为小堆(或大堆)。
  • 大堆:树及任何一个子树中,任何一个父亲节点的值都大于或等于子节点的值。
  • 小堆:树及任何一个子树中,任何一个父亲节点的值都小于或等于子节点的值。

堆结构的代码定义方式与顺序表相同,只不过堆结构要求数据按照一定的规则(所有父亲节点都大于等于子节点或都小于等于子节点)进行存储。

typedef int HPDataType;   //堆中存储数据的类型

typedef struct Heap

{

        HPDataType* a;    //指向存储堆中数据的内存空间

        int size;    //堆中已有数据个数

        int capacity;  //堆的容量

}HP;

1.2 堆的结构及在内存中的存储

  • 假设父亲节点在数组中的下标为parent,则:左孩子节点下标 leftchild = 2 * parent + 1,右孩子节点下标 right = 2 * parent + 2。
  • 对于任意一个有父亲节点的节点,设其在数组中的下标为child,无论这个节点是左孩子节点还是右孩子节点,其父亲节点的下标为:parent = (child - 1) / 2 。
  • 数组中的数据的父子节点值的关系要满足大堆或小堆的要求。
图1.1  大堆和小堆的结构及在内存中的存储示意图

二. 堆的主要接口函数 

2.1 堆初始化函数HeapInit

  • 堆的初始化与顺序表的初始化完全相同,断言确保传入的参数不为空指针后,堆中战术不存储数据,将a置为NULL,将capacity和size置为0即可。

HeapInit函数代码:

void HeapInit(HP* hp)
{
	assert(hp);
	hp->a = NULL;
	hp->size = hp->capacity = 0;
}

2.2 堆销毁函数HeapDestroy

  • 堆的销毁也与顺序表的销毁完全相同,释放存储堆数据的内存空间,然后将size和capacity均置0即可。

HeapDestroy函数代码:

void HeapDestroy(HP* hp)
{
	assert(hp);

	free(hp->a);
	hp->a = NULL;  //释放存储堆数据的内存空间

	hp->size = hp->capacity = 0;
}

2.3 向堆中插入数据函数HeapPush(以小堆为例)

  • 检验堆中是否还存在剩余空间,如果堆已满,就进行扩容操作。
  • 在存储堆数据的数组最后添加数据,为了使数组中的数据满足小堆的结构要求,要对新插入的数据的位置进行向上调整。调整方法为(见图2.1):
  1. 将新插入的数据的大小与其父亲节点的数据进行比较,若该数据小于其父亲节点数据,则交换该节点与父亲节点的数据。
  2. 更新父亲节点和孩子节点,孩子节点变为原来的父亲节点,父亲节点变为新的孩子节点的父亲节点,比较更新后的父亲节点与孩子节点的数据,如须要交换就交换父亲节点和孩子节点的值。
  3. 当遇到父亲节点小于等于孩子节点,或将孩子节点调整到了根节点的位置,则终止调整,此时数组中的数据已经满足小堆结构要求,向堆中插入数据操作结束。
图2.1  小堆向上调整过程图解

 HeapPush函数代码:

//数据交换函数
void swap(HPDataType* a, HPDataType* b)
{
	HPDataType tmp = *a;
	*a = *b;
	*b = tmp;
}

//向上调整函数
//参数a为指向数组首元素的指针,child为插入新数据的下标
void Adjustup(HPDataType* a, int child)
{
	assert(a);

	int parent = (child - 1) / 2;  //父亲节点下标

	while (child)  //如果子节点为根节点,就停止调整
	{
		if (a[parent] > a[child])
		{
			//如果父亲节点值大于孩子节点值,调用函数交换数据
			swap(&a[parent], &a[child]);

			//更新父子节点下标
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}


//向堆中插入数据x
void HeapPush(HP* hp, HPDataType x)
{
	assert(hp);

	//检查是否需要扩容
	if (hp->capacity == hp->size)
	{
		int newcapacity = (hp->capacity == 0) ? 4 : 2 * hp->capacity;  //新空间大小
		HPDataType* tmp = (HPDataType*)realloc(hp->a, newcapacity * sizeof(HPDataType));
		if (NULL == tmp)
		{
			printf("realloc fail\n");
			exit(-1);
		}

		//扩容,更新容量
		hp->a = tmp;
		hp->capacity = newcapacity;
	}

	hp->a[hp->size] = x;
	++hp->size;

	Adjustup(hp->a, hp->size - 1);  //调用函数向上调整数据
}

2.4 删除堆根节点数据函数HeapPop(小堆为例)

  • 如果直接删除根节点数据而不进行其他任何操作,根节点变为第二个数据,那么数组中剩余的数据大概率会不符合小堆的结构要求(父亲节点小于等于孩子节点)。
  • 删除根节点数据,首先应当交换数组首元素(根节点元素)和末位元素的值。然后,将当前的末位元素排除出当前堆,从当前堆的根节点开始向下调整数据,使数组中的数据满足小堆的结构要求,向下调整数据的流程为:
  1. 选取根节点为父亲节点,根节点的子节点为孩子节点。
  2. 找出父亲节点的两个孩子节点中较小的那个,如父亲节点大于较小的孩子节点,就交换父亲节点的值和孩子节点的值。
  3. 更新父亲节点和孩子节点,父亲节点变为原来的孩子节点,孩子节点变为更新后的父亲节点的孩子节点,重复执行步骤2。
  4. 当孩子节点的下标超出数组的范围,或者父亲节点小于等于较小的孩子节点,则终止调整,此时数组中的数据减少了1个,且数据满足小堆的结构要求。

删除小堆根节点数据的具体操作见图2.2。

图2.2  删除小堆根节点操作流程示意图

HeapPop函数代码:

//数据交换函数
void swap(HPDataType* a, HPDataType* b)
{
	HPDataType tmp = *a;
	*a = *b;
	*b = tmp;
}

//向下调整函数
//a为存储堆数据的数组,n为当前堆中数据个数,parent为开始向下调整的节点
void Adjustdown(HPDataType* a, int n, int parent)
{
	int child = 2 * parent + 1;

	//向下调整数据
	while (child < n)  //孩子节点下标不超出数组范围
	{
		if (child + 1 < n && a[child] > a[child + 1])  //选出较小的子节点
			++child;

		//如果父亲节点大于较小的子节点,交换,否则终止调整
		if (a[parent] > a[child])
		{
			swap(&a[parent], &a[child]);
			//更新父子节点下标
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}


void HeapPop(HP* hp)
{
	assert(hp);
	assert(hp->size);  //断言堆中有数据

	swap(&hp->a[0], &hp->a[hp->size - 1]);  //交换首尾节点数据
	--hp->size;  //变更堆中数据个数

	Adjustdown(hp->a, hp->size, 0);  //向下调整数据函数
}

2.5 获取堆中数据个数函数HeapSize

  • 确保传入函数的参数不为空指针,返回hp->size即可。

HeapSize函数代码:

int HeapSize(HP* hp)
{
	assert(hp);
	return hp->size;
}

2.6 获取堆的根节点数据函数HeapTop

  • 确保传入函数的参数不是空指针并且堆中有数据,然后返回hp->a[0]即为根节点数据。

HeapTop函数代码:

HPDataType HeapTop(HP* hp)
{
	assert(hp);
	assert(hp->size);  //确保堆中有数据
	return hp->a[0];   //返回根节点数据
}

2.7 判断堆中是否有数据函数HeapEmpty

  • 若hp->size == 0成立,则堆中无数据,否则堆中有数据。

HeapEmpty函数代码:

bool HeapEmpty(HP* hp)
{
	assert(hp);
	return hp->size == 0;
}

三. 经典Topk问题

3.1 问题描述

经典Topk问题,就是在一组数据中,筛选出最大(或最小)的前k个值。生活中常见的外卖商家热榜排行就是经典Topk问题的典型实际应用。

3.2 Topk问题的三种解决方案(以筛选最大的前k个值为例)

方法一:先调用快排函数排降序,前k个值就是最大的k个。

方法一演示代码:

int cmp(const void* e1, const void* e2)
{
	return *(int*)e2 - *(int*)e1;
}

int main()
{
	int arr[] = { 1,4,2,45,23,56,23,45,12,45,78,14 };  //待筛选的数组
	int sz = sizeof(arr) / sizeof(arr[0]);  //数组中数据个数

	//调用快排函数对数组进行排序
	qsort(arr, sz, sizeof(arr[0]), cmp);

	//选出最大的前5个值打印
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", arr[i]);  //78 56 45 45 45
	}

	return 0;
}

分析:

  • 排降序的时间复杂度为O(log N)
  • Topk问题只要求筛选出前k个最大或最小值,将所有数据进行排序,做了大量无用功,效率低下。

方法二:把N个数据依次插入大堆(大堆根节点数据一定大于或等于其余所有节点数据),然后执行k次获取根节点数据操作和删除堆顶数据操作,每次获取根节点数据都取得了当前堆中的最大值。

方法二演示代码:

//打印最大的前k个数函数
void PrintTopk(HPDataType* a, int n, int k)
{
	HP hp;
	HeapInit(&hp);  //建大堆并初始化
	//将n个数据插入到大堆中
	for (int i = 0; i < n; ++i)
	{
		HeapPush(&hp, a[i]);
	}

	//执行k次获取根节点数据然后删除根节点操作
	while (k--)
	{
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}
}

int main()
{
	int n = 10;

	int* arr = (int*)malloc(n * sizeof(int));
	if (NULL == arr)
	{
		printf("malloc fail\n");
		exit(-1);
	}

	//随机生成10个0-100的值存入arr数组
	for (int i = 0; i < n; ++i)
	{
		int num = rand() % 100;
		arr[i] = num;
	}

	arr[1] = 100 + 1;
	arr[3] = 100 + 2;
	arr[4] = 100 + 3;
	arr[7] = 100 + 4;
	arr[9] = 100 + 5;

	//获取arr中最大的5个值
	PrintTopk(arr, n, 5);  //打印101 103 105 104 102

	free(arr);
	arr = NULL;

	return 0;
}

分析:

  • 建堆操作的时间复杂度为O(N),向上调整Adjustdown和向下调整Adjustup的时间复杂度为O(logN),用方法二实现TopK要先建堆,并且执行Pop操作时要执行向下调整操作Adjustdown,总共执行k次Pop操作。因此,使用方法二解决TopK问题的时间复杂度为:O(N+k*logN)
  • 要开辟N个节点空间来建堆,空间复杂度为O(N)。对于数据量十分巨大,内存无法容纳全部数据的情况不适用。

方法三:假设N非常大,内存中存不下这些数据,数据存放在了文件中。

依次进行如下操作:

  1. 用前k个数建立一个存储k个数据的小堆(排升序建小堆,排降序建大堆)。
  2. 用剩下的N-k的数据,依次与堆顶数据进行比较,如果比堆顶数据大,就替换堆顶数据,再向下调整。
  3. 最后堆里面剩下的k个数据就是最大的k个数据。
图3.1 方法三操作流程示意图

方法三演示代码:

void HeapPrint(HP* hp)
{
	assert(hp);
	for (int i = 0; i < hp->size; ++i)
	{
		printf("%d ", hp->a[i]);
	}
	printf("\n");
}

//筛选出最大的k个数函数并打印函数
//a为存储待排序数据的数组,n为总数据个数
void TopkPrint(HPDataType* a, int n, int k)
{
	assert(a);

	HP hp;
	HeapInit(&hp);  //初始化堆

	//将前k个数插入一个小堆
	int i = 0;
	for (i = 0; i < k; ++i)
	{
		HeapPush(&hp, a[i]);
	}

	//将后面n-k个数与根节点数据进行比较,如果大于根节点数据,则进行替换,然后向下调整数据
	for (i = k; i < n; ++i)
	{
		if (a[i] > hp.a[0])
		{
			hp.a[0] = a[i];  //替换数据
			Adjustdown(hp.a, k, 0);
		}
	}

	HeapPrint(&hp);  //调用堆数据打印函数打印最大的前k个数据
	HeapDestroy(&hp);  //销毁堆
}

int main()
{
	//选出10000个数中最大的5个数
	int* arr = (int*)malloc(10000 * sizeof(int));  //开辟存储数据的内存空间
	if (NULL == arr)  //检验内存开辟是否成功
		exit(-1);

	int i = 0;
	for (i = 0; i < 10000; ++i)
	{
		//生成10000个0-9999的数存入数组中
		arr[i] = rand() % 10000;
	}

	//将arr数组中随机5个数改到10000以上
	//筛选出最大的5个数就应该是被修改的这5个数
	arr[467] = 10000 + 1;
	arr[2345] = 10000 + 2;
	arr[3567] = 10000 + 3;
	arr[8934] = 10000 + 4;
	arr[9678] = 10000 + 5;

	//选出最大的5个数并打印
	TopkPrint(arr, 10000, 5);  //10001 10002 10003 10004 10005

	free(arr);
	arr = NULL;

	return 0;
}

分析:

  • 建堆操作的时间复杂度为O(N),向下调整操作的时间复杂度为O(logN)
  • 该方法要建立一个函数k个数据的小堆,最多进行N-K次向下调整操作,因此,整体的时间复杂度为O(K+(N-k)logk),渐进表示为O(Nlogk)

四. 堆排序问题

4.1 问题描述

给定一组数据,要求利用建堆和堆操作的思想,将这一组数据按升序或降序排列。

4.2 解决方法(排升序为例)

首先,对于应该建大堆还是建小堆的问题,给出下面的结论:

  • 排升序,建大堆
  • 排降序,建小堆

堆排序的操作流程如下(排升序):

  1. 建大堆,这里不再新开辟空间来建堆,而是将给定数据的数据顺序调整为满足大堆结构的排列。采用向下调整的方法来进行数据调整,从最后一个非叶子节点(度不为0的节点)开始向下调整,调整到根节点结束,此时数据的排列顺序已满足大堆的结构要求。
  2. 交换首尾节点的数据值,此时末尾节点为堆中的最大数据。
  3. 将末尾的节点排除出堆,从根节点开始,对堆进行向下调整操作。
  4. 重复步骤2和步骤3,直到堆中仅剩一个数据为止。
图4.1 堆排序排升序操作流程图(部分)

 堆排序函数HeapSort代码:

//数据交换函数
void swap(DataType* px, DataType* py)
{
	int tmp = *px;
	*px = *py;
	*py = tmp;
}

//向下调整函数
//a为存储待排序数据的数组,n为待排序数据的个数
void Adjustdown(DataType* a, int n, int parent)
{
	assert(a);
	int child = 2 * parent + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child] < a[child + 1])
			++child;

		if (a[parent] < a[child])
		{
			swap(&a[parent], &a[child]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}

//堆排序函数(升序)
void HeapSort(DataType* a, int n)
{
	assert(a);

	//先采用向下调整的方式将a中的数据建立为大堆
	//从后往前调整,叶子节点不用单独调整
	//因此,从第一个度不为0的节点开始往前调整到第一个节点即可
	int end = (n - 1 - 1) / 2;
	while(end >= 0)
	{
		Adjustdown(a, n, end);
		--end;
	}

	//将a排为大堆后
	//将a的数据首尾交换,排除最后一个节点,将堆进行向下调整
	//重复进行上述操作n-1次,堆(数组)中的数据变为升序
	end = n - 1;
	while (end)
	{
		swap(&a[0], &a[end]);
		Adjustdown(a, end, 0);
		--end;
	}
}

五. 建堆操作和向上向下调整操作的时间复杂度证明

5.1 建堆操作的时间复杂度证明

5.1.1 通过向下调整法建堆的时间复杂度证明

向下调整法建堆的时间复杂度为O(N)。

满二叉树是一种特殊的完全二叉树,这里使用满二叉树来证明建堆操作的时间复杂度。由于时间复杂度本身就是渐进表示,因此相差几个节点,并不会改变时间复杂度。

观察上图,为了让二叉树中的数据满足堆结构的要求,第一层的到第h层的节点分别至多向下移动h-1、h-2、h-3、...、2、1、0次,从第一层到第n-1层每层的节点数分别为2^02^12^2、...、2^{h-3}2^{h-2}2^{h-1}个,因此,总共要移动的次数T(n)为:

T(N)=2^0\times (h-1)+2^1\times (h-2)+2^2\times (h-3)...+2^{h-3}\times 2+2^{h-2}\times 1      (1)

2T(N)=2^1\times (h-1)+2^2\times (h-2)+2^3\times (h-3)...+2^{h-2}\times 2+2^{h-1}\times 1      (2)

错位相减,由(2)-(1)得:

T(n)=-2^0(h-1)+(2^1+2^2+...+2^{h-2})+2^{h-1}=-h+1+\frac{2-2^h}{1-2}=2^h-h-1

根据满二叉树的性质,节点数(N)和满二叉树深度的关系为:N=2^h-1h=log_{2}(N+1)

则有:T(N)=N-log_2(N+1),用大O渐进法表示时间复杂度为:O(N)。

综上,证得向下调整法建堆的时间复杂度为O(N)。

5.1.2 通过向上调整法建堆的时间复杂度证明

向上调整法建堆的时间复杂度为O(NlogN)

由上图得,对于高度为h的满二叉树构成的堆,最多进行向上调整的次数设为T(N),有:

T(N)=1\times2^1+2\times2^2+3\times2^3+...+(h-2)\times2^{h-2}+(h-1)\times2^{h-1}     (1)

2T(N)=1\times2^2+2\times2^3+3\times2^4+...+(h-2)\times2^{h-1}+(h-1)\times2^{h}      (2)

错位相减,由(2)-(1)得:

T(N)=-(2^2+2^3+...+2^{h-1})+(h-1)\times 2^h=(h-2)\times2^h+4

由:N=2^h-1h=log_{2}(N+1)

T(N)=(N+1)[log_2(N+1)-2]+4,用大O渐进法表示时间复杂度为O(NlogN)

综上,证得向上调整法建堆操作的时间复杂度为O(NlogN)。

5.2 向上(向下)调整操作的时间复杂度证明

向上(向下)调整操作的时间复杂度为O(logN)。

对于节点个数为N、层数为h的满二叉树,向上(向下)调整操作最多进行h-1次数据交换,同时,h=log_2(N+1),则调整次数为:T(N)=h-1=log_2(N+1)-1,用大O渐进法表示为O(logN)。综上,证得向上(向下)调整操作的时间复杂度为O(logN)

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

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

相关文章

C++ 夺冠!成为 TIOBE 2022 年度编程语言

目录&#xff1a;C夺冠—成为TIOBE2022年度编程语言一、前言二、C 摘得桂冠三、Top 10 编程语言 TIOBE 指数走势&#xff08;2002-2023&#xff09;四、历史排名&#xff08;1987-2023&#xff09;五、编程语言“名人榜”&#xff08;2003-2022&#xff09;六、说明一、前言 2…

vitepress(三):自动生成目录

上一节我们将自己的网站发布到了git pages上&#xff0c;但是现在我们需要每次更新一篇文章就重写一次目录&#xff0c;操作上十分的繁琐和不方便&#xff0c;所以我们需要一个方法去自动生成我们的侧边栏结构&#xff0c;方便我们每次只需要更新我们的项目即可。这里我们要知道…

【每日一题】【LeetCode】【第六天】【Python实现】加一

加一的解决之路 题目描述 测试案例&#xff08;部分&#xff09; 第一次 1这个很好理解&#xff0c;唯一的难点就是个位1导致的进位的问题&#xff0c;可能会只会导致十位1&#xff0c;也有像8999这样产生多次进位的情况。 为了解决进位问题&#xff0c;自己想到了第三天学…

mysql三表查询15个例子带你搞懂它

mysql三表查询30个经典案例创建三个表a、b、c表a中的数据表b中的数据表c中的数据1.查询出学习成绩70分以上的学生姓名与成绩与学科&#xff1b;2.查询姓名以mi结尾的学生姓名及其任课老师姓名&#xff1b;3.选修课名为math的学生学号与姓名;4.选修课号为C4的学生学号&#xff1…

QEMU调试Linux内核环境搭建

一个最小可运行Linux操作系统需要内核镜像bzImage和rootfs&#xff0c;本文整理了其制作、安装过程&#xff0c;调试命令&#xff0c;以及如何添加共享磁盘。编译内核源码从 The Linux Kernel Archives 网站下载内核源码&#xff0c;本文下载的版本为4.14.191&#xff0c;4.14.…

危险程度(并查集)

有 nn 种化学物质&#xff0c;编号 1∼n1∼n。 其中&#xff0c;有 mm 对物质之间会发生反应。 现在&#xff0c;要将这些化学物质逐个倒入同一个试管之中&#xff0c;具体倒入顺序不限。 我们需要计算一下试管的危险值。 已知&#xff0c;空试管的危险值为 11&#xff0c;…

【UE4 第一人称射击游戏】21-添加动态扩散准心

素材资料地址&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1epyD62jpOZg-o4NjWEjiyg密码&#xff1a;jlhr上一篇&#xff1a;【UE4 第一人称射击游戏】20-添加瞄准十字线本篇效果&#xff1a;步骤&#xff1a;将资源移至FPS项目文件夹内移入后发现多了一个名为“WBCro…

【web安全】——报错注入

作者名&#xff1a;Demo不是emo主页面链接&#xff1a; 主页传送门创作初心&#xff1a; 舞台再大&#xff0c;你不上台&#xff0c;永远是观众&#xff0c;没人会关心你努不努力&#xff0c;摔的痛不痛&#xff0c;他们只会看你最后站在什么位置&#xff0c;然后羡慕或鄙夷座右…

如何查看sqlite数据库的 .db文件中的表的内容数据

在使用 qt 的sqlite 数据的时候,对于创建的数据库的 .db 文件的内容的查看我们可以按照下面的步骤安装工具进行查看 下载所需的sqlite 查看工具 下载:链接&#xff1a;https://pan.baidu.com/s/1KSl9w61zaEyemhR1Ir04_A 提取码&#xff1a;6666 只需要解压即可,其中安装包内…

MINISForum HX90 主机风扇调教

今年秋天买了个1个HX90 5900H的mini主机。准系统版本&#xff0c;2899元。 但是买回来之后&#xff0c;发现它的风扇声音实在是大&#xff0c;稍微一加载点东西&#xff0c;就 开始呜呜的响&#xff0c;简直让人心烦 意乱。 去了官网查看。好多人的解决办法看了没看明白&…

【机器学习】PR曲线F1评分ROC曲线AUC

参考&#xff1a;《百面机器学习》 PR曲线 TP&#xff08; True Positive&#xff09;&#xff1a;真正例 FP&#xff08; False Positive&#xff09;&#xff1a;假正例 FN&#xff08;False Negative&#xff09;&#xff1a;假反例 TN&#xff08;True Negative&#xff0…

基于imx6ull配置开发环境

1. 交叉编译链背景&#xff1a;因为在原子的教程中有强调最新的Linaro gcc编译完uboot后无法运行的问题&#xff0c;所以原子采用4.9&#xff0c;那我们就沿用下。Linaro gcc有两个版本: gcc-linaro-4.9.4-2017.01-i686_arm-linux-gnueabihf.tar.tar.xz 和 gcc-linaro-4.9.4-20…

linux反弹备忘录

如果你有幸在渗透测试中发现了命令执行漏洞&#xff0c;那么不久之后你可能需要一个交互式shell。如果无法添加新帐户/ SSH密钥/ .rhosts文件并登录&#xff0c;则下一步可能是拖回反向shell或将shell绑定到TCP端口。 本页讨论前者。创建反向 shell 的选项受到目标系统上安装的…

【阶段三】Python机器学习05篇:机器学习项目实战:逻辑回归模型

本篇的思维导图: 要对离散变量进行预测,则要使用分类模型。分类模型与回归模型的区别在于其预测的变量不是连续的,而是离散的一些类别,例如,最常见的二分类模型可以预测一个人是否会违约、客户是否会流失、肿瘤是良性还是恶性等。逻辑回归模型虽然名字中有“回归…

Neural-Pull曲面重建程序配置

前几天一篇曲面重建文章的审稿意见回来了&#xff0c;要求加近三年对比方法。在github上搜了一些项目&#xff0c;大部分的环境都很难配置成功。最后找了一个ICML2021年的点云重建项目[1]作为实验对比。 项目链接&#xff1a;mabaorui/NeuralPull-Pytorch 整体来说&#xff0…

SpringBoot 多种方式配置错误页面

参考资料 SpringBoot异常处理机制-BasicErrorController与ControllerAdviceJava开发从工作到原理–BasicErrorController统一异常处理【spring boot】spring boot 处理异常SpringBoot一个请求的处理全过程ControllerAdvice和ErrorPageRegistrar接口配置错误页面的问题SpringBo…

【Linux操作系统】自动化编译make和Makefile

文章目录一.make/makefile简介1.什么是make,makefile?2.为什么要有make/makefile?二.makefile文件规则1.基本规则2.举一个例子3.伪目标4.其他规则三.文件三个时间问题-make程序1.三个时间何时更新2.touch的两个作用3.make程序如何知道依赖文件是否更新?一.make/makefile简介…

手写Srping11(实现AOP切面)

文章目录目标设计项目结构一、代理方法的案例二、代理方法案例拆解实现1、切点表达式——Pointcut2、类匹配器——ClassFilter3、方法匹配器——MethodMatcher4、实现切点表达式类——AspectJExpressionPointcut4.1、匹配验证5、包装切面信息——AdvisedSupport5.1、被代理的目…

Zynq PS之MIO、EMIO调试

目录 原理框图 Vivado中添加&配置Zynq UltraScale MPSoc IP UART设置&#xff08;仅用于调试&#xff0c;非必需&#xff09; MIO、EMIO设置 DDR配置 执行Generate Output Products 执行Create HDL Wrapper 执行File -> Export ->Export Hardware 执行Launch S…

Springboot中配置文件application.yaml的位置

文章目录位置一&#xff1a;整个项目的config包下位置二&#xff1a;整个项目的根目录下位置三&#xff1a;resources文件夹下config包中位置四&#xff1a;resources文件夹下四个位置的优先级位置一&#xff1a;整个项目的config包下 前些天发现了一个巨牛的人工智能学习网站&…