【数据结构】C语言实现堆及其应用

news2024/11/18 4:25:25

二叉树的顺序结构--堆

  • 一、堆的概念与实现
    • 堆的概念
    • 堆结构定义
    • 堆的初始化与销毁
    • 堆的插入与向上调整
    • 堆的打印、判空、元素个数size、堆顶元素
    • 堆的删除与向下调整
    • 大根堆与小根堆的写法区别
    • 堆的两种建立方式
    • 建堆的时间复杂度推导
  • 二、堆的应用
    • 堆的应用--topK
    • 堆的应用--堆排序
    • 优先级队列--待更新
  • 源码
  • 总结


一、堆的概念与实现

堆的概念

堆的定义:
如果有一个关键码的集合 K = { k 0 , k 1 , k 2 , . . . , k n − 1 } K = \{ k_0, k_1, k_2, ..., k_{n-1} \} K={k0,k1,k2,...,kn1}, 把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足: K i ≤ K 2 i + 1 K_i \le K_{2i + 1} KiK2i+1 K i ≤ K 2 i + 2 K_i \le K_{2i+2} KiK2i+2 K i ≥ K 2 i + 1 K_i \ge K_{2i + 1} KiK2i+1 K i ≥ K 2 i + 2 K_i \ge K_{2i+2} KiK2i+2 ),i = 0,1,2…,则称为小堆(大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

1.堆是一个完全二叉树
2.大堆:树中任何一个节点的父亲都大于或等于孩子。
小堆:树中任何一个节点的父亲都小于或等于孩子。
3.堆的物理结构是数组,逻辑结构是二叉树。逻辑结构方便理解。

堆结构定义

堆的本质是数组,因此定义堆就像定义数组一样:

typedef int HeapDataType;
typedef struct Heap
{
	HeapDataType* a;
	int size;
	int capacity;
}Heap;

堆的初始化与销毁

就是顺序表的操作。

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

void HeapDestroy(Heap* hp)
{
	assert(hp);
	free(hp->a);
	hp->capacity = hp->size = 0;
}

堆的插入与向上调整

1.先将元素插入到堆的末尾,即最后一个孩子之后。
2.插入之后如果堆的性质遭到破坏,则将新插入结点顺着双亲往上调整到合适的位置。

第一步在代码写法上跟顺序表的尾插一样,首先判断是否需要扩容,然后插入数据到末尾。

void HeapPush(Heap* hp, HeapDataType x)
{
	assert(hp);
	// 插入需要检查空间是否足够 是否需要扩容
	if (hp->size == hp->capacity)
	{
		size_t newCapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
		HeapDataType* tmp = realloc(hp->a, sizeof(HeapDataType) * newCapacity);
		if (tmp == NULL)
		{
			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);
}

第二步,在插入数据以后需要向上调整。

首先对该函数参数进行说明:第一个参数是数组而不是堆。第二个参数是待向上调整的元素索引。

void AdjustUp(int* a, int child);

实现过程中,所谓的向上调整就是把数据与父节点进行比较,此过程反复迭代,直到放到合适的位置。

结束的条件是:
1.child的值为0,表示迭代到堆顶。
2.或者迭代的中途,child与parent的值没有交换,也就是child在已经在合适的位置了。

void AdjustUp(int* a, int child)
{
	assert(a);
	
    // 1.先找到父节点索引
	int parent = (child - 1) / 2;

	while (child > 0)
	{
        // 小根堆向上调整规则
		if (a[child] < a[parent])
		{
            // 2.满足向上调整规则 继续迭代
			Swap(&a[child], &a[parent]);

			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
            // 2.已经在合适位置,无需向上调整
			break;
		}
	}
}

这个代码需要注意的是大根堆和小根堆的调整规则不同,具体是a[child]a[parent] 的比较方式不同。


大根堆向上调整函数:

// 大根堆向上调整
void AdjustUp_big(int* a, int child)
{
	assert(a);

	int parent = (child - 1) / 2;
	while (child > 0)
	{
		// 大根堆向上调整规则
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);

			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

堆的打印、判空、元素个数size、堆顶元素

堆的本质是数组,打印就是遍历数组:

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

判空就是数组 size 成员是否为0:

bool HeapEmpty(Heap* hp)
{
	assert(hp);

	return hp->size == 0;
}

函数HeapSize返回堆中元素个数:

int HeapSize(Heap* hp)
{
	assert(hp);

	return hp->size;
}

函数HeapTop返回堆顶元素:

HeapDataType HeapTop(Heap* hp)
{
	assert(hp);
	assert(!HeapEmpty(hp));

	return hp->a[0];
}

堆的删除与向下调整

删除堆是删除堆顶的数据。将堆顶的数据与最后一个元素交换,然后删除数组最后一个数据(尾删),再进行向下调整算法。

void HeapPop(Heap* hp)
{
	assert(hp);
	assert(!HeapEmpty(hp));

	Swap(&hp->a[0], &hp->a[hp->size - 1]);
	hp->size--;

	AdjustDown(hp->a, hp->size, 0);
}

向下调整函数说明:
1.第一个参数是待调整的数组。
2.第二个参数是待调整的数组大小。
3.第三个参数是待调整数据在数组中的位置,向下调整一般是从堆的根部开始,所以该参数一般是0。

void AdjustDown(int* a, int n, int parent);

所谓的向下调整具体就是跟左右孩子中一个进行交换,然后向下迭代。
小根堆:跟左右孩子中小的那个交换
大根堆:跟左右孩子中大的那个交换

小根堆的结束条件:
1.父节点parent调整到合适位置,其值小于等于左右孩子则停止。
2.或者从上到下调整到叶子结点。(叶子特征没有左孩子,也就是左孩子下标超出数组范围)

void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1;
    // 循环结束条件1是向下调整到叶子结点
	while (child < n)
	{
		// 1.选出左右孩子中小的那一个
		if (child + 1 < n && a[child + 1] < a[child])
		{
			++child;
		}

		// 2.如果小的孩子小于父亲,则交换,并继续向下调整
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
            // 循环结束条件2是parent在合适的位置
			break;
		}
	}
}

代码说明:
有了parent,我们首先需要找到左右孩子并且找到最小的孩子。
两个问题:第一个问题是调整的过程中,parent可能没有右孩子。第二个问题是有右孩子,找到左右孩子最小值。代码中我们仅定义child一个变量假设作为左孩子。并且使用如下代码很巧妙的避开了右孩子不存在情况,并且右孩子存在时能够找到左右孩子中最小的那个,excellent,同时完美解决了上面两个问题:

if(child + 1 < n && a[child + 1] < a[child]) // 小根堆调整 选出左右孩子中较小的
{
    ++child;
}

child + 1 < n:用于判断右孩子是否存在。

a[child + 1] < a[child]:右孩子存在,并且右孩子较小,则child++后指向右孩子。

经过上面代码,现在我们的child指向较小的孩子,因为我们需要将parent和child比较:
如果child小于parent,则需要继续向下调整。
否则,parent不比child大,就满足了小根堆的性质,调整结束。

// 小根堆调整 如果小的孩子小于父亲,则交换,并继续向下调整
if (a[child] < a[parent])
{
	Swap(&a[child], &a[parent]);
	parent = child;
	child = parent * 2 + 1;
}
else
{
	break;
}

大根堆的向下调整规则:

// 大根堆向下调整
void AdjustDown_big(int* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child] < a[child + 1])
		{
			++child;
		}

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

大根堆与小根堆的写法区别

从堆的实现来看,大根堆与小根堆的区别点在向上调整与向下调整。

向上调整的大根堆与小根堆区别:

void AdjustUp(int* a, int child)
{
	assert(a);


	int parent = (child - 1) / 2;
	while (child > 0)
	{
		// 这里是小根堆比较规则
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);

			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

大根堆向上调整:只需要将 if (a[child] < a[parent]) 改为 if (a[child] > a[parent])


向下调整的大根堆与小根堆区别:

void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		// 小根堆需要选出左右孩子中小的那一个
		if (child + 1 < n && a[child + 1] < a[child])
		{
			++child;
		}

		// 如果小的孩子小于父亲,则交换,
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

大根堆向下调整:

首先需要找到左右孩子中最大的那一个:

if (child + 1 < n && a[child] < a[child + 1])
{
	++child;
}

如果大孩子大于父亲,则交换:

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

堆的两种建立方式

方式1:向上调整法。

void CreateHeap()
{
	int a[] = { 70, 56, 30, 25, 15, 10, 75 };
	// 向上调整法建立堆
	Heap hp;
	HeapInit(&hp);
	for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i)
	{
		HeapPush(&hp, a[i]);
	}
	HeapPrint(&hp);
	HeapDestroy(&hp);
}

等同于下面写法:

void CreateHeap()
{
	int a[] = { 70, 56, 30, 25, 15, 10, 75 };
	// 向上调整法建立堆
	for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i)
	{
		AdjustUp(a, i);
	}
	// print a ...
}

使用这种方法本质上是向上调整法。既可以创建大根堆也可以创建小根堆,唯一的区别就是Push函数中调用的向上调整AdjustUp函数的不同。


方式2:向下调整法。

使用向下调整算法的前提是,左右子树都是小堆(或大堆)。

对于给定的数组a,我们可以使用向下调整的方法,其思想:

1.叶子所在的子树不需要向下调整。

2.从数组末尾倒着走到第一个非叶子结点的子树,也就是最后一个结点的父亲假设索引为i。

3.从i结点开始进行向下调整。i–,反复迭代到i为0。这样就完成了整个堆的构建过程。

void CreateHeap1()
{
	int a[] = { 70, 56, 30, 25, 15, 10, 75 };
	int n = sizeof(a) / sizeof(a[0]);
	// 向下调整法建立堆
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}
}

n - 1 :最后一个结点索引。
(n - 1 - 1) / 2:倒数第一个非叶子结点,也就是最后一个结点的父节点。

使用这种方法本质上是向下调整法。既可以创建大根堆也可以创建小根堆,唯一的区别就是调用的向下调整AdjustDown函数的不同。

建堆的时间复杂度推导

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果)

假设树的高度为 h h h:我们采用向下调整法建立堆的思路:考虑最坏情况,每次向下调整都需要移动到叶子节点。

第h-1层, 2 h − 2 2^{h-2} 2h2 个结点,需要向下移动1层

第h-2层, 2 h − 3 2^{h-3} 2h3 个结点,需要向下移动2层

第2层, 2 1 2^1 21 个结点,需要向下移动h-2层

第1层, 2 0 2^0 20 个结点,需要向下移动h-1层

所以移动结点的总移动步数:
在这里插入图片描述

由于: n = 2 h − 1 n = 2^{h} - 1 n=2h1,所以:
在这里插入图片描述

因此:建堆的时间复杂度是 O ( N ) O(N) O(N)

二、堆的应用

1.堆排序时间复杂度 O ( N l o g N ) O(NlogN) O(NlogN)

2.topK

3.优先级队列。

堆的应用–topK

在N个数中找出最大的前K个。

方式1:先排降序,前K个就是最大的。时间复杂度: O ( N l o g N ) O(N log N) O(NlogN)

方式2:N个数依次插入大堆,也就是构建N个数的大根堆。Pop K次,每次取堆顶的数据,取K次,需要向下调整K次。
构建一次N个数的大堆: O ( N ) O(N) O(N),Pop K次: O ( K l o g N ) O(K logN) O(KlogN)
时间复杂度: O ( N ) + O ( K l o g N ) O(N) + O(K logN) O(N)+O(KlogN)

方式3:假设N非常大,内存中存不下这些数,他们存在磁盘中。则方式1和方式2都不能用。

1.用前K个数建立一个K个数的小堆。
2.剩下的N-K个数,依次跟堆顶的数据进行比较。如果比堆顶数据大,就替换堆顶数据,再向下调整。
3.最后堆里面的K哥数就是最大的K个数。

想不通就画图测试。

excellent

构建K个数的小堆: O ( K ) O(K) O(K)
N-K个数的向下调整: O ( ( N − K ) l o g K ) O((N-K)logK) O((NK)logK)
时间复杂度: O ( K ) + O ( ( N − K ) l o g K ) O(K) + O((N-K)logK) O(K)+O((NK)logK)

K < < N K << N K<<N 时,显然方式3算法更优。

下面我们使用方式3实现topK问题:

void PrintTopK(int* a, int n, int k)
{
	Heap hp;
	HeapInit(&hp);
	// 1.创建一个K个数的小堆
	for (int i = 0; i < k; ++i)
	{
		HeapPush(&hp, a[i]);
	}

	// 2.剩下的N-K个数跟堆顶的数据比较,比堆顶数据大,就替换堆顶数据进堆
	for (int i = k; i < n; ++i)
	{
		if (a[i] > HeapTop(&hp))
		{
            // 这两行代码不如下面的写法更简洁
			HeapPop(&hp);
			HeapPush(&hp, a[i]);
		}
	}

	HeapPrint(&hp);
	HeapDestroy(&hp);
}

其中调整的两步作用等价于:

hp.a[0] = a[i];
AdjustDown(hp.a, hp.size, 0);

使用数据进行测试:

void TestTopk()
{
	int n = 1000000;
	int* a = (int*)malloc(sizeof(int) * n);
	srand((unsigned int)time(0));
	for (int i = 0; i < n; ++i)
	{
		a[i] = rand() % 1000000;
	}
	// 设置10个比100w大的数
	a[5] = 1000000 + 1;
	a[1231] = 1000000 + 2;
	a[5355] = 1000000 + 3;
	a[51] = 1000000 + 4;
	a[15] = 1000000 + 5;
	a[2335] = 1000000 + 6;
	a[9999] = 1000000 + 7;
	a[76] = 1000000 + 8;
	a[423] = 1000000 + 9;
	a[3144] = 1000000 + 10;
    
	PrintTopK(a, n, 10);
}

使用方式3的思想,求前 K 个最小值:

1.用前K个数建立一个K个数的大堆。
2.剩下的N-K个数,依次跟堆顶的数据进行比较。如果比堆顶数据小,就替换堆顶数据,再向下调整。
3.最后堆里面的K哥数就是最小的K个数。

想不通就画图测试。

void PrintTopK1(int* a, int n, int k)
{
	Heap hp;
	HeapInit(&hp);
	/*
	// 1.向下调整建立K个数的大堆
	hp.a = realloc(hp.a, k * sizeof(HeapDataType));
	for (int i = 0; i < k; ++i)
	{
		hp.a[i] = a[i];
		hp.size++;
	}
	for (int i = (k - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown_big(hp.a, k, i);
	}
	*/
	// 1.向上调整建立K个数的大堆
	hp.a = realloc(hp.a, k * sizeof(HeapDataType));
	for (int i = 0; i < k; ++i)
	{
		hp.a[i] = a[i];
		hp.size++;
	}
	for (int i = 0; i < k; ++i)
	{
		AdjustUp_big(hp.a, i);
	}


	// 剩下的N-K个数跟堆顶最大的数据比较,比他小,就替换堆顶数据进堆
	for (int i = k; i < n; ++i)
	{
        // 注意这里的比较规则不同!!!
		if (a[i] < HeapTop(&hp))
		{
			hp.a[0] = a[i];
			AdjustDown_big(hp.a, hp.size, 0);
		}
	}

	HeapPrint(&hp);
	HeapDestroy(&hp);
}

需要注意的是,在剩下 N-K 个数替换堆顶元素进堆时的比较规则不同。

如果是topK大的:a[i] > HeapTop(&hp)
如果是topK小的:a[i] < HeapTop(&hp)

堆的应用–堆排序

很自然想到以下方法:

void HeapSort1(int* a, int n)
{
	Heap hp;
	HeapInit(&hp);
	// 建议N个元素的小堆
	for (int i = 0; i < n; ++i)
	{
		HeapPush(&hp, a[i]);
	}

	// Pop N 次
	for (int i = 0; i < n; ++i)
	{
		a[i] = HeapTop(&hp);
		HeapPop(&hp);
	}

	HeapDestroy(&hp);
}

这种方法用了额外空间,下面要求只在原数组空间中进行堆排序。


方法1:建立小根堆,根为最小元素。找次小元素需要对除根以外的元素重新建立小根堆。

对于数组a,进行堆排序:(升序)

初始i=0,n=size-1
1.遍历数组a下标从 [i, n] 的每个元素,依次插入到堆中,构建一个小根堆。此时根处元素最小。
2.i++,返回到1处。

void HeapSort2(int* a, int n)
{
	int* arr = a;
	int arrSize = n;

	while (arrSize > 1)
	{
		// 1.向上调整建立小根堆
		for (int i = 1; i < arrSize; ++i)
		{
			AdjustUp(arr, i);
		}
		// 2.数组剩余元素需要重新建立小根堆
		arr += 1;
		arrSize -= 1;
	}
}

循环内建堆时间复杂度: O ( N + ( N − 1 ) + ( N − 2 ) + . . . + 1 ) = O ( N 2 ) O(N + (N - 1) + (N - 2) + ... + 1) = O(N^2) O(N+(N1)+(N2)+...+1)=O(N2)

测试:

void HeapSortTest()
{
	int a[] = { 70, 56, 30, 25, 15, 10, 75 };
	int n = sizeof(a) / sizeof(a[0]);

	HeapSort2(a, n);

	for (int i = 0; i < n; ++i)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

方法2:建立大根堆,然后删除根,此时最后一个元素为最大值,向下调整去除最后一个元素的剩余元素,这样就取出了次大值很快建立了大根堆。继续上面的删除。

void HeapSort3(int* a, int n)
{
	// 1.使用向下调整建立大根堆
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown_big(a, n, i);
	}

	// 2.交换堆顶和末尾 大根堆的堆顶最大值放在数组末尾 重新调堆
	// O(N*logN)
	for (int end = n - 1; end > 0; --end)
	{
		Swap(&a[end], &a[0]);

		// 再调堆,选出次小的数
		AdjustDown_big(a, end, 0);
	}
}

还可以使用上面方式2的测试查看堆排序效果。

注意到该函数的实现:建立大根堆和堆调整函数统一,都是用的是AdjustDown_big。excellent。

总的时间复杂度: O ( N l o g N ) O(N log N) O(NlogN)

上面的代码很容易改成降序:排序的思想没变,只不过使用的是小根堆。

void HeapSort3(int* a, int n)
{
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}
    
	for (int end = n - 1; end > 0; --end)
	{
		Swap(&a[end], &a[0]);

		AdjustDown(a, end, 0);
	}
}

切换大小根堆就能切换升序降序,excellent!

优先级队列–待更新

源码

Gitee-Heap

总结

1.函数AdjustUp和AdjustDown的改变控制者大小根堆的切换。

2.大小根堆的切换可以决定堆排序的升序和降序。

3.大小根堆的切换以及进堆时比较规则的切换可以决定topK大和topK小。

4.建堆的时间复杂度: O ( N ) O(N) O(N)

5.堆排序时间复杂度: O ( N l o g N ) O(N log N) O(NlogN)

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

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

相关文章

learn_C_deep_11 (深刻理解整形提升、左移和右移规则、花括号、++和--操作、表达式匹配:贪心算法)

目录 深刻理解整形提升 左移和右移规则 如何理解"丢弃" 一个问题 0x01<<23 的值是多少 花括号 、--操作 表达式匹配&#xff1a;贪心算法 深刻理解整形提升 #include <stdio.h> int main() {char c 0;printf("sizeof(c): %d\n", sizeo…

C++系列之类与对象(上)

&#x1f497; &#x1f497; 博客:小怡同学 &#x1f497; &#x1f497; 个人简介:编程小萌新 &#x1f497; &#x1f497; 如果博客对大家有用的话&#xff0c;请点赞关注再收藏 &#x1f31e; 类 类的概念 C是基于面向对象的&#xff0c;是对象与对象之间的交互完成的&am…

docker构建PHP环境

docker构建PHP环境 文章目录 docker构建PHP环境下载镜像构建本地目录创建容器配置补充命令解释设置docker启动时启动容器 下载镜像 # php 镜像 docker pull php:7.4-fpm # nginx镜像 docker pul nginx:lates # 检查下载的镜像 docker images构建本地目录 本次构建是在win系统…

怎么查营业执照经营范围

怎么查营业执照经营范围 1.到企业公司所在地查询。一般工商局都要求公司将营业执照正本悬挂于企业办公室醒目位置,在公司工商营业执照正副本中均有描述。 2.登陆国家工商管理网站查询。可以登陆开具发票单位所在的工商行政管理局网站,输入企业名称就可以查询法人、企业类型、经…

第七章 中断

中断是什么&#xff0c;为什么要有中断 并发是指单位时间内的累积工作量。 并行是指真正同时进行的工作量。 一个CPU在一个时间只能执行一个进程&#xff0c;任何瞬间任务只在一个核心上运行。 而CPU外的设备是独立于CPU的&#xff0c;它与CPU同步运行&#xff0c;CPU抽出一点…

2023年十大最佳黑客工具!

​用心做分享&#xff0c;只为给您最好的学习教程 如果您觉得文章不错&#xff0c;欢迎持续学习 在今年根据实际情况&#xff0c;结合全球黑客共同推崇&#xff0c;选出了2023年十大最佳黑客工具。 每一年&#xff0c;我都会持续更新&#xff0c;并根据实际现实情况随时更改…

山西煤矿电子封条算法 opencv

山西煤矿电子封条通过pythonopencv网络模型AI视觉技术&#xff0c;pythonopencv算法模型实现对出入井人监察控制、调度室空岗识别、生产作业状态、摄像头遮挡、挪动角度识别、货运车辆出矿识别等。 OpenCV的全称是Open Source Computer Vision Library&#xff0c;是一个跨平台…

CleanMyMac X如何下载解锁完整版本?

这是一款很受到mac用户喜爱的清理软件。不仅清理文件的步骤十分简单&#xff0c;电脑小白用户也可以高效清理Mac电脑。作为一款全方位保护电脑的软件&#xff0c;CleanMyMac已经不满足于只做简单的Mac清理工具&#xff0c;而是为mac用户提供更多的实用功能&#xff1a;优化系统…

机器学习随记(7)——bootstrap、bagging、boosting、随机森林

bootstrap&#xff1a;是一种统计方法&#xff0c;也是一种思想&#xff0c;简单说就是在所有样本集中进行有放回地抽样&#xff0c;抽取n个样本。如果不清楚样本的分布&#xff0c;bootstrap是一种合适的方法。 bagging&#xff1a;Bagging方法在训练过程中&#xff0c;各基分…

windows提权

权限提升概述 1、提权介绍 权限提升&#xff1a;攻击者通过安全漏洞把获取到的受限制的低权限用户突破限制&#xff0c;提权至高权限的管理员用户&#xff0c;从而获取对整个系统的控制权 windows&#xff1a;user --> system/administrator linux&#xff1a;user --&g…

ASEMI代理Infineon英飞凌IPB072N15N3G原厂MOS管

编辑-Z IPB072N15N3G参数描述&#xff1a; 型号&#xff1a;IPB072N15N3G 持续漏极电流&#xff1a;100A 脉冲漏极电流&#xff1a;400A 雪崩能量&#xff0c;单脉冲&#xff1a;780 mJ 栅极-源极电压&#xff1a;20V 功率耗散&#xff1a;300W 操作和储存温度&#xf…

Voxformer代码 DataLoader 的编写

Stage 1: 目标是 使用QPN 生成 Occupancy Field 读取 需要读取 pseudo 的 vox_path 实际的 test 发生在 lmsnet.py 这个文件 input :25625632 的 pseudo point output: 12812816 的 Occupancy Grid 代码中 实际inference 的输入是 img_metas[0]["pseudo_pc"] 因此…

算法小课堂(十)随机化算法

目录 一、概述 1.1概念 1.2分类 二、数值随机化算法 2.1随机数 2.2用随机投点法计算Π值 2.3随机投点法计算定积分 三、舍伍德&#xff08;Sherwood&#xff09;型随机化算法 3.1随机洗牌算法 3.2随机快速排序&#xff1a;随机选择枢点的快速排序算法 3.3找出这n个元素…

STL——string类的模拟实现

0.关注博主有更多知识 C知识合集 目录 1.编码问题 2.string类概述 2.6习题练习 3.string类的模拟实现 3.1成员变量 3.2迭代器部分 3.3类的默认成员部分 3.4容量接口 3.5增删查改接口 3.6通用接口 3.7输入与输出 3.8完整代码 1.编码问题 实际上在我们接触C之前就…

SpringBoot入门(构建、打包、启动、起步依赖starter)

文章目录 1 SpringBoot快速入门1.1 开发步骤步骤1 创建新模块步骤2 创建 Controller步骤3 启动服务器步骤4 进行测试 1.2 对比1.3 官网构建工程步骤1 进入SpringBoot官网步骤2 选择依赖步骤3 生成工程 1.4 SpringBoot工程快速启动1.4.1 问题导入1.4.2 打包1.4.3 启动 2 SpringB…

OverTheWireBandit教程(1-10)

这个网站还挺好玩的于是我就抽点时间做了一下 OverTheWire的登录网址&#xff1a;OverTheWire: Bandit 本人用的是远程连接软件mobaxterm&#xff0c;windows自带的ssh版本不对用不了 Bandit Level 0 Level Goal The goal of this level is for you to log into the game usi…

使用ASM直接生成字节码的方法

ASM是一套java字节码分析/生成/修改的工具&#xff0c;它能够在java程序运行时直接修改java字节码文件&#xff0c;换句话说它能够直接修改java的二进制文件&#xff1b;也能够跳过编译直接生成字节码文件。所以ASM功能非常强大&#xff0c;对于代码性能提升、代码问题定位都非…

【技术】《Netty》从零开始学netty源码(六十)之ByteToMessageDecoder

ByteToMessageDecoder 在Netty中用于拆包的解码器都继承了抽象类ByteToMessageDecoder&#xff0c;它的类结构如下&#xff1a; 从中可以看出它其实就是一个handler&#xff0c;只要添加到pipeline中channel每次读取数据的时候都会得到解析&#xff0c;它的数据结构如下&#…

业绩涨,股价不涨,蓝思科技深陷「果链困局」

作者 | 辰纹 来源 | 洞见新研社 曾经的女首富&#xff0c;蓝思科技董事长周群飞很困惑&#xff0c;公司业绩明明还算不错&#xff0c;可股价却怎么也涨不起来&#xff0c;距离市值2000亿的顶点更是遥遥无望。 根据不久前&#xff08;4月23日&#xff09;蓝思科技发布的2022年…

Mybatis中处理特殊SQL处理逻辑

文章目录 0、前言1、模糊查询2、动态表名3、获取自增的组件4、批量删除 0、前言 在MyBatis中可能会有一些特殊的SQL需要去执行&#xff0c;一般就是模糊查询、批量删除、动态设置表名、添加功能获取自增的主键这几种&#xff0c;现在分别来进行说明。 为了方便演示 &#xff0…