紫薇星上的数据结构(10)

终于来到最后一部分了,算法,这篇文章的出现也意味着这个系列就结束了,向着最后的胜利冲冲冲!


算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题。不同的算法可能用不同的时间、空间或效率来完成同样的任务。一个算法的优劣可以用空间复杂度与时间复杂度来衡量。

这一部分我们要整理的算法一般都是前人已经完善好的,大家如果不能理解其原理,那就学会如何使用就可以了。

简单来说算法就是解决特定问题的求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令都表示一个或者多个操作。这里要注意:同一个问题可能有多种不同的解决算法;没有一个通用算法可以解决所有问题。

一个算法应该具有以下五个重要的特征

  • 有穷性(Finiteness):算法的有穷性是指算法必须能在执行有限个步骤之后终止;
  • 确切性(Definiteness):算法的每一步骤必须有确切的定义;
  • 输入项(Input):一个算法有0个或多个输入,以刻画运算对象的初始情况,所谓0个输入是指算法本身定出了初始条件;
  • 输出项(Output):一个算法有一个或多个输出,以反映对输入数据加工后的结果。没有输出的算法是毫无意义的;
  • 可行性(Effectiveness):算法中执行的任何计算步骤都是可以被分解为基本的可执行的操作步骤,即每个计算步骤都可以在有限时间内完成(也称之为有效性)。

同一问题可用不同算法解决,而一个算法的质量优劣将影响到算法乃至程序的效率,算法分析的目的在于选择合适算法和改进算法,一个算法的评价主要从空间复杂度与时间复杂度来衡量。

  • 时间复杂度:算法的时间复杂度是指执行算法所需要的计算工作量。一般来说,计算机算法是问题规模n 的函数f(n),算法的时间复杂度也因此记做:T(n)=Ο(f(n))。因此,问题的规模n 越大,算法执行的时间的增长率与f(n) 的增长率正相关,称作渐进时间复杂度(Asymptotic Time Complexity)
  • 空间复杂度:算法的空间复杂度是指算法需要消耗的内存空间。其计算和表示方法与时间复杂度类似,一般都用复杂度的渐近性来表示。同时间复杂度相比,空间复杂度的分析要简单得多。
  • 正确性:算法的正确性是评价一个算法优劣的最重要的标准。
  • 可读性:算法的可读性是指一个算法可供人们阅读的容易程度。
  • 健壮性:健壮性是指一个算法对不合理数据输入的反应能力和处理能力,也称为容错性。

从网上找张图给大家直观的归纳一下:

而对于算法效率的度量,一般使用事后统计法,就是通过设计好的测试程序和数据,利用计算机计时器对不同算法编制的程序的运行时间进行比较,从而确定算法效率的高低。

事后统计方法的缺陷

  • 必须依据算法实现编制好程序,这通常需要花费大量的实践和精力;
  • 时间必须依赖计算机硬件和软件等环境因素,有时会掩盖算法本身的优劣;
  • 算法的测试数据设计困难,并且程序的运行时间往往还与测试数据的规模有很大关系。

而我们一般在测试自己的小算法的时候不推荐这种方式,因为它需要大量的数据和很长的时间,现在发展的人工智能大多是使用这种方法。

而现在人们也发明了事前估算方法,就是在计算机程序编制前,根据统计方法对算法进行估算,不必关心编写程序使用的语言和这些程序跑在什么计算机上,只关心实现的算法。

决定计算机程序运行时间的因素:

  • 算法采取的策略、方法;
  • 编译产生的代码质量(软件支持);
  • 问题的输入规模:指输入量的多少;
  • 机器执行指令的速度。

在分析程序的运行时间的时候,最重要的是把程序看成独立于程序设计语言的算法或一系列步骤,我们来看一下不同的输入量和执行方式不同对输入规模的影响:

所以输入规模的不同对于算法的考验也是不同的。

函数的渐近增长

那么如何判断两个算法哪个更好呢?我们根据对N次数据进行计算时不同算法进行的次数不同来举个例子:

这样就能看出不同算法在对同样的数据输入进行计算时效率也是不一样的,这里我们就引出一个概念:函数的渐近增长,就是给定两个函数f(n)和g(n),如果存在一个整数N使得对于所有的n>N,f(n)总是比g(n)大,那么我们就说f(n)的增长渐进快于g(n)。

同时我们通过这个例子可以看出:最高次项的相乘的常数并不重要;函数随着n的增长,最高次项的指数越大,结果也会变得增长特别快。所以在判断算法效率的时候,函数中的常数和其他次要项可以忽略,而应该关注最高阶项的阶数。

算法的时间复杂度

  • 算法的时间复杂度就是算法的时间度量,记作: T(n) = O(f(n));
  • T(n)表示语句总的执行次数,它是关于问题规模n的函数;
  • 用O( )来体现算法时间复杂度的记法,我们称之为大O记法;
  • 一般情况下,随着n的增大,T(n)增长最慢的算法为最优算法。

推导大O阶的方法:

  • 用常数1取代运行时间中的所有加法常数;
  • 在修改后的运行次数函数中,只保留最高阶项;
  • 如果最高阶存在且不是1,则去除与这个项相乘的常数

我么给举个例子来看一下如何推导大O阶:

简单来讲就是只看最高阶的项数,我们再来延伸一下:

常数阶的时间复杂度只能为O(1),比如下面这些例子:

int n = 1, m = 2;
m += n;
printf("%d", m);


int a[255];
a[0] = 1;
a[1] = 2;
a[2] = 3;
int sum = a[0] + a[1] + a[2];
printf("%d", sum);


for(int i = 0; i < 100; i++){
    printf("这是一条printf语句");
}

在这些例子中,没有输入N这个变量,只有常数阶,所以他们的时间复杂度都是O(1)。

但是只要输入了变量n,那么就变成了线性阶:

int sum = 0;
for(int i = 1; i < n; i++){
    sum += i * 2;
}
printf("%d", sum);


for(int i = 0; i < n; i++){
    printf("这是一条printf语句");
}
//在这些语句中输入规模都是n,而不是常数,所以是非常数阶


void function(){
     printf("这是一条printf语句");
}
void print(int n){
    for(int i = 0; i < n; i++){
        function();
    }
}
//在上面代码中function函数的时间复杂度为常数阶O(1)
//但是print函数的时间复杂度为线性阶O(n)

所以根据上面的推导方法,线性阶的时间复杂度均为O(n)。

还有稍微难一些的平方阶:

for(int i = 0; i < n; i++){
    for(int j = 0; j < n; i++){
        printf("这是一条printf语句");
    }
}
//时间复杂度为O(n*n)

for(int i = 0; i < n; i++){
    for(int j = 0; i < m; i++){
        printf("这是一条printf语句");
    }
}
//时间复杂度为O(n*m)

for(int i = 0; i < n; i++){
    for(int j = i; j < n; i++){
        printf("这是一条printf语句");
    }
}
//时间复杂度为O(f(n))
//f(n) = n + (n - 1) + (n - 2)+ ... + 1
//     = n * (n + 1) / 2
//     = (n * n) / 2 + n / 2
//时间复杂度为O(f(n)) = O(n*n)

简单来说,平方阶的时间复杂度就是循环体的复杂度乘以该循环次数。

还有复杂的对数阶:

int count = 1;
int sum = 1;
while(count <= n){
    sum *= 2;
    count ++;
}
//这是一种常见的计算2的n次幂的方式
//时间复杂度为O(n)

int count = 1;
while(count <= n){
    count = count * 2;
}
//这时候,每次循环count都会乘2,就离n更近一些
//2的f(n)次 = n
//那么f(n) = log以2为底的n
//时间复杂度为O(log n)

这些时间复杂度都是通过输入规模的不同而不同,总结一下:

 

不管是什么程序,我们将其的执行次数函数推导出来,然后就可以推导出他们的时间复杂度。常用的时间复杂度所消耗的时间从小到大依次排序为:

空间复杂度

  • 算法的空间复杂度通过计算算法所需的存储空间实现;
  • 算法的空间复杂度的计算公式记作: S(n) = O(f(n)),其中n为问题的规模,f(n)为语句关于n所占存储空间的函数;
  • 如果算法执行时所需的辅助空间相对于输入数据量而言是个常数,则称此算法为原地工作,空间复杂度为O(1 )。

我们在判断算法的优劣时,先判断时间复杂度,如果相同再判断空间复杂度。

我们就简单的整理到这里,对于一些优秀的算法如二分法、快速排序、冒泡等大家有兴趣可以自行google或百度查看,这里我们不做过多说明。


最后一部分也写完了,中间可能有些纰漏,大家可以在评论区中告诉我,如果有漏掉的知识点我会再补上。

同时在这里说一下因为开学网课也有一段时间了,以后就不会每天都写文章了,一是没有时间来写,二是不管是新的技术HTML、CSS、Python等还是Java的高阶技术,都不是能简单的整理的,所以我会不定期更新,但每天都会看CSDN,大家的想法和评论我以为会第一时间收到,谢谢大家!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值