C语言数据结构基础笔记——树、二叉树简介

news2024/11/19 2:25:20

1.树

树是一种 非线性 的数据结构,它是由 n n>=0 )个有限结点组成一个具有层次关系的集合。 把它叫做树是因 为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
       
                                                    (图片来源于网络)

任何一棵树,都应该被观察成根和子树。

     什么是根和子树呢?

我们利用人与人之间的关系来形象化了解 树 这种数据结构:

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

         “子树都是递归定义的”,所以所有的子树都可以以相同的方法拆解、观察:

                                                     

          子树之间应当绝对独立,否则有环的话就是另一种数据结构——图


2.尝试与选型

                                           没有规定一棵树有多少个孩子

     因此,一棵树中的任意节点到底需要存多少个指针对于我们来说是未知的。                              

如果使用定长数组,势必会造成浪费(有的节点的度很大,有的很小) 

                                           

也可以采用每个树节点中都放顺序表的方法,顺序表作为指针数组存放每一个子树地址(因为是指针数组的指针,所以subA应该为结构体二级指针)。


下面来了解最常用的树的表示法。 

2.1左孩子右兄弟表示法
struct TreeNode{
    int val;
    struct TreeNode* leftchild;
    struct TreeNode* nextbrother;
}

但是请注意,“亲兄弟”才算“兄弟”。

对于树

                          

其中的指针指向就是

         

     如果没有左儿子与右兄弟了,我们便让这个结构的相对应的指针指向空。

若一个父亲想找所有的儿子,就可以:

struct TreeNode* cur;
cur=A->leftchild;
while(cur){
  //...................
  cur=cur->next;
}

2.2双亲表示法:

也就是用下标和深度来实现。

                         

(之后的并查集也是这样使用 )

每一个节点存放数据和父亲所在的下标,类似于KMP匹配算法的感觉。A没有父亲,所以存放不存在的下标-1,B、C存放父亲(A)的下标0,以此类推

(尽管物理结构是数组,其实逻辑结构是森林)

3.二叉树

简单来说,每个节点最多两个孩子的树,就是二叉树(但不是一定有两个孩子,也可以作为叶子或者只有一个儿子),也叫BinaryTree、B树等。

3.1搜索二叉树

B树单纯的用来存储数据意义不大,并不如链表和顺序表。其结构存在意义之一如下图的“搜索二叉树”

    搜索二叉树同时具有存储数据和插入数据的功能

  并且最多查找当前树高度次就能找到需要的数据:

因为搜索二叉树满足左子树<根<右子树(左子>树根>右子树同理),比方要搜索数据34,先与48比较,34小于48所以找左儿子25(后文中叫做leftchild或者左子树),34大于25所以找右儿子,成功匹配。每一次搜索都是向下走一层,所以最多搜索树的高度次就能到叶子的位置,效率极高。若要查找的是35,我们按照规则向下一直走到叶子的位置都未能找到,说明该树中不包含35,匹配失败

3.2完全二叉树 

先来了解完全二叉树的特殊形式:满二叉树

一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是
说,如果一个二叉树的层数为 K ,且结点总数是2^{k}-1 ,则它就是满二叉树。

也就是说,满二叉树必须所有非叶节点都有两个后代。

我们再了解一下二叉树的编号命名规则:根为0号,每一辈都从左到右(第一个leftchild 第一个rightchild 第二个leftchild 第二个rightchild)依次编号,编为一辈,再编下一辈。

                       


完全二叉树 :完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树引出来的。对于深度为 K 的,有n 个结点的二叉树,当且仅当其每一个结点都与深度为 K 的满二叉树中编号从 1 n 的节点一一对应时,称之为完全二叉树。 要注意的是 满二叉树是一种特殊的完全二叉树
换句话说,只有最后一排可以是双叶子不满(单叶子),并且存在的叶子必须是在编号上连续的。
                           

   完全二叉树的部分规律

规律1:有两个儿子的节点个数+1=叶子结点

也就是对于首项为1,公比为2的等比数列中,前n-1项之和加一等于第n项。根据满二叉树可得,an就是叶子结点数,s(n-1)就是有两个儿子的节点数。

我们再以此推广到完全二叉树:每一个正常结点变成一个单儿子节点,就会损失相同个数的叶子节点和有两个儿子的节点(包括他自己),所以在“损失”的累积后,等式依然成立。

a_{n}=s_{n-1}+1              

规律2:

由于右孩子都是偶数,所以-1之后除以二,可以消除小数点的影响。如对于E和D,

(4-1)/2=1(1.5),(3-1)/2=1,D和E就都能找到父亲了。

想利用数组找回二叉树也很容易,按照数量1 2 4 8....的顺序拿数据拼接即可

                                                     


那能否利用数组存储其他类型的树呢?

先说结论,数组只适合满二叉树和完全二叉树 ,否则就如下图,会造成大量空间的浪费

            

其余普通的更适合二叉链

(根为第一层,不从第零层开始计数,但是编号是根一般编为0号,便于和数组下标对接,这一点很容易混淆,望注意)


4.堆(Heap)

4.1堆概念

(请勿将数据结构中的堆与操作系统中的堆概念混淆,仅仅是名字相同而已)

堆必须是一个完全二叉树,分为大堆和小堆(也叫大根堆和小根堆)。

小堆(任何一个父亲<=孩子)

大堆(任何一个父亲>=孩子)

小根堆可用于找最小值,大根堆可用于找最大值

但注意:没有规定兄弟之间的大小关系(也就是叔侄之间没有确定的大小关系),当然,爷孙之间肯定也有大小关系

“只有搜索二叉树规定了左右兄弟的大小

4.2实现堆的基本结构和接口

基于上文,我们用顺序表为物理结构,构建堆(以小堆为例,根为最小)

typedef int HeapDataType;
typedef struct Heap {
	HeapDataType* arr;
	int size;
	int capacity;
}HP;
4.2.1 HPPush

不管是对于一个空堆,还是已经形成的堆,新插入的数据第一步只需要很简单的先加入顺序表。尾差在数组尾巴,空间不够(size==capacity)就扩容

但是假若插入的数据较小,已经大于他此时的父辈了呢?

利用向上调整算法:插入数据后和父亲比,比父亲小就和父亲交换。

     

先进入数组,从child找到parent的下标(由于child是大于等于0的整数,所以parent一定大于等于0),若child更下,就封装一个交换函数,交换child和parent的值,再找到新的child和parent.

如果child已经交换到下标为0的位置了,child已经和parent一起作为根了(并不影响,已达成我们的目的),可以跳出循环了。

实现如下

void Swap(HeapDataType* p1, HeapDataType* p2) {
	assert(p1 && p2);
	HeapDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void AdJustUp(HeapDataType* arr, int child) {
	int parent = (child - 1) / 2;
	while (child>0) {
		if (arr[child] < arr[parent]) {
			Swap(&arr[child], &arr[parent]);
			child = parent;
			parent = (parent - 1) / 2;
		}
		else {
			break;
		}
	}

}

void HPPush(HP* php, HeapDataType x) {
	assert(php);
	//不够就扩容
	if (php->capacity == php->size) {
		int newcapacity = php->size == 0 ? 4 : 2 * php->capacity;
		HeapDataType* tmp = (HeapDataType*)realloc(php->arr, newcapacity * sizeof(HeapDataType));
		if (tmp == NULL) {
			perror("realloc fail");
			exit(1);
		}
		php->arr = tmp;
		php->capacity = newcapacity;
	}
	php->arr[php->size] = x;
	php->size++;
	//向上调整算法
	AdJustUp(php->arr, php->size - 1);
}

(整个树的形状与结构应该一直在你的脑海中)

我们对push操作进行测试:

     

现在,只要经过Push操作进堆,所有数据都满足小堆的排列规则。

一些其他用例:

                   

将一个数组使用堆插入后,他都会直接变成期望的堆————原因就在于插入时有向上调整算法,每插入一个就会根据大小而改变位置


4.2.2HeapTop与HeapPop

HeapTop ——取堆顶,也就是根,小堆就是取最小值,大堆就是取最大值

直接返回arr[0]即可。

HeapPop——规定,删除是删除堆顶的数据

如果直接将下标为1的数据往前挪动,能行吗?

显然不能通过。

于是,计算机的先辈们发明了这样一种方法:

1.首先交换首位数据arr[size-1]和arr[0]

2.再删除尾数据

3.最后进行向下调整算法(让不合规的根走到叶子的位置去)

                              

   向下调整算法:

任意两个亲兄弟,右孩子的下标都是左孩子+1(没有其他特殊情况,+1不可能到下一辈,因为没有右孩子的位置一定是末位置)

不论是往左还是往右,我们一直在寻找更小的那一个数。所以我们利用假设法,找到左右孩子中更小的那个,进行交换操作,并且迭代。

     

每一轮结束后都将child走到新的左孩子的位置,并且由于完全二叉树的定义,不存在child+1就走到其他亲戚处的可能性(唯一的可能性是退化成下图中的树形,但是此时child+1必定是大于child的),更何况这不是一个完全二叉树。

                                         

代码实现:

void AdjustDown(HP* php) {//建议还是将参数设置为上图的参数,便于复用
	int parent = 0;
	int child = parent * 2 + 1;
	//int smaller = leftchild;
	//开始假设法找小
	while (child<php->size) {
         //smaller = leftchild;
	if (php->arr[child] > php->arr[child+1]) {
		child ++;
	}
	//已经找到小,向下调整
	if (php->arr[parent] > php->arr[child]) {
		Swap(&php->arr[parent], &php->arr[child]);
		parent = child;
		child = parent * 2 + 1;
	}
	else {
		break;
	}
      }
}

	
void HPPop(HP* php) {
	assert(php);
	Swap(&php->arr[0], &php->arr[php->size - 1]);
	php->size--;
	//向下调整算法
	AdjustDown(php);
}

我们先打印出堆顶数据(此时的堆结构如前文中push后的结构,再pop掉最小的14) 

新的、由6个数据构成的新的小堆就完成了。

 4.2.3HeapEmpty

直接返回size是否等于0即可

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

(进行测试功能是可以直接复制粘贴,只要完成目标就行,此时不太需要考虑精简代码 )

到此为止,希望认真学明白的同学建议先独立实现以上接口,再接着阅读

4.3 效率分析与优化(重点)
1.堆的层数和数量关系

   回头看我们的堆,完全二叉树都有如下规律:

        

(满二叉树是完全二叉树的特殊形式)

                                              

对于任意h层的完全二叉树,其节点个数就在2^(h-1)~2^h - 1之间。现在我们知道,在之后的时间复杂度计算中,h大概的数值在logN左右(默认省略了底数2)。

2.前文操作的时间复杂度

push和pop的时间复杂度都是O(logn),远远优于O(n)(刚刚的移动数组就是O(n))

                                 

而push、pop的建堆的时间复杂度就是AdJustUp 和 AdJustDown的时间复杂度 。

我们虽然实现了堆的各个接口,但是我们前文中想要push操作就只能一次一次写,而真实的运用情况应该是给了你很多数据(比方给了你一个数组),你希望将这些数据用堆的方式来管理,这个时候如果依然按照前文中 ,先初始化一个堆,再利用循环一次一次Push(共循环n次),其时间复杂度是O(n*logn)(n次logn的操作),因此我们尝试写一个新的初始化函数,能用堆处理交给我们的数组。

void HPInitArry(HP* php, HeapDataType* a, int n) {
	assert(php);
	//a数组就是用户给我们的数据
	php->arr = (HeapDataType*)malloc(sizeof(HeapDataType) * n);
	if (php->arr = NULL) {
		perror("malloc fail!");
		exit(1);
	}
	memcpy(php->arr, a, n * sizeof(HeapDataType));
	php->capacity = php->size = n;
	//至此,我们开辟了一个新的(希望实现成堆的)空间,并且把a数组的内容拷贝了进来。
	//接下来想办法将这些拷贝进来的数据实现成堆

}
3.向上调整算法和向下调整算法用于建堆

先分析逻辑:

       回忆向上调整的逻辑,“我们在本来建立好的堆的末尾(对应的物理结构——数组的末尾)加上新的数据作为新的叶子(child),再依靠公式parent=(child-1)/2  , 来找新叶子的父亲,比较是否需要调整,以此类推,最坏情况可能需要调整到根部  ”

      那么,对于我们已经建立好的但是没有符合数据规律的堆,我们先跳过根部(第1层),

从第二层的第一个元素(数组下标为1)开始,每个元素都进行一次向上调整。这样,每次向上调整之前,要调整的数据之上的数据都是已经调整好的、满足条件的堆的排布。

晃眼一看,每次AdJustUp的复杂度为log n,一共调整(n-1)次,那时间复杂度不就是O(n* logn)吗?

                          

                                           “华生,你没有发现忙点 ”

错误的,只有在最后一层才调整logn, 其余时候,在第几层就调整几次,,,,,

所以整体的调整次数应该是:

                 

(在第h层,其调整次数就是h-1,但是每一层需要调整的数量都是上一层的两倍)

     这是一个高中经典的等差+等比数列求和问题,解法大致为让F(h)左端乘以公比为二式,再让原式与二式作差即可,此处不多赘述。结果如下:

                                         

    我们将h按照N最大的情况进行换算可得,

                                           需要的总次数=

       所以时间复杂度也就是O(nlogn).

我把这种方法称之为“向下遍历的向上调整算法

void HPInitArry(HP* php, HeapDataType* a, int n) {
	assert(php);
	//a数组就是用户给我们的数据
	php->arr = (HeapDataType*)malloc(sizeof(HeapDataType) * n);
	if (php->arr = NULL) {
		perror("malloc fail!");
		exit(1);
	}
	memcpy(php->arr, a, n * sizeof(HeapDataType));
	php->capacity = php->size = n;
	//至此,我们开辟了一个新的(希望实现成堆的)空间,并且把a数组的内容拷贝了进来。
	//接下来想办法将这些拷贝进来的数据实现成堆
	//1.先使用向下遍历的向上调整算法
	for (int i = 1; i < php->size; i++) {
		AdJustUp(php->arr, i);
	}
}

向上遍历的向下调整

同理可得:从倒数第二层开始,每一层都向下调一次,调整完一个数据就--,继续调整上一个数据。

 

//2.使用向上遍历的向下调整算法
for (int i = (php->size - 1 - 1) / 2; i > 0; i--) {
	AdjustDown(php->arr, php->size,i);
}

ps:php->size-1是最后一个元素的下标,也就是child,(child-1)/2才是我们希望找到的倒数第二排的父亲。 

同样的计算方法,最后计算出来答案是O(n). 

为什么在建堆时,“向上遍历的向下调整”优于“向下增加的向上调整”呢?

因为向下调整直接跳过了个数几乎占一半的叶子辈分,需要排序的数据量几乎少一半

而向上调整只跳过了个数为1的根辈分,基本上等于没有跳过。


4.4堆排序简介 

我们现在已经将客户给的数组数据进行堆化了,接下来我们依靠堆化的数据实现经典的堆排序。

    假设我们此时希望升序排列,那就应当使用小根堆,然后依次top再pop.

但是,当我们花了那么多心思写完了整个堆,再top和pop,此时的复杂度为前面的造轮子O(n)+O(n),取为O(n),这样的整体代码成本是不是太高了?     

     综上所述,与其使用向上调整建堆,还不如使用快速排序(也是nlogn)直接对数组进行排序。

          此时我们构建整个堆没有任何时间优势。

那么,能否不造轮子呢???

     直接对客户给我们的数组a使用向上遍历的向下调整算法,获得数组{0,3,1,4,6,9,2,7,5,8}

假设我们希望获得升序的排列,那么应该将调整算法改为大根堆还是小根堆呢?

按照上面的思路,为了从小到大一个一个拿出我们希望的数据,应该是使用小根堆,来康康效果:

向上向下调整的基础都是:“前进”的方向本身已经形成了符合规定的堆

如上图,建小根堆之后,我们拿出最小的数据,剩下的数据关系一乱,就不能使用向下调整,只能重新建堆,时间成本就高了(应该在O(n^{3}))。而重新建堆之后,每建一次堆,都只能选出一个最小的数,比冒泡排序还慢。

接下来就是变魔术的时刻了,我们聪明的前辈们想出了这样一个方法:

    升序用大堆,降序用小堆。(不用实现轮子,但是得实现向下调整算法)

以升序为例:

我们先造出大根堆{9 8 3 7 8 1 2 5 4 0},根(也就是9)一定是最大的数字,让其与较小的0互换位置,并且size--,让9留在数组中但是不再对9进行任何调整的处理,让它置身于事外。再让0做向下调整算法,让除9之外次大的数字坐到根的位置上。

其本质与HPPop是一个道理,只是让希望删除的数据留在数组末尾。

由于前文的实现都是小根堆,所以我们此处实现降序:

     

创建堆的时间复杂度是O(N),类似于pop的堆排序算法的时间复杂度是:

               

也就是 

 

约等于nlogn .

至此,我们的堆排序算法简介就到此。

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

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

相关文章

【Unity】persistentDataPath、streamingAssetsPath和dataPath

介绍 我们在用Unity进行开发时&#xff0c;资源路径是我们最常用到的&#xff0c;下面我就来简单介绍一下几种常用的路径。 1.dataPath dataPath是包含游戏数据文件夹的路径&#xff0c;是app程序包安装路径 Windows: xxx /Assets &#xff08;如下图&#xff09; Mac: xxx…

ideaSSM失物招领管理系统网页模式开发mysql数据库web结构java编程计算机网页源码maven项目

一、源码特点 idea ssm 失物招领管理系统是一套完善的完整信息管理系统&#xff0c;结合SSM框架完成本系统SpringMVC spring mybatis &#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数…

计算机网络笔记(湖科大教书匠版本)

目录 第一章、 ①三种交换方式 ②计算机网络的定义和分类 ③计算机网络的性能指标 1.速率 2.带宽 3.吞吐量 4.时延 5.时延带宽积 6.往返时间 7.利用率 8.丢包率 ④常见的三种计算机网络体系结构​编辑 ⑤计算机网络体系结构分层的必要性​编辑 第二章、物理层 ①…

【Java】常用类和基础API

文章目录 一、String的特性二、String的内存结构2.1 拼接2.2 new 三、String的常用API-13.1 构造器 四、String的常用API-24.1 常用方法4.2 查找4.3 字符串截取4.4 和字符/字符数组相关4.5 开头与结尾4.6 替换 五、StringBuffer、StringBuilder5.1 StringBuilder、StringBuffer…

应对磁盘管理挑战:Linux磁盘分区挂载命令实践指南

前言 在今天的技术世界中&#xff0c;Linux已成为广泛使用的操作系统之一&#xff0c;而对于运维人员和开发人员来说&#xff0c;磁盘分区挂载是一个至关重要的任务。正确地管理和配置磁盘分区挂载可以极大地提升系统的性能和可靠性&#xff0c;同时也能确保数据的安全性。 通…

【数据结构与算法】:选择排序与快速排序

&#x1f525;个人主页&#xff1a; Quitecoder &#x1f525;专栏&#xff1a;数据结构与算法 我的博客即将同步至腾讯云开发者社区&#xff0c;邀请大家一同入驻&#xff1a;腾讯云 欢迎来到排序的第二个部分&#xff1a;选择排序与快速排序&#xff01; 目录 1.选择排序1.…

电子科技大学链时代工作室招新题C语言部分---题号E

1. 题目 这道题大概的意思是说&#xff0c;一座城市中被埋了许多雷&#xff08;用一个只含0和1的字符串表示城市&#xff0c;1代表有雷&#xff0c;0代表无雷&#xff09;。 你作为一个排雷兵&#xff0c;需要花最少的钱引爆所有的雷来使城市中不再有雷&#xff08;太逆天了&a…

分布式文件存储与数据缓存(一)| FastDFS

目录 分布式文件系统FastDFS概述_简介FastDFS特性&#xff1a;分布式文件服务提供商 FastDFS概述_核心概念trackerstorageclientgroup FastDFS概述_上传机制内部机制如下 FastDFS概述_下载机制内部机制如下 FastDFS环境搭建_Linux下载安装gcc下载安装FastDFS下载安装FastDFS依赖…

Transformer代码从零解读【Pytorch官方版本】

文章目录 1、Transformer大致有3大应用2、Transformer的整体结构图3、如何处理batch-size句子长度不一致问题4、MultiHeadAttention&#xff08;多头注意力机制&#xff09;5、前馈神经网络6、Encoder中的输入masked7、完整代码补充知识&#xff1a; 1、Transformer大致有3大应…

由浅到深认识C语言(6):变量的存储类型

该文章Github地址&#xff1a;https://github.com/AntonyCheng/c-notes 在此介绍一下作者开源的SpringBoot项目初始化模板&#xff08;Github仓库地址&#xff1a;https://github.com/AntonyCheng/spring-boot-init-template & CSDN文章地址&#xff1a;https://blog.csdn…

代码随想录算法训练营第40天 | 343. 整数拆分 ,96.不同的二叉搜索树

动态规划章节理论基础&#xff1a; https://programmercarl.com/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html 343. 整数拆分 题目链接&#xff1a;https://leetcode.cn/problems/integer-break/ 思路&#xff1a; 动规五部曲&#xf…

【复现】通天星CMS 安全监控云平台 SQL注入漏洞_64

目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一&#xff1a; 四.修复建议&#xff1a; 五. 搜索语法&#xff1a; 六.免责声明 一.概述 通天星CMSV6拥有以位置服务、无线3G/4G视频传输、云存储服务为核心的研发团队&#xff0c;专注于为定位、无线视频终端产品提供平…

sqllab第三关通关笔记

知识点&#xff1a; 通过回显的信息判断原始语句的组成猜测该语句为 select 1,2,3 from 表名 where id (输入) limit 0,1 首先通过测试判断存在什么类型的sql注入 构造id1/0 发现正常输出&#xff1b;说明是字符型的sql注入 好了&#xff0c;下面就测试有什么限制条件 构造…

论文阅读——MoCo

Momentum Contrast for Unsupervised Visual Representation Learning 动量在数学上理解为加权移动平均&#xff1a; yt-1是上一时刻输出&#xff0c;xt是当前时刻输入&#xff0c;m是动量&#xff0c;不想让当前时刻输出只依赖于当前时刻的输入&#xff0c;m很大时&#xff0…

使用Laravel开发项目

如何使用Laravel框架开发项目 一、安装Laravel框架 1.在安装Laravel框架钱我们需要先查看要安装的Laravel框架版本以及版本所需要的安装运行条件。 2.配置好安装环境后再安装Laravel框架 2.1.配置安装环境 1&#xff09;PHP版本 2&#xff09;PHP OpenSSL扩展 3&#xff…

Ubuntu查看ros版本-linux查看ros版本

使用ros带的rosversion命令即可查看自己的ros版本&#xff1a; rosversion -d

C语言葵花宝典之——文件操作

前言&#xff1a; 在之前的学习中&#xff0c;我们所写的C语言程序总是在运行结束之后&#xff0c;就会自动销毁&#xff0c;那如果我们想将一个结果进行长期存储应该如何操作呢&#xff1f;这时候就需要我们用文件来操作。 目录 1、什么是文件&#xff1f; 1.1 程序文件 1.2…

C语言 之 结构体超详细总结

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;魔法指针&#xff0c;进阶C&#xff0c;C语言&#xff0c;C语言题集&#xff0c;C语言实现游戏&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 坚持创作博文(平均质量分78.8)&#xff0c;分…

docker容器镜像管理

目录 一、 Docker的基本组成 二、 容器和镜像的关系 2.1 面向对象角度 2.2 从镜像容器角度 三、镜像命令 3.1 查看当前已有镜像 3.2 查看已有的全部镜像 3.3 查看镜像ID 3.4 镜像删除 四、 容器命令 4.1 下载镜像 4.2 新建和启动容器 run 4.3 交互式 4.…

【体验有奖】用 AI 画春天,函数计算搭建 Stable Diffusion WebUI

人工智能生成内容 AIGC&#xff08;Artificial Intelligence Generated Content&#xff09;是当下备受关注的概念之一&#xff0c;是继 PGC 和 UGC 之后的新型生产方式。AIGC 技术的核心思想是利用人工智能算法生成具有一定创意和质量的内容。例如&#xff0c;根据用户的描述或…