2.算法——非科班大一学生的数据结构自学笔记

文章介绍了算法的基本概念,包括算法的定义、四个基本特性(输出、有穷性、确定性和可行性),以及算法设计的要求。重点讨论了算法效率的度量,如时间复杂度和空间复杂度,通过实例解释了如何分析算法的时间复杂度,并给出了不同阶的时间复杂度比较。此外,提到了在最坏情况和平均情况下的算法分析。
摘要由CSDN通过智能技术生成

第2章 算法

在这里插入图片描述


2.1 算法定义

算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作

为了解决某个或某类问题,需要把指令表示成一定的操作序列。操作序列包括一组操作,每个操作都完成特定的功能,这就是算法了。


2.2 算法的特性

1. 输出、输出

算法具有零个或多个输入,至少有一个或多个输出。

​ 对于绝大多数算法来说输入参数都是必要的,但对于如打印 ‘hello world’ 这样的代码不需要任何输入参数。输出的形式可以是打印或返回值。

2. 有穷性

有穷性指算法在执行有限的步骤后,自动结束而不会出现无限循环,并且每个步骤在可接受的时间内完成。

3. 确定性

算法的每一步骤都具有确定的含义,不会出现二义性。

​ 算法在一定条件下只有一条执行路径,相同的输入只能有唯一的输出结果。

4.可行性

算法的每一步都能够通过执行有限次数完成


2.3 算法设计的要求

1.正确性
  • 没有语法错误
  • 对于合法的输入数据能够产生满足要求的输出结果
  • 对于非法的输入数据能够得出满足规格说明的结果
2. 可读性

算法设计的另一目的是为了便于阅读、理解和交流

3. 健壮性

当输入数据不合法时,算法也能做出相关处理,而不是产生异常或莫名其妙的结果

4. 高效率和低存储量

设计算法应该尽量满足时间效率高和存储量低的需求


2.4 算法效率的度量方法

一个程序的运行时间,依赖于算法的好坏 和问题的输入规模(输入量的多少)

int i, sum = 0, n = 100;         
for (i = 1; i <= 100; i++) {
    sum += i;
}
printf("%d", sum);          /* 共执行1+(n+1)+n+1=2n+3次 */
int sum = 0, n = 100;
sum = (1 + n) * n / 2;
printf("%d", sum);           /* 共执行1+1+1=3次 */

​ 假设两个算法的输入规模都是n,算法A要做2n+3次操作,算法B要做3n+1次操作。此时给出这样的定义:输入规模n在没有限制的情况下,只要超过一个数值N,这个函数就总是大于另一个函数,我们称函数是渐近增长的。

函数的渐近增长:给定两个函数f(n)和g(n),如果存在一个整数N,使得对于所有n>N,f(n)>g(n),那么我们说f(n)的增长渐近快于g(n)。

判断一个算法的效率时,函数中的常数和其他次要项常常可以忽略,而更应该关注主项(最高阶项)的阶数。

事前分析估算方法:在计算机程序编制前,依据统计方法对算法进行估算。

​ 某个算法,随着n增大,它会越来越优于(差于)另一算法,这就是事前估算方法的理论依据,通过算法时间复杂度来估算算法时间效率。

于是我们可以得出一个结论:判断一个算法好不好,我们只通过少量的数据是不能做出准确判断的,如果我们可以对比算法的关键执行次数函数的渐近增长性,基本就可以分析出:某个算法,随着n增大,它会越来越优于(差于)另一算法。


2.5 算法时间复杂度

算法时间复杂度定义

进行算法分析时,语句==总的执行次数T(n)==是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级,算法的时间复杂度记作T(n) = O(f(n))

T(n) = O(f(n)) 表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度(简称为时间复杂度),其中f(n)是问题规模n的某个函数。

​ 这样用大写O( )来体现算法时间复杂度的记法称为大O记法

一般情况下,随着n的增大,T(n)增长最慢的算法为最优算法。

推导大O阶(算法时间复杂度)方法
1. 用常数**1**取代运行时间中的所有**加法常数**。
1. 在修改后的运行次数函数中,**只保留最高阶项**。
1. 如果**最高阶项存在且其系数不是1**,则**去除与这个项相乘的系数**。

​ 由此得到的结果就是大O阶(算法时间复杂度)

例题
  • 常数阶
int sum = 0, n = 100;   /* 执行1次 */
sum = (1 + n) * n / 2;  /* 执行1次 */
printf("%d", sum);      /* 执行1次 */

​ 计算大O阶:运行次数函数是f(n)=3。根据大O阶推导方法,第一步把常数项3改为1。在保留最高阶项时发现它根本没有最高阶项,因此这个算法时间复杂度为 O(1)。

​ 试想一下,当上面这个算法中,语句 sum=(1+n)*n/2 有10句。但是这两端代码执行的差异与问题的大小(n的大小)无关。执行时间恒定的算法,我们称之为具有O(1)的时间复杂度,又叫常数阶

注意,不管这个常数是多少,我们都记作O(1),而不能是O(3)O(12)等其他任何数字。

​ 对于分支结构而言,无论true/false,执行次数都是恒定的的,所以单纯的分支结构(不包含在循环结构中),其时间复杂度也是O(1)。

  • 线性阶

要分析线性阶算法的复杂度,关键是要分析循环结构的运行情况。

int i;
for (i = 0; i < n; i++) {
       /* 时间复杂度为O(1)的程序步骤序列 */
}

​ 这段代码的时间复杂度为O(n),因为循环体中的代码需要执行n次。

  • 对数阶
int count = 1;
while (count < n) {
    count = count * 2;
    /* 时间复杂度为O(1)的程序步骤序列 */
}

​ 计算大O阶: 有多少个2相乘后大于n,则会退出循环。由此2x=n,x=log2n 。所以这个循环的时间复杂度为O(log2n)

  • 平方阶
int i, j;
for (i = 0; i < n; i++) {
    for (j = 0; j < n; j++) {
           /* 时间复杂度为O(1)的程序步骤序列 */
    }
}

​ 这段代码的时间复杂度为O(n2)。若外循环的循环次数改为m,时间复杂度就变为O(m*n)。

循环的时间复杂度等于 循环体的复杂度 × 该循环运行的次数。

int i, j;
for (i = 0; i < n; i++) {
    for (j = i; j < n; j++) {
           /* 时间复杂度为O(1)的程序步骤序列 */
    }
}

​ 计算大O阶:由于当i=0,内循环执行了n次;当i=1,内循环执行n-1次……当i=n-1,内循环执行1次。因此总执行次数为:n+(n-1)+(n-2)+…+1 = n2/2 + n/2 。用大O阶推导方法,第一条,没有加常数,不考虑;第二条,只保留最高阶项,因此保留n2;第三条,去除与这个项相乘的常数,也就是去除1/2,最终这段代码的时间复杂度为O(n2)

int i;
for (i = 0; i < n; i++) {
    function (i);
}
void function (int count) {
    int j;
    for (j = count; j < n; j++) {
           /* 时间复杂度为O(1)的程序步骤序列 */
    }
}

​ 这和上一题相似,知识把嵌套内循环放到了函数中,因此时间复杂度为O(n2)。

n++;                             /* 执行次数为1 */
function (n);                    /* 执行次数为n */
int i, j;
for (i = 0; i < n; i++) {        /* 执行次数为n*n */
    function (i);  
}
for (i = 0; i < n; i++) {        /* 执行次数为(1+n)n/2 */
    for (j = i; j < n; j++) {
           /* 时间复杂度为O(1)的程序步骤序列 */
    }
}

​ 计算:执行次数f(n)=1+n+n2+(1+n)n/2 = 3n2/2 + 3n/2 + 1 ,根据推导大O阶方法,最终这段代码的时间复杂度也是O(n2)。

最坏情况与平均情况

​ 算法分析的时间有着最坏情况与平均情况。平均运行时间是所有情况中最有意义的,因为它是期望的运行时间。但一般我们提到的运行时间都是最坏情况的运行时间,一般我们提到时间复杂度也都指最坏时间复杂度。

常见时间复杂度所耗时间的大小排列

O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)


2.6 算法空间复杂度

​ 算法空间复杂度通过计算算法所需的存储空间实现,算法空间复杂度的计算公式记作:S(n)= O(f(n)),其中n为问题的规模,f(n)为语句关于n所占存储空间的函数。

​ 一般情况下,一个程序在机器上执行时,除了需要存储程序本身的指令、常数、变量和输入数据外,还需要存储对数据操作的存储单元。若输入数据所占空间只取决于问题本身,和算法无关,这样只需要分析该算法在实现时所需的辅助单元即可。若算法执行时所需的辅助空间相对于输入数据量而言是个常数,则称此算法为原地工作,空间复杂度为O(1)。


结语

算法的优劣直接决定了程序运行的效率。我们需要去利用算法分析的工具研究自己写的代码是否效率低下,是不是可以通过优化算法让计算机更加快速高效。 也许,愚公移山固然可敬,但发明炸药和推土机,可能更加实在和聪明。

💙非科班大一学生的Day2数据结构学习打卡 ~ 《大话数据结构》

因为在自学js的过程中感受到了学习数据结构与算法的必要性,决定系统地学习数据结构与算法。在csdn记录从昨天开始的自律寒假 大概

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

LeonardoSya

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

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

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

打赏作者

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

抵扣说明:

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

余额充值