【八大排序】冒泡、直接选择、直接插入、希尔、堆、归并、快速、计数排序

目录

  • 一、排序的介绍
  • 二、排序算法的实现
    • 2.1 直接插入排序
    • 2.2 希尔排序
    • 2.3 直接选择排序
    • 2.4 堆排序
    • 2.5 冒泡排序
    • 2.6 快速排序
    • 2.7 归并排序
    • 2.8 比较排序算法的性能展示
    • 2.9 计数排序

个人主页<—
数据结构专栏<—

在这里插入图片描述

一、排序的介绍

我们的生活中有很多排序,比如像成绩的高低,身高的高低,体重的胖瘦,价格的高低等,这些都是排序,今天我们这期博客要讲的就是如何去排序,我们一共会讲解8个排序算法,它们分别是:
在这里插入图片描述
这八大排序就是我们常用的主流排序算法,其中大多数都是我们耳熟能详的排序算法,下面我们会逐步实现这八大算法。

二、排序算法的实现

2.1 直接插入排序

在这里插入图片描述
直接插入排序是⼀种简单的插入排序法,基本思想是:把待排序的记录按其关键码值的大小逐个插入到⼀个已经排好序的有序序列中,直到所有的记录插入完为止,得到⼀个新的有序序列 。
这个排序就是依次从前往后扫描建立前k有序序列,之后每次让序列之后的一个元素依次从有序序列的末尾开始与有序序列的元素进行比较,如果序列中的元素比序列之后的第一个元素大,就让序列中的元素向后移动,直到找到小于有序序列之后第一个元素的位置就将它插入。上面动图中标黄的就是排好的有序序列,标红的是有序序列后面的第一个元素,移动的过程就是比较的过程。

//直接插入排序
void InsertSort(int* arr, int n)
{for (int i = 0;i < n-1;i++){int end = i;int tmp = arr[end + 1];while (end >= 0){if (arr[end] > tmp){arr[end + 1] = arr[end];end--;}else{break;}}arr[end + 1] = tmp;}
}

以上代码中,变量end永远指向有序序列的最后一个元素,我们用tmp存储有序序列之后的第一个元素,然后我们让有序序列中的元素和tmp比较,如果大我们就后移,如果小就跳出循环,此时end指向比tmp小的元素或者是下标为-1的元素,那么end+1就是tmp该在的位置。直接插入排序的时间复杂度是:O(n2)。

测试代码:

int main()
{int arr[] = { 9,6,5,1,10,11,35,27 };printf("排序之前:");print(arr, 8);InsertSort(arr, 8);printf("排序之后:");print(arr, 8);return 0;
}

代码中print是我封装的一个打印函数

结果:
在这里插入图片描述

2.2 希尔排序

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数(通常是gap = n/3+1),把待排序文件所有记录分成各组,所有的距离相等的记录分在同一组内,并对每一组内的记录进行排序,然后gap=gap/3+1得到下一个整数,再将数组分成各组,进⾏插⼊排序,当gap=1时,就相当于直接插入排序。它是在直接插入排序算法的基础上进⾏改进而来的,综合来说它的效率肯定是要高于直接插入排序算法的
在这里插入图片描述
经典动图:
在这里插入图片描述

//希尔排序
void ShellSort(int* arr, int n)
{int gap = n;while (gap > 1){gap = gap / 3 + 1;for (int i = 0;i < n - gap;i++){int end = i;int tmp = arr[end + gap];while (end>=0){if (arr[end] > tmp){arr[end + gap] = arr[end];end -= gap;}else{break;}}arr[end + gap] = tmp;}}
}

以上代码与直接插入排序的函数的交换核心相同,唯一不同的是希尔排序是将要排序的分为好几组数据,假设gap是2,就每隔一个数据进行比较然后排序,假设gap是1就和直接插入排序相同了,都是依次比较,这时候也是最后一次排序。也就是gap>1预排序,gap=1直接插入排序。 关于希尔排序时间复杂度,它一直是一个难题,它的时间是所取“增量”序列的函数,这涉及一些数学上尚未解决的难题,希尔排序的时间复杂度大约是:n1.3

测试结果:
在这里插入图片描述

2.3 直接选择排序

  • 在元素集合array[i]--array[n-1]中选择最大(小)的数据元素;
  • 若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换;
  • 在剩余的 array[i]--array[n-2]array[i+1]--array[n-1]) 集合中,重复上述步骤,直到集合剩余 1 个元素;

其排序的核心思想就是同时未排序的序列中找最大值最小值,并将最小值放在前面,把最大值放在后面

//直接选择排序
void SelectSort(int* arr, int n)
{int begin = 0;int end = n - 1;while (begin < end){int max = begin;int min = begin;for (int i = begin+1;i <= end;i++){if (arr[i] > arr[max]){max = i;}if (arr[i] < arr[min]){min = i;}}if (max == begin){max = min;}Swap(&arr[min], &arr[begin]);Swap(&arr[max], &arr[end]);begin++;end--;}
}
void Swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}

直接选择排序的代码比较好理解,需要注意的是,假设我们要对6、5、4进行排升序时,max会指向下标为0的位置,min会指向下标为2的位置,begin指向下标为0,min指向下标为2,如果我们不对max进行if语句判断max是否在begin指向下标位置的话,交换两次之后,还会是6、5、4,序列没有发生改变,就会出错,所以代码中出现了if语句特判

直接选择排序的时间复杂度为:O(n2)。

测试结果:
在这里插入图片描述

2.4 堆排序

堆排序就是利用数据结构堆的思想进行的排序,我们在之前的博客中已经实现了堆排序,所以我们会直接将代码拷贝过来,->堆排序博客

void AdjustDown(int* arr, int parent, int n)
{int child = parent * 2 + 1;while (child < n){if (child + 1 < n && arr[child] < arr[child + 1]){child++;}if (arr[parent] < arr[child]){Swap(&arr[parent], &arr[child]);parent = child;child = parent * 2 + 1;}else{break;}}
}
//堆排序
void HeapSort(int* arr, int n)
{//建大根堆for (int i = (n - 1 - 1) / 2;i >= 0;i--){AdjustDown(arr, i, n);}int end = n - 1;//排序while (end >= 0){Swap(&arr[0], &arr[end]);AdjustDown(arr, 0, end);end--;}
}

虽然代码的风格和之前博客上的略有差异,但思想还是相同的,排升序:建根堆,逐步交换排序;排降序:建根堆,逐步交换排序。堆排序的时间复杂度为:O(nlogn)。

测试结果:
在这里插入图片描述

2.5 冒泡排序

冒泡排序是我们最为熟知,也是最为经典的排序算法,它的代码思路简单且易懂,核心思路就是有n个数字就比较n-1次逐步比较出最大值最小值并交换排序。

//冒泡排序
void BubbleSort(int* arr, int n)
{for (int i = 0;i < n - 1;i++){int flag = 0;for (int j = 0;j < n - i - 1;j++){if (arr[j] > arr[j + 1]){flag = 1;int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;}}if (flag == 0){break;}}
}

经典动图:
在这里插入图片描述

冒泡排序的时间复杂度为:O(n2)。

测试结果
在这里插入图片描述

2.6 快速排序

快速排序Hoare于1962年提出的⼀种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

经典动图:
在这里插入图片描述
快速排序找基准值递归版本可以分成两个版本,一个是hoare版本,一个是lomuto前后指针;非递归版本会借用数据结构进行实现。

快速排序主体框架:

//快速排序
void QuickSort(int* arr, int left, int right)
{if (left >= right){return;}//找基准值int keyi = _QuickSort(arr, left, right);//左序列[left,keyi-1] 右序列[keyi+1,right]QuickSort(arr, left, keyi - 1);QuickSort(arr, keyi + 1, right);
}

找基准值的过程就是排序的过程。

递归版本:

hoare版本:

  • 创建左右指针,确定基准值
  • right从右向左找出比基准值的数据,left从左向右找出比基准值的数据,左右指针数据交换,进入下次循环
//hoare版本
int _QuickSort(int* arr, int left, int right)
{int keyi = left;left++;while (left <= right){//right:从右往左走,找比基准值要小的while (left <= right && arr[right] > arr[keyi]){right--;}//left:从左往右走,找比基准值要大的while (left <= right && arr[left] < arr[keyi]){left++;}if (left <= right){Swap(&arr[left], &arr[right]);}}Swap(&arr[keyi], &arr[right]);return right;
}

测试结果:
在这里插入图片描述
图解
在这里插入图片描述
lomuto前后指针:

创建前后指针,从左往右找比基准值小的进行交换,使得小的都排在基准值的左边。

  • 我们会创建两个指针prevcurcur走在前面,prev在后面
  • cur找到比基准值要小的之后,++prev让prev和cur指向的下标的值进行交换,cur++
  • cur未找到比基准值要小的数据cur++
//lomuto前后指针法
int _QuickSort1(int* arr, int left, int right)
{int keyi = left;int prev = left;int cur = prev + 1;while (cur <= right){if (arr[cur] < arr[keyi] && ++prev != cur){Swap(&arr[cur], &arr[prev]);}cur++;}Swap(&arr[keyi], &arr[prev]);return prev;
}

运行结果:
在这里插入图片描述
图解:
在这里插入图片描述
非递归版本:

非递归版本的快速排序将会使用到数据结构,我们在之前的博客中已经实现过了数据结构,所以我们会将代码拷贝过来,在利用的同时,我们找基准值的方法依旧使用lomuto前后指针法,【数据结构】栈 相关博客<–

//非递归版本快速排序
void QuickSortNoR(int* arr, int left, int right)
{//定义栈ST st;//初始化栈StackInit(&st);StackPush(&st, left);StackPush(&st, right);while (!StackEmpty(&st)){//取栈顶元素int end = StackTop(&st);//销毁栈顶元素StackPop(&st);int begin = StackTop(&st);StackPop(&st);//[begin,end]找基准值int keyi = begin;int prev = begin;int cur = prev + 1;while (cur <= end){if (arr[cur] < arr[keyi] && ++prev != cur){Swap(&arr[cur], &arr[prev]);}cur++;}Swap(&arr[keyi], &arr[prev]);keyi = prev;//[begin,keyi-1]  [keyi+1,end]if (keyi + 1 < end){StackPush(&st, keyi + 1);StackPush(&st, end);}if (begin < keyi - 1){StackPush(&st, begin);StackPush(&st, keyi - 1);}}//销毁栈StackDestroy(&st);
}

快速排序的时间复杂度为:O(nlogn)

测试结果:
在这里插入图片描述
图解
在这里插入图片描述
在这里插入图片描述

2.7 归并排序

归并排序MERGE-SORT是建立在归并操作上的⼀种有效的排序算法,该算法是采用分治法Divideand Conquer的⼀个非常典型的应用。将已有序的子序列合并,得到完全有序的序列先使每个子序列有序,再使子序列段间有序若将两个有序表合并成一个有序表,称为二路归并

归并排序核心步骤
在这里插入图片描述
从图中就可以看出很强烈的递归思想,所以我们的归并排序依旧使用递归写的。

//归并排序
void _MergeSort(int* arr, int left, int right, int* tmp)
{if (left >= right){return;}int mid = left + (right - left) / 2;//[left,mid] [mid+1,right]_MergeSort(arr, left, mid, tmp);_MergeSort(arr, mid + 1, right, tmp);//第一次运行到这里时,是将数组分解成了一个个单个元素的时候。//合并两个有序序列int begin1 = left;int end1 = mid;int begin2 = mid + 1;int end2 = right;int index = begin1;//[begin1,end1] [begin2,end2]while (begin1 <= end1 && begin2 <= end2){if (arr[begin1] < arr[begin2]){tmp[index++] = arr[begin1++];}else{tmp[index++] = arr[begin2++];}}while (begin1 <= end1){tmp[index++] = arr[begin1++];}while (begin2 <= end2){tmp[index++] = arr[begin2++];}//导入原数组for (int i = left;i <= right;i++){arr[i] = tmp[i];}
}
//归并排序
void MergeSort(int* arr, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);_MergeSort(arr, 0, n - 1, tmp);free(tmp);tmp = NULL;
}

归并排序的时间复杂度为:O(nlogn)

测试结果:
在这里插入图片描述
至此我们已经讲解了七种排序算法,并且这七种排序算法都是比较算法,都是通过一个个比较来排序的,而我们的计数排序不是比较算法那么在讲解计数排序之前呢,我们先来了解一下这七种排序算法的性能如何吧!

2.8 比较排序算法的性能展示

测试代码

// 测试排序的性能对⽐
void TestOP()
{srand(time(0));const int N = 100000;int* a1 = (int*)malloc(sizeof(int) * N);int* a2 = (int*)malloc(sizeof(int) * N);int* a3 = (int*)malloc(sizeof(int) * N);int* a4 = (int*)malloc(sizeof(int) * N);int* a5 = (int*)malloc(sizeof(int) * N);int* a6 = (int*)malloc(sizeof(int) * N);int* a7 = (int*)malloc(sizeof(int) * N);for (int i = 0; i < N; ++i){a1[i] = rand();a2[i] = a1[i];a3[i] = a1[i];a4[i] = a1[i];a5[i] = a1[i];a6[i] = a1[i];a7[i] = a1[i];}int begin1 = clock();InsertSort(a1, N);int end1 = clock();int begin2 = clock();ShellSort(a2, N);int end2 = clock();int begin3 = clock();SelectSort(a3, N);int end3 = clock();int begin4 = clock();HeapSort(a4, N);int end4 = clock();int begin5 = clock();QuickSort(a5, 0, N - 1);int end5 = clock();int begin6 = clock();MergeSort(a6, N);int end6 = clock();int begin7 = clock();BubbleSort(a7, N);int end7 = clock();printf("直接插入排序->InsertSort:%d\n", end1 - begin1);printf("希尔排序----->ShellSort:%d\n", end2 - begin2);printf("直接选择排序->SelectSort:%d\n", end3 - begin3);printf("堆排序------->HeapSort:%d\n", end4 - begin4);printf("快速排序----->QuickSort:%d\n", end5 - begin5);printf("归并排序----->MergeSort:%d\n", end6 - begin6);printf("冒泡排序----->BubbleSort:%d\n", end7 - begin7);free(a1);free(a2);free(a3);free(a4);free(a5);free(a6);free(a7);
}

首先生成了100000个随机数据,然后保证每个排序算法排的数组中的元素相同,再根据clock函数记录开始结束时间,就可以知道每个排序算法耗费的时间了(单位:ms)。

测试结果:
在这里插入图片描述
从结果可以看出,

  • 我们的第一梯队分别是:堆排序、希尔排序、快速排序、归并排序
  • 我们的第二梯队分别是:直接插入排序、直接选择排序。但它们都是以秒为单位的。
  • 我们的第三梯队就是我们熟悉的冒泡排序了。

2.9 计数排序

经典动图:
在这里插入图片描述

//计数排序
void CountSort(int* arr, int n)
{//找最大最小值int max = arr[0];int min = arr[0];for (int i = 1;i < n;i++){if (max < arr[i]){max = arr[i];}if (arr[i] < min){min = arr[i];}}//用max-min+1确定数组大小int* count = (int*)malloc(sizeof(int) * (max - min + 1));if (count == NULL){perror("malloc fail!");return;}//初始化数组countmemset(count, 0, sizeof(int) * (max - min + 1));for (int i = 0;i < n;i++){count[arr[i] - min]++;}//将count数组还原到原数组中,使其有序int index = 0;for (int i = 0;i < max - min + 1;i++){while (count[i]--){arr[index++] = i + min;}}
}

测试结果:
在这里插入图片描述

总结:
以上就是本期博客分享的全部内容啦!如果觉得文章还不错的话可以三连支持一下,你的支持就是我前进最大的动力!
技术的探索永无止境! 道阻且长,行则将至!后续我会给大家带来更多优质博客内容,欢迎关注我的CSDN账号,我们一同成长!
(~ ̄▽ ̄)~

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

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

相关文章

linux 查询目录文件大小

​ 在 Linux 系统中&#xff0c;准确地掌握目录和文件的大小对于磁盘空间管理至关重要。​本文将详细介绍如何使用 du&#xff08;disk usage&#xff09;命令逐层查看目录和文件的大小&#xff0c;并结合 sort 命令对结果进行排序&#xff0c;以便有效地识别和管理占用…

如何简单几步使用 FFmpeg 将任何音频转为 MP3?

在多媒体处理领域&#xff0c;FFmpeg 以其强大的功能和灵活性而闻名。无论是视频编辑、音频转换还是流媒体处理&#xff0c;它都是专业人士和技术爱好者的首选工具之一。在这篇文章中简鹿办公将重点介绍如何使用 FFmpeg 进行音频格式转换&#xff0c;提供一些常用的转换方式&am…

通信信号分类识别

通信信号分类识别 AlexNet网络识别InceptionV3、ResNet-18、ResNet-50网络识别 采用短时傅里叶变换将一维信号转换为二维信号&#xff0c;然后采用经典神经网络进行识别 支持识别BASK,BFSK,BPSK,QPSK,8PSK,QAM和MSK。 AlexNet网络识别 在这里插入图片描述 InceptionV3、Re…

TPshop项目-服务器环境部署(部署环境/服务,检查部署环境/服务,上传TPshop项目到服务器,配置文件的更改,安装TPshop)

目录 部署环境/服务&#xff0c;检查部署环境/服务 检查部署环境/服务 上传TPshop项目到服务器&#xff0c;配置文件的更改&#xff0c;安装TPshop 部署环境/服务&#xff0c;检查部署环境/服务 一般部署环境&#xff0c;会根据开发写的部署文档来一步一步的部署环境。 部署…

C++入门基础:命名空间,缺省参数,函数重载,输入输出

命名空间&#xff1a; C语言是基于C语言的&#xff0c;融入了面向对象编程思想&#xff0c;有了很多有用的库&#xff0c;所以接下来我们将学习C如何优化C语言的不足的。 在C/C语言实践中&#xff0c;在全局作用域中变量&#xff0c;函数&#xff0c;类会有很多&#xff0c;这…

缓存 --- Redis基本数据类型

缓存 --- Redis基本数据类型 Redis Intro5种基础数据类型 Redis Intro Redis&#xff08;Remote Dictionary Server&#xff09;是一款开源的高性能键值存储系统&#xff0c;常用于缓存、消息中间件和实时数据处理场景。以下是其核心特点、数据类型及典型使用场景&#xff1a; …

Redis命令——list

列表类型是用来存储多个有序的字符串&#xff0c;列表中的每个字符串称为元素&#xff08;element&#xff09;&#xff0c;⼀个列表最多可以存储个元素 在 Redis 中&#xff0c;可以对列表两端插入&#xff08;push&#xff09;和弹出&#xff08;pop&#xff09;&#xff0c;…

Android Jetpack Compose 状态管理解析:remember vs mutableStateOf,有啥不一样?为啥要一起用?

&#x1f331;《Jetpack Compose 状态管理解析&#xff1a;remember vs mutableStateOf&#xff0c;有啥不一样&#xff1f;为啥要一起用&#xff1f;》 在 Jetpack Compose 的世界里&#xff0c;UI 是响应式的。这意味着当状态发生变化时&#xff0c;UI 会自动重组&#xff0…

使用 PCL 和 Qt 实现点云可视化与交互

下面我将介绍如何结合点云库(PCL)和Qt框架(特别是QML)来实现点云的可视化与交互功能&#xff0c;包括高亮选择等效果。 1. 基本架构设计 首先需要建立一个结合PCL和Qt的基本架构&#xff1a; // PCLQtViewer.h #pragma once#include <QObject> #include <pcl/point…

mybatis plus打印sql日志到指定目录

1、mybatis plus打印sql日志 参考文档&#xff1a;mybatis plus打印sql日志_mybatisplus日志打印-CSDN博客 2、修改 修改InfoLevelLogger Override public void debug(String s) {// 修改这里logger.info(s);log.debug(s); } 增加&#xff1a;log.debug(s); 修改logback.x…

vue3 watch和watchEffect 的用法和区别

在 Vue 3 里&#xff0c;watch 和 watchEffect 都是用于响应式数据变化的 API&#xff0c;但它们在使用方法和应用场景上存在差异。下面详细介绍它们的用法和区别。 用法 watch watch 用于监听特定的响应式数据源&#xff0c;当数据源发生变化时&#xff0c;会执行相应的回调…

Qt中修改了UI设计文件后编译不生效问题的解决办法

复制工程过来后&#xff1a; 1、删除build文件 2、删除.user文件&#xff0c;恢复为文件最初的那样 3、执行make distclean,删除所有由先前构建过程生成的文件 4、再次打开工程&#xff0c;修改ui文件编译生效&#xff01;

EtherCAT转ProfiNet边缘计算网关配置优化:汽车制造场景下PLC与机器人协同作业案例

1.行业背景与需求分析 智能汽车焊装车间是汽车制造的核心工艺环节&#xff0c;某德国豪华品牌在其上海MEB工厂新建的焊装车间中&#xff0c;采用西门子S7-1500PLC作为ProfiNet主站&#xff0c;负责整线协调与质量追溯&#xff1b;同时部署KUKAKR1500Titan机器人&#xff08;Eth…

day46—双指针-两数之和-输入有序数组(LeetCode-167)

题目描述 给你一个下标从 1 开始的整数数组 numbers &#xff0c;该数组已按 非递减顺序排列 &#xff0c;请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] &#xff0c;则 1 < index1 < index2 &l…

线性代数 | 知识点整理 Ref 1

注&#xff1a;本文为 “线性代数 | 知识点整理” 相关文章合辑。 因 csdn 篇幅合并超限分篇连载&#xff0c;本篇为 Ref 1。 略作重排&#xff0c;未整理去重。 图片清晰度限于引文原状。 如有内容异常&#xff0c;请看原文。 线性代数知识汇总 Arrow 于 2016-11-27 16:27:5…

比特币的跨输入签名聚合(Cross-Input Signature Aggregation,CISA)

1. 引言 2024 年&#xff0c;人权基金会&#xff08;Human Rights Foundation&#xff0c;简称 HRF&#xff09;启动了一项研究奖学金计划&#xff0c;旨在探讨“跨输入签名聚合”&#xff08;Cross-Input Signature Aggregation&#xff0c;简称 CISA&#xff09;的潜在影响。…

3.基础开发工具

1.软件包管理器 1.1什么是软件包 • 在Linux下安装软件, ⼀个通常的办法是下载到程序的源代码, 并进⾏编译, 得到可执⾏程序. • 但是这样太⿇烦了, 于是有些⼈把⼀些常⽤的软件提前编译好, 做成软件包(可以理解成windows上 的安装程序)放在⼀个服务器上, 通过包管理器可以很…

Golang errors 包快速上手

文章目录 1.变量2.类型3.函数3.1 New3.2 Is简介函数签名核心功能示例代码使用场景注意事项小结 3.3 As简介函数签名核心功能示例代码使用场景注意事项小结 3.4 Unwrap简介函数签名核心功能使用示例使用场景注意事项小结 3.5 Join简介函数签名核心功能使用场景注意事项小结 4.小…

Java File 类详解

Java File 类详解 File 类是 Java 中用于表示文件和目录路径名的抽象类&#xff0c;位于 java.io 包中。它提供了丰富的 API&#xff0c;用于操作文件系统&#xff0c;包括创建、删除、重命名、查询文件属性等功能。 1. File 类核心知识点 &#xff08;1&#xff09;构造方法…

基于javaweb的SpringBoot儿童爱心管理系统设计与实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论文…