排序(四)——归并排序 + 外排序

目录

1.归并排序递归实现

代码

2.归并排序非递归

代码

3.比较快排、归并和堆排序

4.归并排序实现外排序


1.归并排序递归实现

我们之前对两个有序数组进行排序就用到了归并的思想,对于两个有序数组,我们分别取他们首元素比较大小,取小的插入到一个新开辟的数组中,

归并排序的前提就是要有两个有序序列,而且还要开辟一块新的空间来存放新的有序的数组。

那么对于一个无序的数组该如何使用归并排序呢

这一整个数组我们可以把它分成左右两个区间来看,一个是 [0,4] ,一个是 [5,9] ,而只要这两个区间有序了,我们就有办法通过控制下标来进行归并排序。

怎么使两个子区间有序呢?这就又回到了二叉树的结构,这不就是相当于二叉树的后序遍历吗?我们要先使两个字区间有序,然后再对这两个字区间进行归并排序。

递推到最后的子问题就是 只有一个数据时(begin==end),

递推到最后就开始返回了,

代码

最终左右区间都有序之后,再归并就能够将整个数组都排成有序了。我们首先将归并的逻辑写出来,当一个序列左右区间都有序了之后我们就要进行归并,合成一个新的有序序列。

归并逻辑
void _MergePartSort(int* a, int begin, int end,int*tmp)
{int mid = begin + (end - begin) / 2;int begin1 = begin;int end1 = mid;int begin2 = mid + 1;int end2 = end;int index = begin;//将两个有序区间归并到一起while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[index++] = a[begin1++];}else{tmp[index++] = a[begin2++];}}//剩余的数据放进去while (begin1 <= end1)tmp[index++] = a[begin1++];while (begin2 <= end2)tmp[index++] = a[begin2++];//最后将新序列拷贝回原数组memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}

归并的逻辑我们写完了之后,就要考虑如何实现后序遍历了,后序遍历无非就是先递归排序子区间,最后将这两个子区间归并成一个有序的序列

//归并排序实现
void _MergeSort(int* a, int begin, int end, int* tmp)
{if (begin == end)return;//先处理子区间int mid = begin + (end - begin) / 2;_MergeSort(a, begin, mid, tmp);_MergeSort(a, mid+1, end, tmp);//归并_MergePartSort(a, begin, end,tmp);
}

最后,我们的归并排序的主函数中还创建了一个动态开辟数组,我们要记得释放掉。为什么我们要单独拿一层逻辑逻辑来创建一个数组呢?我们每一次归并都需要用到一个额外的数组,与其每一次都开辟一次,不如直接开辟一个完整的能够对所有数据归并临时存放的数组,这样更好管理,方便释放。

//归并排序 创建新数组
void MergeSort(int* a, int begin, int end)
{assert(a);int* tmp = (int*)malloc(sizeof(int) * (end - begin+1));assert(tmp);_MergeSort(a, begin, end,tmp);free(tmp);
}

归并排序不会像快排的递归那样可能会栈溢出,归并排序由于每一次区间都是均分的,最多调用logN层栈帧,而他还创建了一个额外的数组来归并数据,所以他的总的空间复杂度就是O(N)。

2.归并排序非递归

归并排序的递归实现其实不难,难的是他的非递归实现。对于归并排序,我们能使用栈或者队列来实现非递归吗?栈和队列实现起来十分的复杂,后序遍历不适合栈和队列的特点,那么我们就得想其他的的办法来解决了。

其实,对于归并排序,我们只需要两层循环就能够实现他的非递归。首先数组的每一个数都可以看成是有序的,那么我们是不是可以直接对相邻的两个数进行递归呢?这样一来每两个数为一组就都是有序的了。

这样就成了 n/2 个有序序列,然后对这些数据 相邻两组 进行归并

 这时候我们就发现了一个要注意的问题,就是在分组的时候,有可能并不是偶数组,可能是奇数组或者有些组可能数据并不满 ,比如当原数组是十一个元素时,在两两归并之后最后一组只有一个数据,我们在用代码实现时要考虑到这些点。

我们要怎么控制这些分组呢?我们知道,当数据总个数为n时,第一次分组都是 一个数与一个数进行归并,而第二次就变成了 两个数的有序序列 与 两个数的有序序列进行归并 ,然后就是 四个数的有序序列和四个数的有序序列进行归并,以此类推,最后一次 就是 n/2 个数据的有序序列和 n/2个数据的有序序列进行归并,这是在每一次都是偶数个组的时候,也就是数据总数为 2 的次方。

首先我们假设是 gap 个有序数与 gap 个有序数进行归并,先把单趟的归并逻辑理清楚。非递归的实现也要依赖于一个额外的数组。

	//gap个数为一组,两组进行归并int begin1 = ;//第一组的开始int end1 = begin1 + gap-1;int begin2 = begin1 + gap;int end2 = begin1 + 2 * gap - 1;
	int i = 0;for (i = 0; i <= end; i += 2 * gap){//gap个数为一组,两组进行归并int begin1 = i;//第一组的开始int end1 = begin1 + gap - 1;int begin2 = begin1 + gap;int end2 = begin1 + 2 * gap - 1;int index = begin1;//一次归并while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2])tmp[index++] = a[begin1++];elsetmp[index++] = a[begin2++];}while (begin1 <= end1){tmp[index++] = a[begin1++];}while (begin2 <= end2){tmp[index++] = a[begin2++];}}

这边是一轮归并的逻辑,而我们除了归并之外,还需要拷贝数据回到原数组,我们要如何拷贝呢?

可以一次将一轮归并之后的tmp数组全部拷贝到原数组,也可以每一次两两归并就拷贝回去。我们先用一次性拷贝。 然后我们要开始处理边界的问题了,也就是前面说的数据不是完整的偶数组。

首先第一种情况就是,最后的 end1刚好是 end,而 beign2 和 end2 越界了,这种情况怎么处理呢?我们直接将 begin1 到end1 的数据写到tmp就行了

 

而第二种情况就是,最后递归的 begin1合法,而end1 就已经越界了,更不用谈begin2和end2了,这时候我们则是将 beign1 到 n-1 的数据写到tmp中。

第一种和第二种情况的处理是一样的

		if (end1 >= end)//第一组就越界了或者第一组的 end1 就是数组尾{while (begin1 <= end){tmp[begin1] = a[begin1];begin1++;}break;}

第三种越界就是:第二组数据部分越界,也就是 begin2 <=end,而end2>end,这时候这两组还要进行归并。

这时候我们只需要把end2 修改为 end 就行了。

这样一来我们就能写出第一轮的相邻两个数的的归并的代码

//非递归归并
void MergeSortNonR(int* a, int begin, int end)
{int* tmp = (int*)malloc(sizeof(int)*(end - begin + 1));assert(tmp);int gap=1;int i = 0;for (i = 0; i <= end; i += 2 * gap){//gap个数为一组,两组进行归并int begin1 = i;//第一组的开始int end1 = begin1 + gap - 1;if (end1 >= end)//第一组就越界了或者第一组的 end1 就是数组尾{while (begin1 <= end){tmp[begin1] = a[begin1];begin1++;}break;}int begin2 = begin1 + gap;int end2 = begin1 + 2 * gap - 1;if (end2 > end){end2 = end;}int index = begin1;//一次归并while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2])tmp[index++] = a[begin1++];elsetmp[index++] = a[begin2++];}while (begin1 <= end1){tmp[index++] = a[begin1++];}while (begin2 <= end2){tmp[index++] = a[begin2++];}}memcpy(a, tmp, sizeof(int) * (end-begin+1));}

这一层逻辑写完就简单了,我们在写一层循环来控制 gap 就行了,gap每一次都是上次的二倍,而因为数据个数可能不是 2 的次方个,所以最后一次的 gap < n 。而数据个数 n 我们用传过来的参数表示就是 end-begin+1。

最后在把tmp数组释放掉,我们就完成了归并的非递归实现。

代码
//非递归归并
void MergeSortNonR(int* a, int begin, int end)
{int* tmp = (int*)malloc(sizeof(int)*(end - begin + 1));assert(tmp);int gap=1;for (; gap < end - begin + 1; gap *= 2){int i = 0;for (i = 0; i <= end; i += 2 * gap){//gap个数为一组,两组进行归并int begin1 = i;//第一组的开始int end1 = begin1 + gap - 1;if (end1 >= end)//第一组就越界了或者第一组的 end1 就是数组尾{while (begin1 <= end){tmp[begin1] = a[begin1];begin1++;}break;}int begin2 = begin1 + gap;int end2 = begin1 + 2 * gap - 1;if (end2 > end){end2 = end;}int index = begin1;//一次归并while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2])tmp[index++] = a[begin1++];elsetmp[index++] = a[begin2++];}while (begin1 <= end1){tmp[index++] = a[begin1++];}while (begin2 <= end2){tmp[index++] = a[begin2++];}}memcpy(a, tmp, sizeof(int) * (end - begin + 1));}free(tmp);
}

我们可以观察每一轮归并的结果来分析程序逻辑是否正确

gap为1时

gap为2时

gap为4时

gap为8时

这时候就已经完全排完了。

3.比较快排、归并和堆排序

void test()
{int N = 1000000;int* a1 = (int*)malloc(sizeof(int) * N);assert(a1);int* a2 = (int*)malloc(sizeof(int) * N);assert(a2);int* a3 = (int*)malloc(sizeof(int) * N);assert(a3);srand((unsigned int)time(NULL));for (int i = 0; i < N; i++){a1[i] = rand();a2[i] = a1[i];a3[i] = a1[i];}int begin1 = clock();HeapSort(a1, N);int end1 = clock();printf("HeapSort:%d\n", end1 - begin1);int begin2 = clock();QuickSort(a1,0, N-1);int end2 = clock();printf("QuickSort:%d\n", end2 - begin2);int begin3 = clock();MergeSort(a1,0, N-1);int end3 = clock();printf("MergeSort:%d\n", end1 - begin1);free(a1);free(a2);free(a3);
}

当要排序一百万个数据时

而排序十万个数据时

这里我们就能发现虽然我们解析他们的时间复杂度都是NlogN,但是其实快排是要比另外两个排序快的,首先是因为,堆排序要建堆,建堆的时间复杂度为N,而选数的时间复杂度时NlogN,所以他要比快排慢,而归并排序则是因为,他要开辟额外的空间,同时我们拷贝数据回原数组时,memset的时间复杂度也很高,影响了我们的效率。

4.归并排序实现外排序

什么是外排序?

我们前面讲的排序都是在内存中去排序数据,因为他们的数据量都不是很大,在内存中能放下,他们都是内排序算法。

而当我们的数据量十分大时,无法一次性放进内存中进行排序,数据存在磁盘中,也就是外存中,这时候我们就需要改进前面学过的排序来实现对外存中的数据进行排序,而其他的排序算法都只能在内存中排序,只有归并排序能实现外排序,所以归并排序既是一种内排序算法,也是外排序算法。

为什么不直接在磁盘文件中进行排序呢?第一,文件是不支持随机读写的,只能做一个串行的直接访问,虽然我们可以用 fseek 函数来修改文件指针的位置,但是当数据量很大的时候我们是顾不过来对文件指针的操作的。第二,磁盘的访问速度以及数据挪动等操作速度都是远慢于内存的,在实际应用中不适合在磁盘中操作数据。

外排序的思路是怎么样的呢?

首先我们有一个放了数据的文件,数据很大,无法一次性放到内存中

虽然我们无法一次性全加载到内存中,但是我们可以先将一部分数据放到内存中去排序,数据的个数取决于内存的大小。 

这时候内存中的数据我们就能够通过内排序算法进行排序了,最好用快排,因为堆排效率比不上快排,而归并则还需要额外的空间,内存本来就不够了,所以对内存中的数据排序不能用归并。 我们将排序好的数据从内存中输出到一个小文件中

然后重复上述过程,直到把数据都读完,排好序之后都写到小文件中

这时候我们就能得到几个数据有序的小文件,对这些有序的小文件,我们就能进行归并排序了。

这样一来,我们就得到了一个排好序的文件。

归并排序实现外排序的思想其实并不难,难的是在操作的过程中的文件操作,以及给文件取名的细节,还有最后归并小文件时的迭代。

我们首先创建一个文件,我们在实现代码的时候没必要用海量数据来实现,也做不到。这就假设内存的空间就够存储一个 二十个数据的数组,因为我们要用数组来接受文件中的数据,我们还要考虑内排序算法调用堆栈的消耗,我们在文件中存进100个随机数据,范围在100~999之间,然后再放上几个二位数与四位数

int main()
{FILE* fout = fopen("data", "w");srand((unsigned)time(NULL));for (int i = 0; i < 100; i++){int data = rand() % 900 + 100;fprintf(fout, "%d\n", data);}fclose(fout);return 0;
}

我们先生成一个data文件,然后手动添加几个数据进去便于我们最后结果的检验。

有一点要注意,我们在这个示例中的文件名都不加后缀了,因为加了后缀之后太过复杂(在后面部分给文件取名的时候),而C语言对文件名的操作并不是这么方便,所以我们就不加文件后缀了。

假如这就是我们要排序的文件。

我们假设内存中最多就只能放二十个整形数据(除去排序所需要的空间),这时候我们每次要用一个数组来接受20个数据,对他们进行排序之后再分别写入一个小文件中保存


int main()
{//FILE* fout = fopen("data", "w");//srand((unsigned)time(NULL));//for (int i = 0; i < 100; i++)//{//	int data = rand() % 900 + 100;//	fprintf(fout, "%d\n", data);//}//fclose(fout);char filename[20] = "data";//测试小文件的个数int filenum=Read_Sort(filename);printf("%d", filenum);return 0;
}

读取数据并排序放到小文件的函数

//读取数据排序写入小文件
int Read_Sort(char* filename)
{//内存中最多存储个数int N = 20;int* a = (int*)malloc(sizeof(int) * N);assert(a);FILE* pf = fopen(filename, "r");assert(pf);//记录小文件个数并用于小文件命名int n = 0;char sfile[20] = "sorted";int data;//用于读取保存数据int i = 0;//a数组下标//读取数据while (fscanf(pf,"%d", &data) != EOF){if (i < N-1)//先放19个数据{a[i++] = data;}else//放第20个数据{a[i++] = data;//把第20个数据放进数组//对这一组数据排序QuickSort(a, 0, N - 1);//小文件命名char file[20];n++;sprintf(file, "%s%d", sfile, n);//将小文件命名为sorted1  sorted2 ... ...FILE* fout=fopen(file, "w");assert(fout);for (int j = 0; j < i; j++){fprintf(fout, "%d\n", a[j]);//存数据的时候要注意格式,到时候取数据的格式要与存数据的格式一样}//写完之后关闭文件fclose(fout);//i复原,重新读取i = 0;}}//最后要判断a数组中还有没有数据if (i != 0){n++;char file[20];sprintf(file, "%s%d", sfile, n);FILE* fout = fopen(file, "w");assert(fout);for (int j = 0; j < i; j++)fprintf(fout,"%d\n", a[j]);fclose(fout);}fclose(pf);//返回小文件个数free(a);return n;
}

我们可以看到一共生成了6个小文件,

我们的data总数是110个,前五个文件都有20个数据,第六个文件右10个数据,说明这一步是没问题的。

这一步完成之后,下一步就是对着6个小文件进行归并了。

文件归并的过程中我们每次都只操作三个文件,file1 和 file2 是要读取数据进行归并的文件,而mfile适用于归并后数据存储的文件。 

一次归并完之后,将 file1 改成 mfile(sorted12)去管理生成的归并之后的文件 ,file2则修改为sorted3,然后再创建一个mfile(sorted123)文件,来进行新的归并。

这个过程是一个循环与迭代的过程


//文件归并
void Merge_File(int n)
{//初始条件char filename[20] = "sorted";char file1[20]="sorted1";char file2[20];char mfile[20] = "sorted12";int i = 0;for (i = 1; i < n; i++){sprintf(file2, "%s%d", filename, i+1);//对file2修改FILE* fin1 = fopen(file1, "r");assert(fin1);FILE* fin2 = fopen(file2, "r");assert(fin2);FILE* fout = fopen(mfile, "w");assert(fout);//读取file1和file2数据int num1;int ret1=fscanf(fin1, "%d\n", &num1);int num2;int ret2= fscanf(fin2, "%d\n", &num2);while (ret1!=EOF&&ret2!=EOF){if (num1 < num2){fprintf(fout, "%d\n", num1);ret1 = fscanf(fin1, "%d\n", &num1);}else{fprintf(fout, "%d\n", num2);ret2 = fscanf(fin2, "%d\n", &num2);}}//其中一个文件读取完了while (ret1 != EOF){fprintf(fout, "%d\n", num1);ret1 = fscanf(fin1, "%d\n", &num1);}while (ret2 != EOF){fprintf(fout, "%d\n", num2);ret2 = fscanf(fin2, "%d\n", &num2);}//都读取完之后关闭文件fclose(fin1);fclose(fin2);fclose(fout);//迭代memcpy(file1, mfile, 20);//将file修改为 sorted12sprintf(mfile, "%s%d", mfile, i + 2);//将mfile修改为 sorted123}}

这里我们注意用num1和num2,还有ret1和ret2来分别保存读取的数据和fscanf的返回值,如果不用ret1和ret2的话,要特别注意不要多读或者漏读数据,yaoqingchuwenjianzhizhendedingwei

当我们把程序运行完之后,我们去工程目录底下就能找到这一些列文件

我们可以打开最终的文件 sorted123456来验证一下程序的正确性,

首先数据的个数是没问题的,还是110个数据,而我们手动添加的十个数据也都在文件的开头和结尾,这说明排序也没问题。

外排序的实现其实最主要的不是排序的算法,而是对文件的操作的熟悉,外排序我们可能造成写程序用不到,但是在以后处理大数据的排序时我们就可以借鉴这个思路。

总的来说,归并排序不适合常规场景,因为他有额外的空间消耗,但是适合这种外排序。

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

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

相关文章

Unity给地图物体添加对撞机

在项目/Assets下创建Prefabs文件夹 选择素材拖入层级下&#xff0c;注意此时地图素材有可能看不到&#xff0c;此时选择Tilemap在检查器中修改图层顺序调至最低。 添加对撞机 选择素材&#xff0c;在检查器中点击添加组件Box Collider 2D&#xff0c;将素材拖入Prefabs文件下…

【Arduino IDE 环境配置】

目录 Arduino IDE 环境配置 1. 安装方式2. 操作方法&#xff08;Arduino中文社区&#xff09; 2.1. 安装Arduino IDE2.2. 下载固件2.3. 修改Arduino IDE语言2.4. 添加开发板管理网址2.5. 运行离线包2.6. 检查安装是否成功 下载Arduino IDE&#xff1a; 如果你还没有安装Arduin…

如何使用Postgres的JSONB数据类型进行高效查询

文章目录 解决方案1. 创建包含JSONB列的表2. 插入JSON数据3. 使用GIN索引加速查询4. 执行高效的JSONB查询 示例代码解释 PostgreSQL的JSONB数据类型提供了一种灵活的方式来存储和查询JSON格式的数据。JSONB不仅允许你在PostgreSQL数据库中存储JSON文档&#xff0c;而且还对这些…

科技云报道:大模型加持后,数字人“更像人”了吗?

科技云报道原创。 北京冬奥运AI 虚拟人手语主播、杭州亚运会数字人点火、新华社数字记者、数字航天员小诤…当随着越来越多数字人出现在人们生活中&#xff0c;整个数字人行业也朝着多元化且广泛的应用方向发展&#xff0c;快速拓展到不同行业、不同场景。 面向C端&#xff0…

.NET开源免费的跨平台框架 - MAUI(附学习资料)

前言 前几天分享了一个.NET MAUI开源免费的UI工具包 - Uranium&#xff0c;然后技术群有不少同学问.NET MAUI是不是免费的&#xff1f;能做什么&#xff1f;今天特意写这篇文章来介绍一下.NET开源、免费&#xff08;基于MIT License&#xff09;的跨平台框架&#xff1a;MAUI。…

【刷题笔记】第八天

文章目录 [928. 尽量减少恶意软件的传播 II](https://leetcode.cn/problems/minimize-malware-spread-ii/)方法1&#xff1a;dfs方法2&#xff1a;并查集 [GCD and LCM](https://vjudge.net.cn/problem/Aizu-0005)[Missing Bigram](https://vjudge.net.cn/problem/CodeForces-1…

基于springboot+vue+Mysql的简历系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

数据赋能(60)——要求:数据服务部门能力

“要求&#xff1a;数据服务部门实施数据赋能影响因素”是作为标准的参考内容编写的。 在实施数据赋能中&#xff0c;数据服务部门的能力体现在多个方面&#xff0c;关键能力如下图所示。 在实施数据赋能的过程中&#xff0c;数据服务部门应具备的关键能力如下。 业务理解和沟…

案例与脚本实践:DolphinDB 轻量级实时数仓的构建与应用

DolphinDB 高性能分布式时序数据库&#xff0c;具有分布式计算、事务支持、多模存储、以及流批一体等能力&#xff0c;非常适合作为一款理想的轻量级大数据平台&#xff0c;轻松搭建一站式的高性能实时数据仓库。 本教程将以案例与脚本的方式&#xff0c;介绍如何通过 Dolphin…

MySQL 的事务

事务概念 MySQL事务是一个或者多个的数据库操作&#xff0c;要么全部执行成功&#xff0c;要么全部失败回滚。 事务是通过事务日志来实现的&#xff0c;事务日志包括&#xff1a;redo log和undo log。 事务状态 事务有以下五种状态&#xff1a; 活动的部分提交的失败的中止的…

使用GAN做图像超分——SRGAN,ESRGAN

在GAN出现之前&#xff0c;使用的更多是MSE&#xff0c;PSNR,SSIM来衡量图像相似度&#xff0c;同时也使用他们作为损失函数。 但是这些引以为傲的指标&#xff0c;有时候也不是那么靠谱&#xff1a; MSE对于大的误差更敏感&#xff0c;所以结果就是会倾向于收敛到期望附近&am…

【深度学习】wandb模型训练可视化工具使用方法

【深度学习】wandb模型训练可视化工具使用方法 wandb简介功能介绍登陆注册以及API keysproject和runsproject和runs的关系 wandb的配置实验跟踪版本管理Case可视化分析可视化自动调参&#xff08;wandb.sweep&#xff09;配置wandb.sweep1.配置 sweep_config2.初始化 sweep con…

【python】flask中ORM工具SQLAIchemy,各种数据查询操作详细解析

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

Python 密码学实用指南(全)

原文&#xff1a;zh.annas-archive.org/md5/fe5e9f4d664790ea92fb33d78ca9108d 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 前言 密码学在保护关键系统和敏感信息方面有着悠久而重要的历史。本书将向您展示如何使用 Python 加密、评估、比较和攻击数据。总的来说&…

Qt对话框

文章目录 创建普通的对话框对话框的内存泄露问题自定义对话框模态式对话框QMessageBox&#xff08;消息对话框&#xff09;对话框类型对话框按钮接收对话框返回值演示其他创建方式 QColorDialog&#xff08;颜色对话框&#xff09;案例 QFileDialog&#xff08;文件对话框&…

仿真测试的应用领域

仿真测试在各种领域中都有广泛的应用&#xff0c;以下是一些应用最广泛的场景&#xff1a; 工业制造&#xff1a;通过模拟制造过程&#xff0c;可以预测产品的质量和性能&#xff0c;优化生产流程&#xff0c;降低成本。航空航天&#xff1a;飞机、导弹、航天器等的设计和研发…

Python - 字符串的应用

字符串格式化问题 使用规则举例: name input("请输入你的名字&#xff1a;") address input("请输入你的住址&#xff1a;") age int(input("请输入你的年龄&#xff1a;")) hobby input("请输入你的爱好&#xff1a;") # %s 字符…

【Android GUI】从总体上了解Android的GUI体系

文章目录 概览Android硬件接口HALGralloc与Framebuffer Gralloc模块的加载Gralloc提供的接口Android原生的Gralloc实现打开framebuffer设备打开gralloc设备 参考 概览 Linux内核提供了统一的framebuffer显示驱动。设备节点/dev/graphics/fb*或者/dev/fb*&#xff0c;其中fb0表示…

快速上手Linux核心命令

Linux 的重要性不用我多说了吧&#xff0c;大多数互联网公司&#xff0c;服务器都是采用的Linux操作系统 Linux是一个主要通过命令行来进行管理的操作系统。 只有熟练掌握Linux核心命令&#xff0c;在使用起来我们才会得心应手 这里给大家整理了Linux一些核心命令&#xff0…

Android --- Activity

官方文档-activity Activity 提供窗口&#xff0c;供应在其中多个界面。此窗口通常会填满屏幕&#xff0c;但也可能小于屏幕并浮动在其他窗口之上。 大多数应用包含多个屏幕&#xff0c;这意味着它们包含多个 Activity。通常&#xff0c;应用中的一个 Activity 会被指定主 Ac…