数据结构与算法——2. 数据结构与算法的概念以及复杂度的渐进表示法

一、抽象数据类型与数据结构

计算机科学不仅仅是对计算机的研究,更重要的是研究问题、问题解决过程、问题的解决方案

编程是通过一种程序设计语言,将抽象的算法实现为计算机可以执行的代码的过程,而最终的产物就是程序。

瑞士计算机科学家尼古拉斯·沃斯在1984年获得图灵奖时,说的一句话:程序等于算法加数据结构

程序设计语言需要为算法的实现提供实现“过程”和“数据”的机制:具体表现为“控制结构”(顺序、分支和循环)和“数据类型”。

并且,程序设计语言也提供最基本的数据类型来表示数据,如整数、字符、列表等。

1. 对问题进行抽象

为了控制问题和问题解决过程的复杂度,利用抽象来保持问题的“整体感”,以避免过多的陷入对细节的思考当中去。

这就要求对问题进行建模的时候,对算法所要处理的数据,也要保持与问题本身的一致性(数据要迎合算法),不要有太多与问题无关的细节。

问题解决过程的抽象:

在这里插入图片描述

2. 抽象数据类型(ADT)

我们不光要对“过程抽象”,还要对“数据抽象”。

相对于程序设计语言中基本数据类型,抽象数据类型(ADT:Abstract Data Type)是对数据进行处理的一种逻辑描述,并不涉及如何实现这些处理。

ADT是对数据的一种“封装(Encapsulation)”,尽可能地将实现细节隐藏起来,只将逻辑接口暴露给用户使用,有效的控制了算法的复杂度。

3. 什么是数据结构(Data Structure)

数据结构则是对ADT的具体实现,同一ADT可以通过不同的数据结构来实现。

采用程序设计语言的控制结构基本数据类型来实现ADT所提供的逻辑接口。

对数据实现逻辑层次和物理层次的分离,可以定义复杂的数据模型来解决问题,而不需要立即考虑此模型如何实现。

通常情况下,一个合适的数据结构可以带来最优效率的算法

二、算法和计算复杂性

1. 什么是算法(Algorithm)

用任何一个“有限能行方法”计算模型可以解决的问题,都是“可计算”的!

算法则是以解决问题为目的的,一系列清晰的指令

  • 一个有限指令集;
  • 接收一些输入(有些情况下不需要输入);
  • 产生输出;
  • 一定在有限步骤之后终止;
  • 每一条指令必须:
    • 有充分明确的目标,不可以有歧义
    • 计算机能处理的范围之内
    • 描述应不依赖于任何一种计算机编程语言以及具体实现的手段。

世界上最早的算法:辗转相除法(欧几里得算法),用于求最大公约数。

2. 计算复杂性

虽然很多问题可以通过计算解决,但由于我们的资源(时间/空间)相当有限,所以对于问题的解决需要考虑其可行性如何:

  • 有些问题容易解决,如:基本的四则运算;
  • 有些问题的解决方案我们不是十分满意,比如:表达式求值、排序等;
  • 有些问题的解决方案则会爆炸性地吞噬资源,可行性很低,比如:哈密顿回路、旅行商问题(TSP)等。

3. 算法和计算复杂性

算法研究不同资源约束下的不同解决方案,致力于找到效率最高的方案。

计算复杂性理论研究问题的本质,将问题按照难度分类,不关心具体的解决方案。

三、复杂度的表示方法

1. 复杂度的类别

  • 空间复杂度 S ( n ) S(n) S(n)

    根据算法写成的程序在执行时,占用的储存空间的大小。这个大小往往与输入数据的规模有关。

    空间复杂度过高的算法可能导致使用的内存超限,造成程序的异常中断。

  • 时间复杂度 T ( n ) T(n) T(n)

    根据算法写成的程序在执行时,消耗时间的长度。这个长度往往也与输入数据的规模有关。

    时间复杂度过高的算法可能导致我们在有生之年都等不到运行结果。

在分析一般算法的效率时,我们通常只关心下面两种复杂度:

  • 最坏情况复杂度: T w o r s t ( n ) T_{worst}(n) Tworst(n)

  • 平均复杂度: T a v g ( n ) T_{avg}(n) Tavg(n)

    并且: T w o r s t ( n ) > T a v g ( n ) T_{worst}(n)>T_{avg}(n) Tworst(n)>Tavg(n)

2. 复杂度的渐进表示法

在计算算法复杂度时,我们没有必要去一步步的去数其中的步骤,而是粗略的计算出增长趋势就可以了。于是,就有了复杂度的渐进表示法:

  • T ( n ) = O ( f ( n ) ) T(n)=\text{O}(f(n)) T(n)=O(f(n))表示存在常数 C > 0 C>0 C>0 n 0 > 0 n_0>0 n0>0使得当 n > n 0 n>n_0 n>n0时有 T ( n ) ≤ C ⋅ f ( n ) T(n) \leq C \cdot f(n) T(n)Cf(n)

    O ( f ( n ) ) \text{O}(f(n)) O(f(n))表示 f ( n ) f(n) f(n) T ( n ) T(n) T(n)的某一上界。

  • T ( n ) = Ω ( g ( n ) ) T(n)= \Omega (g(n)) T(n)=Ω(g(n))表示存在常数 C > 0 C>0 C>0 n 0 > 0 n_0>0 n0>0使得当 n > n 0 n>n_0 n>n0时有 T ( n ) ≥ C ⋅ g ( n ) T(n) \geq C \cdot g(n) T(n)Cg(n)

    Ω ( g ( n ) ) \Omega(g(n)) Ω(g(n))表示 g ( n ) g(n) g(n) T ( n ) T(n) T(n)的某一下界。

  • T ( n ) = Θ ( h ( n ) ) T(n)= \Theta (h(n)) T(n)=Θ(h(n))表示同时有 T ( n ) = O ( h ( n ) ) T(n)=\text{O}(h(n)) T(n)=O(h(n)) T ( n ) = Ω ( h ( n ) ) T(n)=\Omega (h(n)) T(n)=Ω(h(n))

    Θ ( h ( n ) ) \Theta (h(n)) Θ(h(n))表示 h ( n ) h(n) h(n)既是 T ( n ) T(n) T(n)的某一上界也是它的某一下界。

以上都是以 T ( n ) T(n) T(n)为例,对于 S ( n ) S(n) S(n)是一样的。

在复杂度中:

  • 对数的底数对复杂度的整体影响影响不大,所以通常都不写;
  • 上界和下界有很多个,但我们只关心能取到的最小上界或最大下界

不同复杂度函数的输出和图像:

不同复杂度函数的输出

不同复杂度函数的图像

四、算法分析窍门

  • 若两段算法分别有复杂度 T 1 ( n ) = O ( f 1 ( n ) ) T_1(n)=\text O(f_1(n)) T1(n)=O(f1(n)) T 2 ( n ) = O ( f 2 ( n ) ) T_2(n)=\text O(f_2(n)) T2(n)=O(f2(n)),则:

    • T 1 ( n ) + T 2 ( n ) = max ( O ( f 1 ( n ) ) , O ( f 2 ( n ) ) ) T_1(n)+T_2(n)=\text{max}(\text O(f_1(n)),\text O(f_2(n))) T1(n)+T2(n)=max(O(f1(n)),O(f2(n)))

      即在两个算法拼接在一起时,它们的上界是两个上界中较大的一个。

    • T 1 ( n ) × T 2 ( n ) = O ( f 1 ( n ) × f 2 ( n ) ) T_1(n)\times T_2(n)=\text O(f_1(n)\times f_2(n)) T1(n)×T2(n)=O(f1(n)×f2(n))

      即在两个算法嵌套在一起时,它们的上界是两个上界的乘积。

  • T ( n ) T(n) T(n)是关于 n n n k k k阶多项式,那么 T ( n ) = Θ ( n k ) T(n)=\Theta (n^k) T(n)=Θ(nk),即真正影响复杂度的是最大的那一项,其他的都可以忽略不计。

  • 一个for循环的时间复杂度 = = =循环次数 × \times ×循环体代码的复杂度。

  • if-else结构的复杂度取决于if的条件判断复杂度和两个分支部分的复杂度,总复杂度取三者中的最大值。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

花_城

你的鼓励就是我最大的动力

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

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

打赏作者

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

抵扣说明:

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

余额充值