数据结构与算法(Python)-一般概念和算法效率分析

Posted The fool

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构与算法(Python)-一般概念和算法效率分析相关的知识,希望对你有一定的参考价值。

写在前面

前面学习完了Python基础内容后,从本节开始正式学习数据结构与算法相关内容。这是一个比较复杂的主题,一般分为初级、高级、以及专门的算法分析三个阶段来学习,因此我们也需要循序渐进。本节主要熟悉数据结构与算法中一般概念,然后熟悉算法效率分析的大O记法,知识结构如下图所示:

什么是算法?

1)算法的定义

算法(Algorithm),指的是对特定问题求解步骤的一种描述。
在数学上,它是运算步骤的有限序列,每一步代表执行某种运算。例如,手动计算两个整数和的算法描述为: 将两个数字对齐写在纸上,然后从个位开始,逐位求和,遇到和大于10则向高位进位,最终计算出两个数字之和。

在计算机中,算法是指令的有限序列,每条指定代表一个或者多个操作。例如,在12306网站完成车票查询、车票订购等任务,在计算机上都是由一系列算法实现。

在利用计算机求解问题的过程中,我们首先对问题进行建模,构造合适的算法,然后编写相关程序,流程如下(来自Introduction to Algorithm):

2)算法的5大特性

对于一个算法,有5大特性,列出如下:

  • 输入 一个算法有零个或者多个外部输入,注意可以没有输入。

  • 输出 一个算法有一个或者多个输出,注意算法必定有某种形式的输出。

  • 有穷性(Finiteness) 算法必须在有限步骤内完成。

  • 确定性(Definiteness ) 算法中每条指定必须没有二义性(只有一种解释),在任何条件下,算法只有唯一的一条执行路径,对于相同的输入只能得到相同的输出。

  • 可行性(Effectiveness ) 算法中描述的操作都可以通过已经实现的基本操作执行有效次实现,具有可行性。

3) 算法评价的因素

解决同一个问题有不同的方法,这些方法之间如何比较和选择成为一个关键。

例如去不同城市,可以选择的交通方式有火车、轮船、飞机、自驾、客运大巴、拼车等多种,这些不同的旅行方式,在舒适度、价钱、时间、安全等方面各有不同,需要从多个角度比较和选择。

评价算法好坏也有各种因素,主要包括下面几个因素:

  • 正确性 是否正确地解决了问题?

  • 可读性 算法主要是人来编写,其次才是机器执行。是否容易理解成为实现和维护的关键。

  • 实现难度 算法是否容易实现?

  • 存储开销 算法消耗的内存、外存储空间合理吗?

  • 执行时间 算法执行时耗时能接受吗?

  • 健壮性 程序遇到非预期输入能否做出合理反应? 例如简单的计算程序,遇到除0操作时,应该提醒用户错误,而不是程序崩溃掉。

什么是数据结构?

1)抽象数据类型

利用计算机求解问题的首要步骤是对问题进行建模,在建模的过程中,我们需要考虑到数据的输入、处理、输入等内容,算法描述了操作这些数据的具体流程,但是如何表示和存储问题模型中的数据则需要选择或者重新设计一种有利的结构。
抽象数据类型(Abstract Data Types,ADT),是一种理论上的概念,它从逻辑层面,描述了可能值范围、允许的操作以及操作的行为表现。ADT与具体的实现细节无关(implementation-independent )。例如整数,是一种ADT,它可能的值包括-1,0,1…,允许的操作包括加减乘除,以及大于、小于比较等。这些是数学上的模型,与在计算中具体如何表示无关。

2)数据结构

ADT是一种理论上的数学模型,而数据结构则是计算机上对这个抽象数据类型的实现,是实现层面的概念,由具体计算机语言以及这个语言的基础类型来实现。

3)ADT与数据结构的区别

从上面的定义可以看出了它们之间的差别。例如栈(Stack)是一种ADT,定义了它是先进后出的结构,支持的操作包括:入栈(push)、出栈(pop)、查看栈顶元素(top)、判断栈是否为空(empty)等4种操作。在计算上可以通过数组实现,称为ArrayStack,或者通过链表实现,称为LinkListStack。这两种具体实现称之为栈的数据结构。

算法效率评判标准

上面提到了,如果我们评价交通工具,我们可能会选择舒适度、时间、安全、价格等标准进行评判,与此类似,评判一个算法好坏也需要一些标准。

对一个算法进行空间和时间复杂度分析时,可以通过执行完程序后进行统计分析(事后统计方法),也可以在未执行程序时就进行理论分析(事前估算分析估计方法)。对于同一个算法,在不同的机器上执行,受到处理器、机器字长、存储空间、指令集等的影响,运行时间存在差异,例如运行在“天河一号”超级计算机和普通PC上的程序,运行时间就可能大不相同;同一个算法,即使利用同一台机器来运行程序,但采用C或者Ada编写的程序就比用Basic或者Lisp编写的快约20倍。因此,事后统计分析的方法很多时候并不可靠(为特定设备编写的程序进行性能比较除外),因此人们常常采用事先分析估算的方法。

既然事先估算方法,并没有实际执行程序,使用绝对单位的字节大小或者时间长度,显然是不可能的了,应该使用某种理论上的抽象标准。在上面我们提到了诸如可读性、正确性等因素,这些因素是每个好的算法都必须具备的,这些因素没有区分度,真正具有区分度的因素是空间复杂度(Space Complexity)和时间复杂度(Time Complexity)两个标准。这两个复杂度,一般随着问题输入的数据量,即与问题规模n,成某种函数关系,例如时间复杂度可以表示为:
T(n)=f(n)

在寻求这个函数关系时,我们首先找出一种被作为基本操作(Elementary operation)的运算,估算它的执行次数与n的关系。所谓基本操作指的是算法中对时间有着关键影响,与问题规模成正比的操作。例如检查一个元素x是否在一组数字a中,比较x与a中某个元素值是否相等的操作,就可以视为基本操作。

def find_in_array(array,val):
    """
    naive search algorithm
    :param array: input elements array
    :param val:  the value to search
    :return: index if found or -1
    """
    for i, x in enumerate(array):
        if x == val:   # 基本操作
           return i
    return -1

在上面的查找过程中,我们会遇到3种情形:

  • 最坏情况下(worst-case) 要查找的元素在数组最后一个位置 T(n)=n
  • 平均情况下(average case) 假定每个元素被查找的概率相同,则平均查找时需要的比较次数为: T(n)=ni=11ni=1+n2
  • 最好情况下(best-case) 要查找的元素在数组第一个位置 T(n)=1

下面我们来重点熟悉时间复杂度的大O记法。

渐进分析法

1)什么是渐进分析法?

渐进分析法(Asymptotic Analysis)的目标是寻找到问题处理的时间与问题规模之间,随着问题规模变大时的一种上限和下限关系,通过上限我们了解到算法最坏情况,通过下限了解到算法最好的情况。

大O定义: 假设 f(n) 是算法时间复杂度的表示,而 g(n) 是其中最具影响的因子,如果: f(n)<=Cg(n) ,对于所有的 n>=n0,C>0,n0>=1 都成立,则我们可以将 f(n) 记为: f(n)=O(g(n))

上面定义中,最具影响的因子,是复杂度表达式中,对结果影响最大的部分,例如: f(n)=5n2+2n+1 ,那么当n增大时,显然 n2 决定了 f(n) 的大小,这个因子就是上面定义中的 g(n)

f(n) g(n) 关系如下图所示:

例如, f(n)=3n+2 , g(n)=n ,令: f(n)<=Cg(n)3n+2<=Cn ,求解这个不定方程,可以得到: C=4,n>=2 时: f(n)<=Cg(n) 成立,因此有: f(n)=O(n)

除了大O记法,还有一个表达复杂度下界的 f(n)=Ω(g(n)) 记法,和表达复杂度平均界限的 f(n)=Θ(g(n)) 记法,在算法分析中,我们通常考虑的是算法的上界,即最坏情况下的表现,对于另外两种记法,感兴趣地可以参考Asymptotic Notation,这里不再详细展开。

2)渐进分析法为什么有效?

引用一个来自[1]的例子,假设时间复杂度表示为:
f(n)=n2+100n+logn10+1000 ,当n逐渐增大时,我们统计如下表所示:

从这个表可以看出,当n=1,10时,100n和1000所占比重较大;当n=100时,n平方和100n所占比重相同;当n>100后,n平方所占比重越来越大,到最后n=100000时,n平方接近100%。这就说明,使用n平方来近似表达f(n)的计算复杂度是完全可行的,也就是 f(n)=O(n2)以上是关于数据结构与算法(Python)-一般概念和算法效率分析的主要内容,如果未能解决你的问题,请参考以下文章

数据结构C语言版 —— 时间复杂度&空间复杂度概念和计算

人工智能算法效率怎么算

数据结构 Java 版时间和空间复杂度

数据结构与算法学习笔记

——数据结构之绪论

数据结构与算法之基本概念