数据结构——二叉树-堆(堆的实现,堆排序,TopK问题)

目录

前言

1.树的概念及结构

1.1 树的概念

1.2 树的相关概念

1.3 树的表示

2. 二叉树的概念及结构

2.1概念

2.2 特殊的二叉树

2.3 二叉树的性质

2.4 二叉树的存储结构

3. 堆

3.1 堆的概念及结构

3.2 堆的实现

3.1.2 数据结构的设计和文件准备

3.1.2堆的初始化和销毁

3.1.3 向下调整算法与向上调整算法

3.1.4 堆的插入和堆的删除

3.1.5 获取堆顶元素和堆的数据个数,堆的判空

3.3 建堆

4. 堆的应用

4.1 堆排序

4.1.2 思路

4.2.2 代码

4.1.3 时间复杂度的分析

4.2 TopK问题

4.2.1思路

4.2.2 代码实现

总结


前言

这篇文章介绍二叉树中的特殊的数据结构堆,堆的应用有堆排序和TopK问题。有详细的图文讲解,可以边看边敲,一起学起来吧!


1.树的概念及结构

1.1 树的概念

树是一种数据结构,它是由n(n≥0)个有限节点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

  • 有一个特定的节点被称为根节点或树根(root)。
  • 除根节点之外的其余数据元素被分为个互不相交的集合,其中每一个集合本身也是一棵树,被称作原树的子树(subtree)。
  • 树是递归定义的。

1.2 树的相关概念

我们以上面这颗树为例:

节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6
叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I...等节点为叶节点
非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G...等节点为分支节点
双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点。
孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点。
兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点。
树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6。
节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;。
树的高度或深度:树中节点的最大层次; 如上图:树的高度为4。
堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点。
节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先。
子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙。
森林:由m(m>0)棵互不相交的树的集合称为森林。

1.3 树的表示

树的结构相对于之前学的线性表更复杂,存储表示起来也比较麻烦,既要保存值域,也要保存结点和结点之间的关系,实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。这里展示孩子兄弟表示法,这个方法是非常棒的一种方法。

typedef int DataType;
struct Node
{
    struct Node* firstChild1; // 第一个孩子结点
    struct Node* pNextBrother;// 指向其下一个兄弟结点
    DataType data;            // 结点中的数据域
};

2. 二叉树的概念及结构

2.1概念

一棵二叉树是结点的一个有限集合,该集合:或者为空,或者由一个根节点加上两棵别称为左子树和右子树的二叉树组成。

从上图可以看出:

  • 二叉树不存在度大于2的结点
  • 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

且对于任意的二叉树都是有下面的几种情况合成的:

2.2 特殊的二叉树

  1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是2^k-1,则它就是满二叉树。
  2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

2.3 二叉树的性质

  1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1)个结点
  2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是2^h-1
  3. 对任何一棵二叉树, 如果度为0其叶结点个数为n0 , 度为2的分支结点个数为n2 ,则有n0 =n2 +1。
  4. 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h=log2(n+1)。 (ps:是log以2为底,n+1为对数)。
  5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为i的结点有:
  • 若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点
  • 若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子
  • 若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子

2.4 二叉树的存储结构

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。

1.顺序存储

顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树

下图中,完全二叉树的春旭存储没有浪费数组空间,而非完全二叉树的存储,需要补齐一些树的空间,体现在数组中就是不存储有效值。没有填写有效值,会被系统赋值为随机值,浪费空间极大。

2.链式存储

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,现在我们学习一般的都是二叉链。

typedef int BTDataType;
// 二叉链
struct BinaryTreeNode
{
    struct BinTreeNode* _pLeft;  // 指向当前节点左孩子
    struct BinTreeNode* _pRight; // 指向当前节点右孩子
    BTDataType _data;            // 当前节点值域
}

我们下一个章节会讲解二叉链实现。

3. 堆

3.1 堆的概念及结构

堆通常是一个可以被看做一棵树的数组对象。堆的物理结构本质上是顺序存储的,是线性的。但在逻辑上不是线性的,是完全二叉树的这种逻辑储存结构。 堆的这个数据结构,里面的成员包括一维数组,数组的容量,数组元素的个数,有两个直接后继。

堆总是满足下列性质:

  • 堆中某个结点的值总是不大于或不小于其父结点的值;
  • 堆总是一棵完全二叉树。将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆。

3.2 堆的实现

在实现堆之前,需要准备三个文件,分别是Heap.h,Heap.c和test.c。

  • Heap.h文件用来存放堆的数据结构的设计和各种接口函数的声明。
  • Heap.c文件里面存放各种接口函数的实现。
  • test.c文件是用来写测试函数,检验接口函数的功能。

3.1.2 数据结构的设计和文件准备

堆的存储结构本质是数组,所以数据结构的设计跟顺序表相同,有一个数组,一个存储数据个数的值,另外一个存储数据容量大小。

typedef int HPDataType;

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

//堆的初始化
void HPInit(HP* php);
//堆的销毁
void HPDestroy(HP* php); 
//堆的插入
void HPPush(HP* php, HPDataType x);
//删除堆顶的数据
void HPPop(HP* php);
//获取堆顶数据
HPDataType HPTop(HP* php);
//堆的数据个数
int HeapSize(HP* hp);
//堆的判空
bool HPEmpty(HP* php);
//交换数据
void Swap(HPDataType* px, HPDataType* py);

3.1.2堆的初始化和销毁

堆的存储结构本质上是数组,所以初始化操作只需要把数组置空,数据个数和容量赋值为零即可。而堆的销毁只需要释放动态开辟的空间。

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

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

3.1.3 向下调整算法与向上调整算法

现在有一个数组,逻辑上看成一个完全二叉树。观察下图,根节点的左右子树都是小堆,但是根节点不满足堆的性质,不是最小的数。这个时候就可以使用向下调整算法使其变成一个小堆。

注意:只有左右子树都是小堆才能进行向下调整算法。(如果要调整大堆,左右子树就必须是大堆,如无特别声明,都是以小堆为主)

int arr[] = {27,15,19,18,28,34,65,49,25,37};

如下面所示,我们利用父亲结点和孩子结点的式子关系,让根节点和孩子节点比较,如果比孩子结点小,就交换。

  • leftchild = parent * 2 + 1
  • rightchild = parent * 2 + 2
  • parent = chlid - 1

 代码如下,先写一个交换函数,因为后面的接口函数也会复用。

void Swap(HPDataType* px, HPDataType* py)
{
	HPDataType tmp = *px;
	*px = *py;
	*py = tmp;
}

void AdjustDown(HPDataType* 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;
		}
	}
}

与向下调整算法类似,向上调整算法需要保证这个完全二叉树满足堆的性质,在尾部插入一个数据,然后向上跟父亲结点比较大小,进行交换。

void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)//parent > 0不行
	{
		if (a[child] < a[parent])//小堆
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (parent - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

3.1.4 堆的插入和堆的删除

堆的插入是在存储结构数组最后面插入一个数据,然后再使用向上调整算法,使其符合堆的性质。首先检查数据容量大小是否足够,不够就扩容。然后将x值插入到数组后面。

void HPPush(HP* php, HPDataType x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		size_t newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = realloc(php->a, sizeof(HPDataType) * newCapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		php->a = tmp;
		php->capacity = newCapacity;
	}
	
	php->a[php->size] = x;
	php->size++;

	AdjustUp(php->a, php->size - 1);
}

堆的删除一般指的是删除堆顶元素。将堆顶元素删除后,如果直接将孩子结点元素向上移动会破坏堆的结构。所以,合适的操作是先将堆顶元素与最后一个结点元素交换,再删除最后一个节点元素。对根节点进行向下调整算法,使其成为一个堆。

void HPPop(HP* php)
{
	assert(php);
	assert(php->size > 0);

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

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

3.1.5 获取堆顶元素和堆的数据个数,堆的判空

获取堆顶元素和获取堆的数据个数比较简单。堆的判空直接返回数据个数是否为零的判断。

HPDataType HPTop(HP* php)
{
	assert(php);
	return php->a[0];
}

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

bool HPEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}

3.3 建堆

建堆就是通过使用堆的插入这个函数,将一个数组的逻辑结构转变成堆,然后我们使用while循环进行堆删除的操作,对删除是删除对顶的元素。

int main2()
{
	int a[] = { 50,100,70,65,60,32 };

	HP hp;
	HPInit(&hp);
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		HPPush(&hp, a[i]);
	}

	while (!HPEmpty(&hp))
	{
		printf("%d ", HPTop(&hp));
		HPPop(&hp);
	}

	HPDestroy(&hp);
	return 0;
}

运行结果如下,我们会发现从小到大打印在控制台上,因为每次取堆顶元素,然后再进行堆的删除,堆顶元素是最小的元素,所以是升序打印。

4. 堆的应用

4.1 堆排序

4.1.2 思路

堆排序就是利用堆的性质进行排序。堆排序分为两个步骤:

1.建堆

  • 升序:建大堆
  • 降序:建小堆

2.利用堆删除的思想来排序。

  • 堆的性质是不管那个结点的值都不小于或者不大于父亲结点的值。假设我们要排升序,需要建一个大堆,那么根结点的值是最大。但要注意的是,使用向下调整算法建大堆,最后一层不用调整,要从最后一个叶子结点的父亲结点开始使用向下调整算法,一直到根结点。
  • 接着跟堆的删除操作一样先将根节点的值和尾结点的值进行交换,此时尾结点就是最大的值。然后对此时的根节点使用向下调整算法,那么根节点的值是除了开始调整到尾结点的值中的最大值。
  • 现在下面有一组数据,我们使用堆排序算法来排升序
int arr[] = { 11,8,3,5,21,15 }

4.2.2 代码

代码如下:

void Swap(HPDataType* px, HPDataType* py)
{
	HPDataType tmp = *px;
	*px = *py;
	*py = tmp;
}

void AdjustDown(HPDataType* 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;
		}
	}
}

void HeapSort(int* a, int n)
{
	//a数组向下调整算法建堆
	for (int i = (n-1-1)/2;i>=0; i--)
	{
		AdjustDown(a, n, i);
	}

	//利用堆的删除的思想,只需要交换n-1次
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

int main()
{
	int a[] = { 50,100,70,65,60,32,26,43,82,54 };
	int size = sizeof(a) / sizeof(int);

	HeapSort(a, size);

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

	return 0;
}

结果如下:

4.1.3 时间复杂度的分析

堆排序主要分为两个部分一个是建堆,然后进行交换元素进行向下调整。

  • 建堆的复杂度是O(N)
  • 向下调整的复杂度是O(logN),是以2为底数的对数。

所以时间复杂度就是O(N*logN),算是排序中比较快的算法了。

4.2 TopK问题

4.2.1思路

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。

比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。

对于这种问题,一般会想到使用排序,但是数据量非常大的情况下,排序不太可取,因为数据可能不能一下子加载到内存中。最佳的方法还是用堆来解决:

1.用数据集合中前K个元素来建堆

  • 前k个最大的元素,建小堆
  • 前k个最小的元素,建大堆

2.用剩余的N-K个元素依次与堆顶元素进行比较,不满足则替换堆顶元素

比如,要找前k个最大的元素,我们建k个元素的小堆,只要剩下的元素的值比堆顶元素大,就与堆顶元素发生交换,并使用向下调整算法,这样子在堆顶的元素一定是最小的,在堆顶的元素如果不是前k个最大元素,就会被剩下的元素中出现的前k个最大元素所替代,这样子就可以解决TopK问题了。

大家需要注意的是,当堆排序排升序的时候需要建大堆,而TopK问题找前k个最大的元素是建小堆。两者不一样。

4.2.2 代码实现

我们先要创建一个文件,并随机生成十万个数据。这里需要用到srand函数,并传入时间戳time。并写入文件中。

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

void CreateDate()
{
	int num = 100000;
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("File: fin open fail");
		exit(-1);
	}
    //时间戳的随机数
	srand(time(0));
	for (int i = 0; i < num; i++)
	{	//生成0~999999的数
		fprintf(fin, "%d\n", (rand() + i) % 1000000);
	}
	
	fclose(fin);
}

以只读的方式打开文件,然后动态开辟一个k个大小的数组。将前k个元素赋值到数组中,使用向下调整算建小堆。之后用while循环读取剩下元素,比堆顶大的元素,与其进行交换,再向下调整,直到结束。

void TopK()
{
	printf("请输入k:>");
	int k = 0;
	scanf("%d", &k);

	//打开文件
	const char* file = "data.txt";
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen fail");
		exit(-1);
	}

	//新建一个数组
	int* arr = (int*)malloc(sizeof(int) * k);
	if (arr == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	//读取k个数据到新建的堆中
	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &arr[i]);
	}

	//建小堆,向下调整
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(arr, k, i);
	}

	int x = 0;
	while (fscanf(fout, "%d", &x) != EOF)
	{
		//比较另外的N-K个数与堆顶的大小
		if (x > arr[0])
		{
			arr[0] = x;
			AdjustDown(arr, k, 0);
		}
	}

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

	fclose(fout);
}

int main()
{
	CreateDate();
	TopK();

	return 0;

 


总结

一开始的顺序表和链表适用于存储数据。当开始学习树的数据结构,会发现树的结构十分复杂,如果仅用于存储,还不如一开始学习顺序表和链表。但是树也确实有其他的重要功能,难度会陡然上升,需要前面牢固的数据结构基础。因此,更需要我们大量的练习。

创作不易,希望这篇文章能给你带来启发和帮助,如果喜欢这篇文章,请留下你的三连,你的支持的我最大的动力!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

小字辈(左子右兄加强版)

文章目录 题目描述思路AC代码总结 题目描述 输入样例1 5 1 2 0 2 4 3 3 0 0 4 0 5 5 0 0 输出样例1 3 4 5输出样例2 5 2 5 3 1 2 0 3 0 0 4 0 0 5 0 4 输出样例2 3 4 5思路 dfs 二叉树 存储结构 1.用结构体数组存储每个节点的父节点、左右节点 2.用vector存储答案 具体做法 1.…

[论文笔记] Dual-Channel Span for Aspect Sentiment Triplet Extraction

一种利用句法依赖和词性相关性信息来过滤噪声&#xff08;无关跨度&#xff09;的基于span方法。 会议EMNLP 2023作者Pan Li, Ping Li, Kai Zhang团队Southwest Petroleum University论文地址https://aclanthology.org/2023.emnlp-main.17/代码地址https://github.com/bert-ply…

升级!Sora漫步街头的女人可以跳舞啦!科目三蹦迪多种舞姿停不下来,可精准控制动作

Sora为我们展开了一个充满惊喜的新篇章&#xff0c;同时&#xff0c;Viggle这一模型也吸引了公众的目光&#xff0c;并在推特上迅速走红&#xff01; 想象一个场景&#xff0c;你仅需用几句话就能让画面活灵活现&#xff0c;无论是让角色跳舞、做后空翻&#xff0c;这些曾经只能…

LeetCode刷题【树状数组、并查集、二叉树】

目录 树状数组307. 区域和检索 - 数组可修改406. 根据身高重建队列673. 最长递增子序列的个数1409. 查询带键的排列 并查集128. 最长连续序列130. 被围绕的区域 二叉树94. 二叉树的中序遍历104. 二叉树的最大深度101. 对称二叉树543. 二叉树的直径108. 将有序数组转换为二叉搜索…

算法 之 排序算法

&#x1f389;欢迎大家观看AUGENSTERN_dc的文章(o゜▽゜)o☆✨✨ &#x1f389;感谢各位读者在百忙之中抽出时间来垂阅我的文章&#xff0c;我会尽我所能向的大家分享我的知识和经验&#x1f4d6; &#x1f389;希望我们在一篇篇的文章中能够共同进步&#xff01;&#xff01;&…

CSS(一)

一、CSS 简介 1.1 HTML 的局限性 说起 HTML&#xff0c;这其实是个非常单纯的家伙&#xff0c;他只关注内容的语义。比如 <h1> 表明这是一个大标题&#xff0c;<p> 表明这是一个段落&#xff0c;<img> 表明这儿有一个图片&#xff0c;<a> 表示此处有链…

PCB产业渐出谷底,超颖电子能否找到发展确定性?

经历了三年多低迷期&#xff0c;消费电子在2024年终于以企稳回升的姿态逐步回暖。IDC预期&#xff0c;2024年&#xff0c;智能手机、PC、服务器等关键领域的出货量或迎来修复性成长。 这也将带动“电子产品之母”印刷电路板&#xff08;Printed Circuit Board&#xff0c;PCB&…

Python之装饰器-无参装饰器

Python之装饰器-无参装饰器 装饰器介绍 1. 为何要用装饰器 Python 中的装饰器是一种语法糖&#xff0c;可以在运行时&#xff0c;动态的给函数或类添加功能。装饰器本质上是一个函数&#xff0c;使用 函数名就是可实现绑定给函数的第二个功能 。将一些通用的、特定函数的功…

InstructGPT的流程介绍

1. Step1&#xff1a;SFT&#xff0c;Supervised Fine-Tuning&#xff0c;有监督微调。顾名思义&#xff0c;它是在有监督&#xff08;有标注&#xff09;数据上微调训练得到的。这里的监督数据其实就是输入Prompt&#xff0c;输出相应的回复&#xff0c;只不过这里的回复是人工…

python5:基于多进程的并发编程、基于协程的并发编程的学习笔记

进程 为什么要使用多进程&#xff1f;——GIL的存在&#xff0c;多线程实际不是并发执行 将任务分为两类&#xff1a;IO密集型&#xff08;多线程&#xff09;CPU密集型&#xff08;多进程&#xff09; 多进程的基本用法 concurrent.futures.process.ProcessPoolExecutor#进…

李清照想老公了,人比黄花瘦

年轻时的风光无限&#xff0c;中年时的颠沛流离&#xff0c;晚年时的孤独寂寞&#xff0c;李清照的一生跌宕起伏&#xff0c;十分凄惨。 北宋的国破家亡&#xff0c;让李清照从活泼可爱的少女成为孤独的老妇人。 一、少女情思 少女时期&#xff0c;李清照有一次去游玩&#…

【Linux】误删除/home家目录怎么办? -- 此时ssh连接登录的就是此普通用户

&#x1f468;‍&#x1f393;博主简介 &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01; &#x1f40b; 希望大家多多支…

Windows系统安装VNC客户端结合内网穿透实现公网远程连接Deepin桌面

文章目录 1. 安装x11vnc2. 本地远程连接测试3. Deepin安装Cpolar4. 配置公网远程地址5. 公网远程连接Deepin桌面6. 固定连接公网地址7. 固定公网地址连接测试 x11vnc是一种在Linux系统中实现远程桌面控制的工具&#xff0c;它的原理是通过X Window系统的协议来实现远程桌面的展…

AJAX-综合

文章目录 同步代码和异步代码回调函数地狱解决回调函数地狱Promise-链式调用async函数和awaitasync函数和await-捕获错误 事件循环宏任务与微任务Promise.all静态方法 同步代码和异步代码 同步代码&#xff1a;逐步执行&#xff0c;需原地等待结果后&#xff0c;才继续向下执行…

深入探索C语言动态内存分配:释放你的程序潜力

&#x1f308;大家好&#xff01;我是Kevin&#xff0c;蠢蠢大一幼崽&#xff0c;很高兴你们可以来阅读我的博客&#xff01; &#x1f31f;我热衷于分享&#x1f58a;学习经验&#xff0c;&#x1f3eb;多彩生活&#xff0c;精彩足球赛事⚽ &#x1f31f;感谢大家的支持&#…

【Java程序设计】【C00341】基于Springboot的药品管理系统(有论文)

基于Springboot的药品管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 项目获取 &#x1f345;文末点击卡片获取源码&#x1f345; 开发环境 运行环境&#xff1a;推荐jdk1.8&#xff1b; 开发工具&#xff1a;eclipse以及idea&…

力扣刷题31-33(力扣 0024/0070/0053)

今日题目&#xff1a; 24. 两两交换链表中的节点 题目&#xff1a;给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09; 思路&…

BOM 浏览器对象模型

文章目录 1. BOM 概述2.window 对象的常见事件窗口加载事件调整窗口大小事件 3.定时器setTimeout() 定时器案例&#xff1a;5秒后自动关闭的广告停止 setTimeout() 定时器setInterval() 定时器案例&#xff1a;倒计时停止 setInterval() 定时器案例&#xff1a;发送短信this 4.…

亚马逊云科技:企业如何开启生成式AI之旅?

如果要评选最近两年全球科技行业最热门的细分领域&#xff0c;那么生成式AI绝对会以遥遥领先的票数成为当仁不让的冠军。 然而眼见生成式AI发展得如火如荼&#xff0c;越来越多的企业却陷入了深深的焦虑&#xff1a;应该如何开启生成式AI之旅&#xff1f;又该怎样搭建大模型&am…