『数据结构与算法』之时间复杂度与空间复杂度,看这一篇就够啦

⭐️大一新生小何,还在学习当中,欢迎交流指正~

 

前言

     复杂度分析是数据结构和算法中最重要的知识点,毫不夸张的说,这就是数据结构与算法学习的核心所在。你学会了它你就入的了门,你学不会它,你就永远不知道门儿在哪。学习数据结构与算法的第一课,一定要选复杂度分析,所以,在我看来,这是数据结构与算法中最重要的知识点,且不接受任何反驳


一、复杂度分析

它的出现是想着花更少的时间和更少的存储来解决问题

为了“更少的时间和更少的存储”,复杂度分析为此而生。
简单来说,就是你需要提前写好算法代码和编好测试数据,然后在计算机上跑,通过最后得出的运行时间判断算法时效的高低,这里的运行时间就是我们日常的时间

事后统计法太依赖计算机的软件和硬件等性能。代码在 不同处理器运算速度不同,更不用说不同的操作系统、不同的编程语言等软件方面,就算是在同一台电脑上,用的所有的东西都一样,内存的占用或者是 CPU 的使用率也会造成运行时间的差异

可以看出,我们需要一个不依赖于性能和规模等外力影响就可以估算算法效率、判断算法优劣的度量指标,而复杂度分析天生就是干这个的,这也是为什么我们要学习并必须学会复杂度分析!

二、时间复杂度

1.大o表示法

时间复杂度,也就是指算法的运行时间

大 O 表示法,表示的是算法有多快。(也读作big O)

这个多快,不是说算法代码真正的运行时间,而是代表着一种趋势,一种随着数据集的规模增大,算法代码运行时间变化的一种趋势。用O表示时间复杂度,一般记作 O(f(n))。

有T(n) = O(f(n)),其中 f(n) 是算法代码执行的总步数,也叫操作数。

n 作为数据集大小,它可以取 1,10,100,1000 或者更大更大更大的数,到这你就会发现一件很有意思的事儿,那就是当数据集越来越大时,你会发现代码中的某些部分“失效”了。

当 n = 1000 时,1 + 2n = 2001,当 n = 10000 时,1 + 2n = 20001,当这个 n 持续增大时,常数 1 和系数 2 对于最后的结果越来越没存在感,即对趋势的变化影响不大。所以不要低阶项只要高阶项,而且忽略高阶项上的系数。

所以对于我们这段代码样例,最终的 T(n) = O(n)。

2.时间复杂度分析

首先看一个例子

O(n)

for(i=0; i<n; i++)
{
   j = i;
   j++;
}

由公式,f(n)表示每行代码执行次数之和,与O成正比例关系,可以知道这段代码的时间复杂度为O(n)。此外,如果n无限大的时候,T(n) = time(1+2n)中的常量1就没有意义了,倍数2也意义不大。因此直接简化为T(n) = O(n) 就可以了。

这里列举一些常见的时间复杂度

时间复杂度f(n) 举例
常数复杂度O(1)1
对数复杂度O(logn)logn + 1
线性复杂度O(n)n + 1
线性对数复杂度O(nlogn)nlogn + 1
k 次复杂度O(n²)、O(n³)、....n² + n +1 
指数复杂度O(2^n)2^n + 1
阶乘复杂度O(n!)n! + 1

 上表的时间复杂度由上往下依次增加

这里选取一些常见的进行讲解

O(1) 

O(1) 是常数时间复杂度。

在这你要先明白一个概念,不是只执行 1 次的代码的时间复杂度记为 O(1),只要你是常数,像 O(2)、O(3) ... O(100000) 在复杂度的表示上都是 O(1)。

n = 10000
xiaohe = n / 2 
print(xiaohe)

比如像上面这段代码,它运行了 3 次,但是它的时间复杂度记为 O(1),而不是 O(3)。

因为无论 n 等于多少,对于它们每行代码来说还是只是执行了一次,代码的执行时间不随 n 的变化而变化。

O(logn)

O(logn) 是对数时间复杂度。首先我们来看一段代码:

int i;
while(i<n)
{
    i=i*2
}

上面的代码翻译一下就是初始化 i = 1, 乘上多少个 2 以后会 ≥ n。

假设需要 x 个,那么相当于求:

所以上述代码的时间复杂度应该为 O(log2n)。

但是对于对数复杂度来说,不管你是以 2、3 为底,还是以 10 为底,通通记作 O(logn),这是为什么呢?

这就要从对数的换底公式说起了

根据换底公式,log2n 可以写成:

而对于时间复杂度来说:

所以在对数时间复杂度中我们就忽略了底,直接用 O(logn) 来表示对数时间复杂度。

 O(nlogn)

直接对上面的代码进行修改

for(hhh=1; hhh<n; hhh++)
{
    i = 1;
    while(i<n)
    {
        i = i * 2;
    }
}

 其实很好懂,线性对数阶O(nlogN)就是将复杂度为O(logn)的代码循环n遍,复杂度即n*O(logN)

,也就是 O(nlogn)啦

 O(n^2)

这个就很好理解啦,即把O(n)的代码再循环一遍,这段代码是两层循环嵌套啦

for(i=0;j<n;i++)
{
    for(j=0;j<n;j++)
    {
        m=j;
        m++;
    }
}

它的时间复杂度就是 O(n*n),即 O(n²)

同理,O(n^3)就相当于三层循环

最好情况时间复杂度

 最好情况就是在最理想的情况下,代码的时间复杂度。

典例:冒泡排序

如果将数组里的这些数从小到大排序,

首先,列举一般情况

视频笔记

 最好的情况:

这个时候对应的时间复杂度 O(1) 就是这段代码的最好情况时间复杂度。

最坏情况时间复杂度

最坏情况就是在最差的情况下,代码的时间复杂度。 

最坏的情况: 

 还是从小到大排列

这个时候对应的时间复杂度 O(n) 是这段代码的最坏情况时间复杂度。

平均情况时间复杂度

 大多数情况下,代码的执行情况都是介于最好情况和最坏情况之间,所以又出现了平均情况时间复杂度,

最好情况,反应最理想的情况,怎么可能天上天天掉馅饼,没啥参考价值。

平均情况,这倒是能反映算法的全面情况,但是一般“全面”,就和什么都没说一样,也没啥参考价值。

最坏情况,干啥事情都要考虑最坏的结果,因为最坏的结果提供了一种保障,触底的保障,保障代码的运行时间这个就是最差的,不会有比它还差的。

所以,不需要纠结各种情况,算时间复杂度就算最坏的那个时间复杂度即可。

三、空间复杂度

1.空间复杂度分析

其实,空间复杂度相较于时间复杂度来说,需要掌握的内容不多,它反映的也是一种趋势,只不过这种趋势是代码运行过程中临时变量占用的内存空间。

空间复杂度记作 S(n),表示形式与时间复杂度一致。

程序执行所需要的临时空间不随着某个变量n的大小而变化,即此算法空间复杂度为一个常量,可以表示为 O(1)

来段j简单的代码

int m=1,n=1;
int s;
s=m+n;

由于代码中的量不随数据变化,很直观反映出其空间复杂度为 S(n)=O(1)

2.常见空间复杂度

 O(1)、O(n)、O(n²)是常见的空间复杂度

O(1)如上

O(n)

如果是个一维数组,传入数据n个,那么一维数组的空间复杂度就是O(N),

对于递归来说,递归的深度就是空间复杂度的最大值,深度如果是n,那么空间复杂度就是O(N)
 

O(n²)

如果是个二维数组,同理啦,传入数据n个,那么二维数组的空间复杂度就是O(N)。

当然啦,可以参考这个图


总结

  • 本文部分参考了知乎上的资料和左程云大神的视频,希望对大家有帮助
  • 图片部分用的是 风口浪尖上的猪 的哈哈,强烈推荐大家去看他在知乎上的精彩回答
  • 如果有什么疑问或者错误啦,希望大家在评论区提出呀,谢谢!
  • 一起加油鸭冲冲冲~
  • 48
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 78
    评论
很高兴为您提供学生成绩管理系统的算法数据结构的课程设计。本课程设计旨在帮助学生深入了解算法数据结构的应用,同时实现一个简单的成绩管理系统。 一、课程设计要求 设计一个学生成绩管理系统,能够实现以下功能: 1. 输入学生信息:包括学号、姓名、班级等信息。 2. 输入学生的考试成绩:包括数学、语文、英语三门科目的成绩。 3. 查询学生信息:可以根据学号或姓名查询学生信息。 4. 查询学生成绩:可以根据学号或姓名查询学生成绩。 5. 统计学生的总分、平均分、最高分、最低分等信息。 6. 对学生成绩进行排序:可以按总分、数学、语文、英语等科目进行排序。 二、算法数据结构设计 为了实现上述功能,我们需要选择合适的算法数据结构。以下是本系统中所使用的算法数据结构: 1. 数据结构 本系统中使用结构体来存储学生信息和成绩信息。定义如下: ``` struct student{ char id[20]; //学号 char name[20]; //姓名 char className[20]; //班级 float math; //数学成绩 float chinese; //语文成绩 float english; //英语成绩 float totalScore; //总分 }; ``` 2. 算法 (1)查询学生信息 本系统中使用二分查找算法来查询学生信息。由于学号和姓名是唯一的,因此可以根据学号或姓名进行查找。二分查找的时间复杂度为O(logn),效率较高。 (2)查询学生成绩 查询学生成绩同样使用二分查找算法。与查询学生信息不同的是,查询学生成绩需要先查询学生信息,然后再输出成绩信息。 (3)排序 本系统中使用快速排序算法来对学生成绩进行排序。快速排序的时间复杂度为O(nlogn),效率较高。 (4)统计 统计学生总分、平均分、最高分、最低分等信息时,可以使用遍历算法来实现。时间复杂度为O(n),效率较高。 三、C语言代码实现 以下是本系统的C语言代码实现。包括头文件、全局变量、函数声明和主函数。 1. 头文件 ``` #include <stdio.h> #include <stdlib.h> #include <string.h> ``` 2. 全局变量 ``` #define MAX_STUDENT_NUM 100 //最大学生数 struct student stu[MAX_STUDENT_NUM]; //学生信息数组 int studentNum = 0; //学生数目 ``` 3. 函数声明 ``` void inputStudentInfo(); //输入学生信息 void inputStudentScore(); //输入学生成绩 void queryStudentInfo(); //查询学生信息 void queryStudentScore(); //查询学生成绩 void sortStudentScore(); //排序学生成绩 void calculateStudentScore();//统计学生成绩 void printStudentInfo(struct student stu); //输出学生信息 void printStudentScore(struct student stu); //输出学生成绩 ``` 4. 主函数 ``` int main(int argc, char *argv[]) { int choice; //选择功能 while(1){ printf("请选择功能:\n"); printf("1.输入学生信息\n"); printf("2.输入学生成绩\n"); printf("3.查询学生信息\n"); printf("4.查询学生成绩\n"); printf("5.排序学生成绩\n"); printf("6.统计学生成绩\n"); printf("0.退出系统\n"); scanf("%d",&choice); switch(choice){ case 0: return 0; case 1: inputStudentInfo(); break; case 2: inputStudentScore(); break; case 3: queryStudentInfo(); break; case 4: queryStudentScore(); break; case 5: sortStudentScore(); break; case 6: calculateStudentScore(); break; default: printf("输入错误,请重新输入!\n"); break; } } return 0; } ``` 5. 具体函数实现 (1)输入学生信息 ``` void inputStudentInfo(){ if(studentNum >= MAX_STUDENT_NUM){ printf("学生数目已达到最大值,无法继续添加!\n"); return; } printf("请输入学生信息:\n"); printf("学号:"); scanf("%s",stu[studentNum].id); printf("姓名:"); scanf("%s",stu[studentNum].name); printf("班级:"); scanf("%s",stu[studentNum].className); studentNum++; } ``` (2)输入学生成绩 ``` void inputStudentScore(){ char id[20]; int i; printf("请输入学生学号:"); scanf("%s",id); for(i=0;i<studentNum;i++){ if(strcmp(stu[i].id,id) == 0){ printf("请输入学生成绩:\n"); printf("数学:"); scanf("%f",&stu[i].math); printf("语文:"); scanf("%f",&stu[i].chinese); printf("英语:"); scanf("%f",&stu[i].english); stu[i].totalScore = stu[i].math + stu[i].chinese + stu[i].english; printf("学生成绩录入成功!\n"); return; } } printf("未找到该学生,请重新输入!\n"); } ``` (3)查询学生信息 ``` void queryStudentInfo(){ char key[20]; int i; printf("请输入要查询的学生学号或姓名:"); scanf("%s",key); for(i=0;i<studentNum;i++){ if(strcmp(stu[i].id,key) == 0 || strcmp(stu[i].name,key) == 0){ printStudentInfo(stu[i]); return; } } printf("未找到该学生,请重新输入!\n"); } ``` (4)查询学生成绩 ``` void queryStudentScore(){ char key[20]; int i; printf("请输入要查询的学生学号或姓名:"); scanf("%s",key); for(i=0;i<studentNum;i++){ if(strcmp(stu[i].id,key) == 0 || strcmp(stu[i].name,key) == 0){ printStudentInfo(stu[i]); printStudentScore(stu[i]); return; } } printf("未找到该学生,请重新输入!\n"); } ``` (5)排序学生成绩 ``` void sortStudentScore(){ int i,j; struct student temp; printf("请选择排序方式:\n"); printf("1.按总分排序\n"); printf("2.按数学成绩排序\n"); printf("3.按语文成绩排序\n"); printf("4.按英语成绩排序\n"); scanf("%d",&i); switch(i){ case 1: for(i=0;i<studentNum-1;i++){ for(j=0;j<studentNum-i-1;j++){ if(stu[j].totalScore < stu[j+1].totalScore){ temp = stu[j]; stu[j] = stu[j+1]; stu[j+1] = temp; } } } break; case 2: for(i=0;i<studentNum-1;i++){ for(j=0;j<studentNum-i-1;j++){ if(stu[j].math < stu[j+1].math){ temp = stu[j]; stu[j] = stu[j+1]; stu[j+1] = temp; } } } break; case 3: for(i=0;i<studentNum-1;i++){ for(j=0;j<studentNum-i-1;j++){ if(stu[j].chinese < stu[j+1].chinese){ temp = stu[j]; stu[j] = stu[j+1]; stu[j+1] = temp; } } } break; case 4: for(i=0;i<studentNum-1;i++){ for(j=0;j<studentNum-i-1;j++){ if(stu[j].english < stu[j+1].english){ temp = stu[j]; stu[j] = stu[j+1]; stu[j+1] = temp; } } } break; default: printf("输入错误,请重新输入!\n"); break; } printf("学生成绩排序成功!\n"); } ``` (6)统计学生成绩 ``` void calculateStudentScore(){ int i; float total = 0.0,avg = 0.0,max = 0.0,min = 100.0; for(i=0;i<studentNum;i++){ total += stu[i].totalScore; if(stu[i].totalScore > max) max = stu[i].totalScore; if(stu[i].totalScore < min) min = stu[i].totalScore; } avg = total / studentNum; printf("学生总数:%d\n",studentNum); printf("总分:%4.2f 平均分:%4.2f 最高分:%4.2f 最低分:%4.2f\n",total,avg,max,min); } ``` (7)输出学生信息 ``` void printStudentInfo(struct student stu){ printf("学号:%s 姓名:%s 班级:%s\n",stu.id,stu.name,stu.className); } ``` (8)输出学生成绩 ``` void printStudentScore(struct student stu){ printf("数学:%4.2f 语文:%4.2f 英语:%4.2f 总分:%4.2f\n",stu.math,stu.chinese,stu.english,stu.totalScore); } ``` 四、课程设计计算说明书 本课程设计中所使用的算法数据结构均具有较高的效率和可靠性。根据算法时间复杂度空间复杂度,可以对系统的性能进行计算和评估。 1. 时间复杂度 本系统中所使用的算法时间复杂度如下: 输入学生信息:O(1) 输入学生成绩:O(n) 查询学生信息:O(logn) 查询学生成绩:O(logn) 排序学生成绩:O(nlogn) 统计学生成绩:O(n) 因此,本系统的时间复杂度为O(nlogn),效率较高。 2. 空间复杂度 本系统中所使用的数据结构空间复杂度如下: 学生信息数组:O(n) 因此,本系统的空间复杂度为O(n),较为合理。 综上所述,本课程设计具有高效、可靠、合理的特点,能够满足学生成绩管理的基本需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 78
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超级小何

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值