二叉树的顺序结构及堆的实现

news2024/11/27 3:37:09

                                       创作不易,兄弟们给个三连!!

一、二叉树的顺序存储

      顺序结构指的是利用数组来存储,一般只适用于表示完全二叉树,原因如上图,存储不完全二叉树会造成空间上的浪费,有的人又会问,为什么图中空的位置不能存储呢??原因是我们需要根据数组的下标关系才能访问到对应的节点!!有以下两个下标关系公式:

1、父亲找孩子:leftchild=parent*2+1,rightchild=parent*2+2

2、孩子找父亲:parent=(child-1)/2   要注意,这边无论用左孩子算还是右孩子算都是可以的,因为一般俩说,(child-1)/2 由于int类型向下取整的特点,所以得到的结果都是一样的!!

      所以我们想要上面这种方式去访问节点,并且还不希望有大量的空间浪费,现实中只有堆才会使用数组存储,二叉树的顺序存储中在物理上是一个数组,再逻辑上是一颗二叉树!!

二、堆的概念及结构

    现实中我们把堆(类似完全二叉树)使用顺序结构来存储,要注意这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分区。

   如果有一个关键码的集合k,我们将他的全部元素按照完全二叉树的存储逻辑放在一个一维数组中,则成为堆,根节点最大的堆叫做大堆,根节点最小的堆叫做小堆。 

堆的性质:

1、堆中某个节点的值总是不大于或不小于其父节点的值

2、堆总是一颗完全二叉树

注意:并不一定有序 

三、堆的实现

假设我们实现小堆

3.1 相关结构体的创建

跟顺序表的形式是一样的,但是换了个名字

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

3.2 堆的初始化

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

3.3 堆的插入

堆的插入很简单,但是我们要保证堆插入后还能维持堆的形状

所以我们在插入后,还要进行向上调整,也就是孩子要根据下标关系找到自己的父亲去比较,小就交换

void HeapPush(Heap* php, HPDataType x)
{
	assert(php);
	//首先要判断是否需要扩容
	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
		HPDataType* temp = (HPDataType*)realloc(php->a,sizeof(HPDataType) * newcapacity);
		if (temp == NULL)
		{
			perror("malloc fail");
			exit(1);
		}
	    //扩容成功
		php->a = temp;
		php->capacity = newcapacity;
	}
     //扩容后,我们插入这个元素并size++
	php->a[php->size++] = x;
	//但是插入之后可能会破坏堆的结构,所以我们需要这个元素和他的父辈进行逐个比较, 
	AdjustUp(php->a,php->size-1);//封装一个向上调整函数,传入数组和新加元素的下标
}

3.4 向上调整算法

void AdjustUp(HPDataType* a, int child)
{
	assert(a);
    //通过孩子找父亲  parent=(child-1)/2
	int parent = (child - 1) / 2;
	//孩子和父亲开始比较,如果孩子小,就交换,如果孩子大,退出循环
	while (child>0)//如果孩子变成了根节点,就没有必要再找了,因为已经没有父母了
		//如果用parent>=0来判断,那么由于(0-1)/2是-1/2,取整后还是0,就会一直死循环,所以必须用孩子来当循环条件
	{
		if (a[child] < a[parent])//孩子小,交换
		{
			Swap(&a[child], &a[parent]);
			//但是交换过后,可能还需要继续往上比,所以我们要让原来的父亲变成孩子,然后再找新的父亲进行比较
			child = parent;
			parent = (child - 1) / 2;
		}
		else//孩子大,退出
			break;
	}
}

注:这里的向上调整算法和后面向下调整算法我们都不用跟堆有关的接口,原因就是这个算法的运用范围很广,可以用在堆排序以及top-k问题中!!

3.5 交换函数

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}

3.6 堆的删除

         一般来说,如果直接删除堆的最后一个元素,其实是没什么意义的,一行代码就可以搞定,没必要封装什么函数,所以这里的堆的删除指的是删除根部的元素!!

        

void HeapPop(Heap* php)//一般来说,堆中的删除指的是删除根位置的数据
//如果直接删除根然后往前挪动一位,那么亲缘关系就会十分混乱,为了能够尽量在调整中减少对关系的改变
//我们将根部元素与最后一个元素进行交换之后再删除,此时的根是原先的最后一个元素
//然后将该元素进行向下调整(封装一个函数,传入数组、元素个数、)
{
	assert(php);
	assert(!HeapEmpty(php));//为空的话没有删除的必要
	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;
	//开始向下调整
	AdjustDown(php->a, php->size,0);
}

3.7 向下调整算法

void AdjustDown(HPDataType* a, int n,int parent)
{
	assert(a);
	//此时根部为原来的最后一个元素,往下比较
	//即通过父亲去找到自己的孩子,如果孩子比自己小,就得交换位置,如果孩子比自己大,就退出
	//但是因为父亲有一个左孩子parent*2+1,右孩子parent*2+2,我们选择孩子中较小的和自己交换
	int child = parent * 2 + 1;//假设左孩子比右孩子小
	while (child<n)//当child超出个数的时候结束
	{
		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;//如果孩子大于等于父亲,直接退出
	}
}

       在上述算法中,我们应用了先假设再推翻的方法,一开始我们先假设左孩子比较小,然后我们再给个条件判断,如果左孩子大于右孩子,假设不成立,再推翻,这样可以保证我们的child变量一定是较小的孩子!! 

       虽然这里的parent很明显是从a[0]开始,好像不需要专门去传一个parent的参数,但是这也是为了之后的堆排序做准备!

3.8 取堆顶的数据

HPDataType HeapTop(Heap* php)
{
	assert(php);
	assert(!HeapEmpty(php));//为空的话没有取的必要
	return php->a[0];
}

3.9 堆的数据个数

int HeapSize(Heap* php)
{
	assert(php);
	return php->size;
}

3.10 堆的判空

bool HeapEmpty(Heap* php)
{
	assert(php);
	return php->size == 0;
}

3.11 堆的销毁

void HeapDestory(Heap* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->size = php->capacity = 0;
}

3.12 堆的打印(测试)

我们要实现堆的打印,利用我们之前封装的函数,每获取一次堆顶元素就删除一次,直到堆删完就可以获取全部的元素了!!

#include"Heap.h"
int main()//该方法实现堆的顺序打印
{
	Heap hp;
	HeapInit(&hp);
	int a[] = { 55,100,70,32,50,60 };
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
		HeapPush(&hp, a[i]);//不断进堆
	while (!HeapEmpty(&hp))
	{
		int top = HeapTop(&hp);
		printf("%d\n", top);
		HeapPop(&hp);
	}
	HeapDestory(&hp);
	return 0;
}

前面只是先创建一个堆,从while循环开始才是实现对堆的打印!!

运行结果 :32 50 55 60 70 100

          我们发现了一个情况:按道理来说堆只有父子节点之间有大小关系,兄弟之间没有的,但是我们最后打印出来的结果却完成了排序!!!下面我们来进行分析

     总之任何一个堆,我们都可以通过不断地pop去实现它的顺序打印!!堆排序后面会介绍!

四、堆实现的全部代码

4.1 Heap.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

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

void Swap(HPDataType* p1, HPDataType* p2);//实现父亲和孩子的交换
void AdjustUp(HPDataType* a, int child);//向上调整算法

// 堆的初始化
void HeapInit(Heap* php);
// 堆的插入
void HeapPush(Heap* php, HPDataType x);
// 堆的删除
void HeapPop(Heap* php);
// 取堆顶的数据
HPDataType HeapTop(Heap* php);
// 堆的数据个数
int HeapSize(Heap* php);
// 堆的判空
bool HeapEmpty(Heap* php);
// 堆的销毁
void HeapDestory(Heap* php);

4.2 Heap.c

#include"Heap.h"
//当前实现小堆
void HeapInit(Heap* php)
{
	assert(php);
	php->a = NULL;
	php->capacity = php->size = 0;
}

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}

void AdjustUp(HPDataType* a, int child)
{
	assert(a);
    //通过孩子找父亲  parent=(child-1)/2
	int parent = (child - 1) / 2;
	//孩子和父亲开始比较,如果孩子小,就交换,如果孩子大,退出循环
	while (child>0)//如果孩子变成了根节点,就没有必要再找了,因为已经没有父母了
		//如果用parent>=0来判断,那么由于(0-1)/2是-1/2,取整后还是0,就会一直死循环,所以必须用孩子来当循环条件
	{
		if (a[child] < a[parent])//孩子小,交换
		{
			Swap(&a[child], &a[parent]);
			//但是交换过后,可能还需要继续往上比,所以我们要让原来的父亲变成孩子,然后再找新的父亲进行比较
			child = parent;
			parent = (child - 1) / 2;
		}
		else//孩子大,退出
			break;
	}
}

void AdjustDown(HPDataType* a, int n,int parent)
{
	assert(a);
	//此时根部为原来的最后一个元素,往下比较
	//即通过父亲去找到自己的孩子,如果孩子比自己小,就得交换位置,如果孩子比自己大,就退出
	//但是因为父亲有一个左孩子parent*2+1,右孩子parent*2+2,我们选择孩子中较小的和自己交换
	int child = parent * 2 + 1;//假设左孩子比右孩子小
	while (child<n)//当child超出个数的时候结束
	{
		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;//如果孩子大于等于父亲,直接退出
	}
}


void HeapPush(Heap* php, HPDataType x)
{
	assert(php);
	//首先要判断是否需要扩容
	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
		HPDataType* temp = (HPDataType*)realloc(php->a,sizeof(HPDataType) * newcapacity);
		if (temp == NULL)
		{
			perror("malloc fail");
			exit(1);
		}
	    //扩容成功
		php->a = temp;
		php->capacity = newcapacity;
	}
     //扩容后,我们插入这个元素并size++
	php->a[php->size++] = x;
	//但是插入之后可能会破坏堆的结构,所以我们需要这个元素和他的父辈进行逐个比较, 
	AdjustUp(php->a,php->size-1);//封装一个向上调整函数,传入数组和新加元素的下标
}

void HeapPop(Heap* php)//一般来说,堆中的删除指的是删除根位置的数据
//如果直接删除根然后往前挪动一位,那么亲缘关系就会十分混乱,为了能够尽量在调整中减少对关系的改变
//我们将根部元素与最后一个元素进行交换之后再删除,此时的根是原先的最后一个元素
//然后将该元素进行向下调整(封装一个函数,传入数组、元素个数、)
{
	assert(php);
	assert(!HeapEmpty(php));//为空的话没有删除的必要
	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;
	//开始向下调整
	AdjustDown(php->a, php->size,0);
}


HPDataType HeapTop(Heap* php)
{
	assert(php);
	assert(!HeapEmpty(php));//为空的话没有取的必要
	return php->a[0];
}

int HeapSize(Heap* php)
{
	assert(php);
	return php->size;
}

bool HeapEmpty(Heap* php)
{
	assert(php);
	return php->size == 0;
}

void HeapDestory(Heap* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->size = php->capacity = 0;
}

4.3 test.c(测试)

#include"Heap.h"
int main()//该方法实现堆的顺序打印
{
	Heap hp;
	HeapInit(&hp);
	int a[] = { 55,100,70,32,50,60 };
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
		HeapPush(&hp, a[i]);//不断进堆
	while (!HeapEmpty(&hp))
	{
		int top = HeapTop(&hp);
		printf("%d\n", top);
		HeapPop(&hp);
	}
	HeapDestory(&hp);
	return 0;
}

五、堆的应用

5.1 堆排序

要对数组排序前,我们要用堆排序,首先要建堆!

大家看看之前堆的打印时的测试代码逻辑的方法

就是我们得到一个数组,就先建堆,然后先把数组push进去,再pop出来,是可以实现有序的

但是现在我们的需求不是打印出来,而是将他排好序后放进数组里,所以们可以这么写:

void HeapSort(int* a, int n)
{
	HP hp;
	HeapInit(&hp);
	// N*logN
	for (int i = 0; i < n; ++i)
	{
		HeapPush(&hp, a[i]);
	}

	// N*logN
	int i = 0;
	while (!HeapEmpty(&hp))
	{
		int top = HeapTop(&hp);
		a[i++] = top;
		HeapPop(&hp);
	}

	HeapDestroy(&hp);
}

 这个方法固然是可以的,但是很麻烦,原因如下:

1、每次都要建立一个新的堆,然后再销毁,比较麻烦,而且空间复杂度比较高 

2、我通过把数组放进变成堆,还要再把堆拷贝到数组中,数据的拷贝是很繁琐的!!

所以我们要思考一种方式避免数据的拷贝,所以就有了向上调整建堆和向下调整建堆的方法了!!

也就是我们在原数组的基础上直接建堆,然后向下调整排序即可,下面会详细介绍

5.1.1 向上调整建堆

 假设数组有n个元素

for (int i = 1; i < n; i++)
{
	AdjustUp(a, i);
}

5.1.2 向下调整建堆

for (int i = (n-1-1)/2; i >= 0; i--)
{
	AdjustDown(a, n, i);
}

5.1.3 堆排序的实现

那我们究竟选择向下建堆好还是向下建堆好呢??我们来分析一下

所以我们发现向上调整建堆的时间复杂度大概是N*logN,而向下调整建堆的时间复杂度是N

其实们在推导的时候也能发现,向上调整建堆是节点多的情况调整得多,节点少的情况调整的少,次数是多*多+少*少 ,而向下调整建堆是节点多的情况调整得少,节点少的情况调整的多,次数是多*少+少*多,显然是向下调整建堆是更有优势的!!

     接下去我们建好堆,就要想着怎么去排序了,我们思考一下,之前我们对堆的打印时,不断pop打印出来有序结果的原因是什么??原因就是pop函数里的向下调整算法!!每一次交换根节点和尾节点,将每个节点进行向下调整,最后就可以得到有序的

 

 因为我们之前实现的向下调整算法是小堆的,所以我们这边来实现一个降序的堆排序算法

void HeapSort(int* a, int n)
{
	//降序  建小堆
	//升序  建大堆
	for (int i = (n-1-1)/2; i >=0;i--)
		AdjustDown(a, n, i);
	//开始排序   先交换向下调整
	int end = n - 1;
	while (end >= 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

 

 如果我们想实现升序,将向下调整算法按照大堆的规则改一下就行 

向下调整算法和向上调整算法的空间复杂度都是(logN) 

堆排序中,建堆的时间复杂度是o(N),排序的时间复杂度是(N*logN)所以堆排序的总时间复杂度是N*logN

5.2 TOP-K问题

Top-k问题:即求数据中前k个最大的元素或者是最小的元素,一般情况下的数据量都比较大!

比如:专业前10名、世界五百强、富豪榜前十

堆排序能够帮助我们在大量数据中筛选出最好的几个。

5.2.1 思路

        比如说我们要从1000个学生的成绩中找到前10个分数最高的,方法就是将所有的数据放在一个数组里,直接建大堆,然后pop9次就可以找到了(pop中的向下调整算法可以使得每次pop出去的都是最大值,然后pop9次的原因是因为第10次就可以直接去获取堆顶元素即可)

但是有些情况,上述思路解决不了,分析:

5.2.2 通过数组验证TOP-K

void PrintTopK(int* a, int n, int k)
{
	//建前k个建小堆
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
		AdjustDown(a, k, i);
	//将剩余n个数据不断与堆顶元素比较,大就交换,然后向下调整
	for (int i = k; i < n; i++)
	{
		if (a[i] > a[0])
		{
			a[0] = a[i];//直接覆盖就行,不用交换
			AdjustDown(a, k, 0);
		}
	}
	//打印
	for(int i=0;i<k;i++)
	printf("%d ", a[i]);
}

void TestTopk()
{
	int n = 10000;
	int* a = (int*)malloc(sizeof(int) * n);
	srand((unsigned int)time(NULL));
	for (size_t i = 0; i < n; ++i)
	{
		a[i] = rand() % 1000000;//随机数范围0-999999
	}
// 为了能够方便找到这些数
	a[5] = 1000000 + 1;
	a[1231] = 1000000 + 2;
	a[531] = 1000000 + 3;
	a[5121] = 1000000 + 4;
	a[115] = 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);
}

int main()
{
	TestTopk();
	return 0;
}

5.2.3 通过文件验证TOP-K

其实用数组的方法,并不能有效地模拟,我们可以尝试用文件的方式来验证

void CreateNDate()
{
	// 造数据
	int n = 10000;
	srand((unsigned int)time(NULL));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}

	for (size_t i = 0; i < n; ++i)
	{
		int x = rand() % 1000000;
		fprintf(fin, "%d\n", x);//将随机数写进文件
	}
	fclose(fin);
}

void PrintTopK(int k)
{
	const char* file = "data.txt";
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen fail");
		return;
	}

	int* kminheap = (int*)malloc(sizeof(int) * k);
	if (kminheap == NULL)
	{
		perror("malloc fail");
		return;
	}

	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &kminheap[i]);//从文件读取数据
	}

	// 建小堆
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(kminheap, k, i);
	}

	int val = 0;
	while (!feof(fout))//feof是文件结束的标识,如果返回1,则说明文件结束
	{
		fscanf(fout, "%d", &val);//fscaf的光标闪动到原先的位置,所以会从k的位置开始读
		if (val > kminheap[0])
		{
			kminheap[0] = val;
			AdjustDown(kminheap, k, 0);
		}
	}

	for (int i = 0; i < k; i++)
	{
		printf("%d ", kminheap[i]);
	}
	printf("\n");
}
int main()//该方法实现堆的顺序打印
{
	CreateNDate();
	PrintTopK(5);
	return 0;
}

友友们上述代码有不理解的,看看博主关于文件操作里的函数介绍:

C语言:文件操作详解-CSDN博客

 不太好找,所以我们可以先注释创造数据的文件,然后再文件中修该出5个最大数,然后再执行一次函数

以上就是通过数组验证top和利用文件验证tok的方法!!

 

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

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

相关文章

ELAdmin 部署

后端部署 按需修改 application-prod.yml 例如验证码方式、登录状态到期时间等等。 修改完成后打好 Jar 包 执行完成后会生成最终可执行的 jar。JPA版本是 2.6&#xff0c;MyBatis 版本是 1.1。 启动命令 nohup java -jar eladmin-system-2.6.jar --spring.profiles.active…

探索微信小程序的奇妙世界:从入门到进阶

文章目录 一、什么是微信小程序1.1 简要介绍微信小程序的定义和特点1.2 解释小程序与传统应用程序的区别 二、小程序的基础知识2.1 微信小程序的架构2.2 微信小程序生命周期的理解2.3 探索小程序的目录结构和文件类型 三、小程序框架和组件3.1 深入了解小程序框架的核心概念和原…

【JavaEE】_JavaScript(Web API)

目录 1. DOM 1.1 DOM基本概念 1.2 DOM树 2. 选中页面元素 2.1 querySelector 2.2 querySelectorAll 3. 事件 3.1 基本概念 3.2 事件的三要素 3.3 示例 4.操作元素 4.1 获取/修改元素内容 4.2 获取/修改元素属性 4.3 获取/修改表单元素属性 4.3.1 value&#xf…

nba2k24 灌篮高手樱木花道面补

nba2k24 灌篮高手樱木花道面补 nba2k23-nba2k24通用 灌篮高手樱木花道面补 下载地址&#xff1a; https://www.changyouzuhao.cn/9539.html

linux系统配置zabbix监控agent端

目录 客户端配置 启动服务 浏览器工具设置 创建主机群组 创建主机 创建监控项 ​编辑 ​编辑 创建触发器 查看监控 客户端配置 rpm -Uvh https://repo.zabbix.com/zabbix/5.0/rhel/7/x86_64/zabbix-release-5.0-1.el7.noarch.rpm # yum clean allyum install -y zab…

【Python】Python代码的单元测试

Python代码的单元测试 单元测试的概念 定义&#xff1a;是指对软件中的最小可测试单元进行检查和验证。 作用&#xff1a;可以确保程序模块是否否和我们规范的输出&#xff0c;保证该模块经过修改后仍然是满足我们的需求。 单元测试的策略 如果要创建单元测试&#xff0c;…

2024最新APP下载单页源码 带管理后台 首发

2024最新APP下载单页源码 带管理后台 首发 新版带后台管理app应用下载页,自动识别安卓苹果下载页&#xff0c;带管理后台&#xff0c;内置带3套App下载模板带中文模板/英文模板随时切换。 下载地址2023最新APP下载单页源码 带管理后台 .zip官方版下载丨最新版下载丨绿色版下载…

重复导航到当前位置引起的。Vue Router 提供了一种机制,阻止重复导航到相同的路由路径。

代码&#xff1a; <!-- 侧边栏 --><el-col :span"12" :style"{ width: 200px }"><el-menu default-active"first" class"el-menu-vertical-demo" select"handleMenuSelect"><el-menu-item index"…

深度学习-吴恩达L1W2作业

作业1&#xff1a;吴恩达《深度学习》L1W2作业1 - Heywhale.com 作业2&#xff1a;吴恩达《深度学习》L1W2作业2 - Heywhale.com 作业1 你需要记住的内容&#xff1a; -np.exp&#xff08;x&#xff09;适用于任何np.array x并将指数函数应用于每个坐标 -sigmoid函数及其梯度…

【教学类-16-02】20240214《数字卡片1-9(正方形)华光彩云_CNKI》

背景需求&#xff1a; 前期坐过长方形A4纸的数字卡片 【教学类-16-01】20221121《数字卡片9*2》&#xff08;中班)_数字卡片pdf-CSDN博客文章浏览阅读897次。【教学类-16-01】20221121《数字卡片9*2》&#xff08;中班)_数字卡片pdfhttps://blog.csdn.net/reasonsummer/artic…

B端系统从0到1:有几步,其中需求分析要做啥?

一款B系统从无到有都经历了啥&#xff0c;而其中的需求分析又要做什么&#xff1f;贝格前端工场给老铁们做一下分析&#xff0c;文章写作不易&#xff0c;如果咱们有界面设计和前端开发需求&#xff0c;别忘了私信我呦&#xff0c;开始了。 一、B端系统从0到1都有哪些要走的步骤…

第13章 网络 Page729~733 链式任务反应

链式任务反应 当io_service对象身上没有任务的时候&#xff0c;当前正在运行的run()过程就结束了。这时再往它身上添加任务&#xff0c;程序收不到任务完成事件。 如果本次任务完成后&#xff0c;run()函数退出前再添加一项或更多任务&#xff0c;这就叫链式任务。 在asio的…

日志监控须知

在这个领域,最流行的应该是ELK. ELK可以让收集日志,检索日志更加的简单,让定位日志问题更加的高效,在也不需要挨个登录服务器,然后用一堆Linux命令去搜索日志了. ELK ( Elasticsearch Logstash Kibana ) ELK架构: 各个微服务,通过某种机制把自己的日志交给Logstash 这里的某…

455. Assign Cookies(分发饼干)

题目描述 假设你是一位很棒的家长&#xff0c;想要给你的孩子们一些小饼干。但是&#xff0c;每个孩子最多只能给一块饼干。 对每个孩子 i&#xff0c;都有一个胃口值 g[i]&#xff0c;这是能让孩子们满足胃口的饼干的最小尺寸&#xff1b;并且每块饼干 j&#xff0c;都有一个…

代码随想录算法训练营Day25|回溯算法·组合总和III,电话号码的字母组合

组合总和III 题目&#xff1a;找出所有相加之和为n的k个数的组合。组合中只允许含有1-9的正整数&#xff0c;并且每种组合中不存在重复的数字。 组合变量个数为k个&#xff0c;和为n。简单思路是使用k重循环&#xff0c;一层层找出来&#xff0c;然后把每一层的数相加&#x…

数据库被人破解,删除数据,勒索

事情是这样的&#xff0c;我买了一台服务器自己部署项目玩儿玩儿&#xff0c;我的数据库运行在3306端口&#xff0c;密码没改&#xff0c;就是默认的123456&#xff0c;诡异的事情发生了&#xff0c;用了一段时间之后&#xff0c;数据库突然连接不上了&#xff0c;我一通操作猛…

【深度学习】“智能皮肤:深度学习驱动的‘智慧之眼‘应用如何革新皮肤病诊疗未来“

在一个不久的未来世界&#xff0c;医疗科技取得了惊人的突破。一款名为“智慧之眼”的神秘应用横空出世&#xff0c;它如同科幻小说中的神器&#xff0c;能够通过摄像头扫描皮肤病变&#xff0c;并借助深度学习技术迅速得出专业级别的诊断结果。这个革新性的故事始于一场科研马…

Sentinel 流控-链路模式

链路模式 A B C 三个服务 A 调用 C B 调用 C C 设置流控 ->链路模式 -> 入口资源是 A A、B 服务 package com.learning.springcloud.order.controller;import com.learning.springcloud.order.service.BaseService; import org.springframework.beans.factory.annotatio…

ClickHouse--05--MergeTree 表引擎

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 MergeTree 系列表引擎前言MergeTree 系列表引擎 --功能MergeTree 系列表引擎 --种类 1.MergeTree1.1MergeTree 建表语句&#xff1a;1.2 MergeTree 引擎表目录解析查…

【AI视野·今日Robot 机器人论文速览 第七十八期】Wed, 17 Jan 2024

AI视野今日CS.Robotics 机器人学论文速览 Wed, 17 Jan 2024 Totally 49 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Robotics Papers Safe Mission-Level Path Planning for Exploration of Lunar Shadowed Regions by a Solar-Powered Rover Authors Olivier L…