目录
一、什么是数据结构与算法
在进行计算机进行数值计算解决实际问题时,一般要经过以下步骤:首先从具体问题中抽象出数学模型,然后设计一个解决此数学模型的算法,再编写数据、进行测试、调试,解决问题。
但是在进行非数值计算问题时,实际问题的数学模型不在是数学方程,而是一些线性表、数和图的数据结构。而数据结构就是一门研究非数值计算程序设计中的操作。
1、什么是数据结构
数据结构是计算机存储、组织数据的方式,相互之间存在一种或多种特定关系的数据元素的集合。比如:对于一些项目的时候,需要在内存中将数据存储起来;那存放数据的方式有很多种(数组存储、链表存储、树、哈希表存储、……)每个方式都有其自身的特点。选择不同的方式进行处理数据就是数据结构研究的主要内容。
2、什么是算法
我们已经知道的一些算法,如:排序、查找、去重等等,总结来看:有一组数据输入经过计算,再将其进行输出,算法就是进行一系列的计算步骤,用来将输入数据转化为输出结果。简单来说,我们再进行排序的时候,可以按照销量、时间、价格进行不同的排序,同一种产品(输入数据)按条件(算法)进行排序(输出数据)就是一种算法的作用。
抖音,将我们的访问记录、停留时间等结合起来,对相关视频进行排序,输出到我们手机界面,也是一种算法的体现。
二、算法的时间复杂度
对于一个算法的优劣,我们认为达到同样的目的,使用的空间越少以及时间越短,则算法越优。 我们再衡量一个算法的优劣时主要有两类方法:1、将算法实现,再测算其时间与空间的开销。这种方法显然不可取,一是要将算法转化为可执行的程序,二是在测量时,结果依赖于计算机的性能。2、采用事前分析估算法,通过计算算法的渐进复杂度来衡量算法的效率;
一个算法执行的时间=所有语句执行时间的总和
语句执行时间的总和=语句的单次执行时间×重复执行的次数
但是,一个语句单次的执行具体时间是与机器的环境有密切相连的关系,无法精确的计算出一个算法实际执行所需要的时间,因此,我们只针对语句执行的次数进行判断,让其代表算法的时间。
2.1时间复杂度的概念:
在计算机科学中,算法的时间复杂度是一个函数(带有未知数的表达式),它定量描述了该算法的运行时间,一个算法执行缩耗费的时间,从理论上说是不能算出来的,只有你把程序放在机器上进行跑,才知道时间,但是这样十分麻烦,所以才有了时间复杂度这个分析方式,一个算法所花费的时间与其中语句执行的次数成正比;算法中的基本操作的执行次数,为算法的时间复杂度。
for(i=1;i<=n;i++)//执行次数n+1
for(j=i;j<=n;j++)//执行次数n*(n+1)
{
c[i][j]=0;//执行次数n^2
for(k=1;k<=n;k++)//执行次数n^2*(n+1)
c[i][j]=c[i][j]+a[i][j]*b[k][j];//执行次数n^3
}
该算法的所有语句的执行次数:
f(n)=2n^3+3n^2+2n+1;
对于这种简单的算法,我们可以直接计算出它的所有语句的执行次数,但是对于一些稍微复杂的算法,我们无法计算出,或者是一个十分复杂的函数。因此,为了客观的反映一个算法的执行时间,可以用算法中“基本语句”的执行次数来衡量算法的工作量;
所谓的基本语句是指:算法中重复执行次数和算法的执行时间成正比的语句,它对算法的运行时间贡献最大。
实际中,我们计算时间复杂度,我们其实并不是一定要计算出精确的执行次数,而是只需要大概执行的次数,那么这里我们使用大o的渐进表示法,估算。先算出精确的,在算出对结果影响最大一项。
2.2算法时间复杂度的定义
一般情况下,算法中基本语句重复执行的次数是问题规模n的某个函数f(n);算法的时间量度记作
T(n)=O(f(n))
上述举例的时间复杂度T(n)=(n^3);
大O符号:用于描述函数渐进行为的数学符号.推导大O阶方法:
- 用常数1取代运行时间的所有加法常数;
- 在修改后的运行次数函数中,只保留最高阶项;
- 如果最高阶项存在且不是1,则去除与这个项目相乘的常数,得到的结果就是大O阶;
2.3算法时间复杂度的分析举例
分析算法时间复杂度的基本方法为:找出所有语句中语句执行次数最多的那条语句作为基本语句,计算基本语句的函数f(n),取其数量级,用符号“O”表示。
说明1:若f(n)=an^m+bn^m-1+……c;是一个多项式,则T(n)=O(n^m);在计算时间复杂度时,可以忽略低次幂项与高次幂的系数;
说明2:若算法的执行次数是一个与n无关的常数,不管其有多大,算法的时间复杂度都为T(n)=O(1);称常量阶;
举例1:
{x++;s=0;}
两个语句的执行次数都为1,所以算法的时间复杂度:T(n)=O(1);
举例2:
for(i=1;i<1000:i++)
语句执行次数为1000,与n无关, 所以算法的时间复杂度:T(n)=O(1);
举例3:
for(i=1;i<n;i++)
语句执行次数f(n)=n,所以算法的时间复杂度:T(n)=O(n);
举例4:
int x=1;
for(i=1;i<=n;i++)
for(j=1;j<=i;j++)
for(k=1;k<=j;k++)
x++;
语句执行次数最多的是x++,语句执行次数f(n)=[n(n+1)(2n+1)/6+n(n+1)/2]/2,所以算法的时间复杂度:T(n)=O(n^3);
2.4最好、最坏和平均复杂度
对于一些问题的算法,其基本语句的执行次数不仅与规模相关,还依赖于其他因素;
例如:在一个一维数组a中顺序查找某个值等于f的元素,并返回其所在位置
for(i=0;i<n;i++)
{
if(a[i]==e)
return i+1;
}
可以看出,if语句的执行次数与数组中e的位置有很大关系,最好的情况就是,数组的第一个元素是e,这样只需要执行1次,最坏的情况就是数组中没有e,需要执行n次,一般情况下,可以用最好情况与最坏情况的平均值即n/2作为度量。
此例说明:算法的时间复杂度不仅同问题的规模有关,还与问题的其他因素有关,称算法在最好情况下的时间复杂度为最好时间复杂度,指的是算法计算量可能达到的最小值;称算法在最坏情况下的时间复杂度为最坏时间复杂度,指的是算法计算量可能达到的最大值。算法的平均时间复杂度是指在所有可能情况下,算法计算的加权平均值。
三、算法的空间复杂度
3.1算法空间复杂度的概念与定义
关于算法的存储空间的需求,类似于算法的时间复杂度,我们采用渐进空间复杂度作为算法所需存储空间的量度,简称空间复杂度,记作:
S(n)=O(f(n))
一般情况下,一个程序在机器上执行时,除了寄存器本身所用的指令、常数、变量外,还需要对一些数据进行辅助存储空间,对于输入数据所占的具体存储量取决于问题本身,与算法无关,这样只需要分析该算法实现时所需要的辅助空间即可。
空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时额外占用存储空间大小的程度。
例如:将一个数组逆序排列。
算法1:
for(i=0;i<n/2;i++)
{
t=a[i];
a[i]=a[n-i-1];
a[n-i-1]=t;
}
算法2:
for(i=0;i<n;i++)
b[i]=a[n-i-1];
for(i=0;i<n;i++)
a[i]=b[i];
算法1仅需要借助一个变量t,与问题规模n无关,其空间复杂度S(n)=O(1);
算法2仅需要借助一个大小为n的数组b,其空间复杂度S(n)=O(n);
3.2算法空间复杂度的分析举例
举例1、计算冒泡排序的空间复杂度
void bubble(int *a,int n)
{
for(size_t end =n;end>0;--end)
{
int exchange =0;
for(size_t i=1;i<end;++i)
{
if(a[i-1]>a[i])
{
Swap(&a[i-1],&a[i]);
exchange =1;
}
}
if(exchange ==0)
break;
}
}
在分析冒泡排序的空间复杂度时,不能仅通过代码进行判断,还需要结合冒泡排序的思想进行判断。
定义了一个变量end和一个变量n,都是一个空间重复使用,所以其空间复杂度为 S(n)=O(1);1代表的是常数个,不是一个。
举例2:计算斐波那契数列的空间复杂度
long long *Fibonacci(size_t n)
{
if(n==0)
return NULL;
long long *fibArry=(long long*)malloc((n+1)*sizeof(long long));
fibArry[0]=0;
fibArry[1]=1;
for(i=2;i<=n;++i)
{
fibArry[i]=fibArry[i-1]+fibArry[i-2];
}
return fibArry;
}
构造了一个n+1大小的数组,所以其空间复杂度为 S(n)=O(n);
举例三:计算阶乘递归Fac的空间复杂度
long long Fac(size_t N)
{
if(N==0)
return 0;
return Fac(N-1)*N
}
每调用一次Fac函数都需要创建一个空间(栈帧),故空间复杂度S(n)=O(n);
本节主要内容是,让大家认识到算法时间复杂度与空间复杂度的相关概念,并且学会对于一些简单的算法能够判断其空间复杂度与时间复杂度的大小,后面对于一些较为难的算法的计算,还需要多加练习。
创作不易,还请大家多多点赞支持。