数据结构之绪论

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Euphoria_0217/article/details/83541360

绪论

  • 古埃及人的绳索

  古埃及人以其复杂而浩大的建筑工程而闻名于世,在长期规划与实施此类工程的过程中,他们逐渐归纳并掌握了基本的几何度量和测绘方法。考古研究发现,公元前2000年的古埃及人已经知道如何解决如下实际工程问题:
    > 通过直线l上给定的点A,作该直线的垂线。

  他们所采用的方法如下:

    perprndicular(l, A)
    输入:直线l及其上一点A
    输出:经过A且垂直于l的直线
    1.取12段等长的绳索,依次将首位联结成环 //联结处称作“结”,按顺时针方向编号:0,1,...,11
    2.奴隶A看管0号结,将其固定于点A处
    3.奴隶B牵动4号结,将绳索沿直线l方向尽可能地拉直
    4.奴隶C牵动9号结,将绳索尽可能地拉直
    5.经过0号和9号结,绘制一条直线

我和我的小伙伴们都惊呆了

  • 欧几里得的尺规

  欧几里得几何是现代公理系统的鼻祖,比如经典的线段三等分过程:

    tripartition(AB)
    输入:线段AB
    输出:将AB三等分的两个点C和D
    1.从A发出一条与AB不重合的射线ρ
    2.任取ρ上三点C`、D`和B`,使|AC`| = |C`D`| = |D`B`|
    3.连接B`B
    4.过D`做B`B的平行线,交AB与D
    5.过C`做B`B的平行线,交AB与C
  • 冒泡排序

  在排序过程中,所有元素朝各自最终位置亦步亦趋的移动过程,犹如气泡在水中的上下沉浮,冒泡排序(bubblesort)算法也因此得名。

//1.1 整数数组的冒泡排序
void bubblesortA (int A[], int n) { //冒泡排序算法(版本1A):0 <= n
    bool sorted = false; //整体排序标志,首先假定尚未排序
    while (!sorted) { //在尚未确认已全局排序之前,逐趟进行扫描交换
        sorted = true; //假定已经排序
        for (int i = 0; i < n; i++) { //自左向右逐对检查当前范围A[0, n)内的各相邻元素
            if (A[i - 1] > A[i]) { //一旦A[i- 1]与A[i]逆序,则
                swap (A[i - 1], A[i]); // 交换之
                sorted = false; //因整体排序不能保证,需要清除排序标志
            }
        }
        n--; //至此末元素必然就位,故可以缩短待排序序列的有效长度
    }
} //借助bool型标志位sorted,可及时提前退出,而不致总是蛮力地做n- 1趟扫描交换

算法

  以上三例都可以称作算法。

  算法是指基于特定的计算模型,旨在解决某一信息处理问题而设计的一个指令序列。

  实际上,算法还应必须具备以下要素:

算法要素

  • 输入与输出

- 待计算问题的任一实例,都需要以某种方式交给对应的算法,对所求问题特定实例的这种描述统称为输入 (input)

- 经计算和处理之后得到的信息,即针对输入问题实例的答案,称作输出 (output)

  • 基本操作、确定性与可行性

- 基本操作是指,在整个求解过程中可以明白无误地描述为一系列如算术运算、比较、分支、函数调用与返回等可以兑现的方法
    
    - 确定性与可行性是指,算法应可描述为由若干语义明确的基本操作组成的指令序列,且每一基本操作在对应的计算模型中均可兑现

  • 有穷性与正确性

- 任意算法都应在执行有限次基本操作之后终止并给出输出,此即所谓算法的有穷性
    
    - 算法不仅应该迟早会终止,而且所给的输出还应该能够符合有问题本身在事先确定的条件,此即所谓算法的正确性

  • 退化与鲁棒性

- 除一般情况外,使用的算法还应能处理各种极端的输入实例。以排序问题为例,极端情况下待排序序列的长度可能不是正数,或者长度超过系统支持的最大值,或者序列中元素全体相等,以上种种情况都属于退化 (degeneracy) 情况
    
    - 鲁棒性就是要求能够尽可能充分地应对此类情况

  • 重用性

- 从实用角度评判不同算法及其不同实现方式时,可采用的另一标准是:算法的总体框架能否便捷地推广至其他场合。仍以排序算法为例,无论是对与float、char或其他类型,只要元素之间可以比较大小,算法的整体框架依然可以沿用。算法模式可推广并适用于不同类型基本元素的这种特性,即是重用性的一种典型形式。

  由上可知,无论是算法的初始输入、中间结果还是最终输出,在计算机中都可以以数据的形式表示。对于数据的存储、组织、转移及变换等操作,不同计算模型和平台环境所支持的具体形式不尽相同,其执行效率将直接影响和决定算法的整体效率。数据结构这一学科正是以“数据”这一信息表现形式为研究对象,旨在建立支持高效算法的数据信息处理策略、技巧和方法。要做到根据实际应用需求自如地设计、实现和选用适当的数据结构,必须首先对算法设计的技巧以及相应的数据结构的特性了然于心,这也是学习数据结构的难点。

复杂度度量

  算法的计算成本涵盖诸多方面,为确定计算成本的度量标准,我们不妨先从计算速度这一主要因素入手。

时间复杂度

  虽设输入规模的扩大,算法的执行时间将如何增长?执行时间的这一变化趋势可表示为输入规模的一个函数,称作该算法的时间复杂度 (time complexity) 具体地,特定算法处理规模为n的问题所需的时间可记作T(n)。

严格来说,从保守估计的角度出发,在规模为n的所有输入中,选择执行时间最长的作为T(n),并以T(n)度量该算法的时间复杂度。

  • 渐进分析

  至此,对于同一问题的两个算法A和B,通过比较其时间复杂度即可评价两者对于同一输入规模n的计算效率高低,然而,藉此还不足以就其性能优劣做出总体性的评判,比如对于某些问题,一些算法更适用于小规模的输入,而另一些则相反。

  幸运的是,在评价算法运行效率时,我们往往可以忽略其处理小规模问题时的能力差异,原因是小规模问题所需的处理时间本来就相对更少,故此时不同的算法实际的效率差异并不明显;而在处理大规模的问题时,效率的些许差异都将对实际执行效果产生巨大的影响。这种着眼长远、更为注重时间复杂度的总体变化趋势和增长速度的策略和方法,就是渐进分析 (asymptotic analysis)

  • 大O记号

  出于保守的估计,我们关注T(n)的渐进上界。为此可引入“大O记号” (big-O notation) 。具体地,若存在正的常数c和函数f(n),使得对任何n >> 2都有

T(n) \leq c \cdot f(n)

则可认为在n足够大之后,f(n)给出了T(n)增长速度的一个渐进上界。此时,记为

T(n) = O(f(n))

  在大O记号的意义下,函数各项整的常系数可以忽略,多项式中的低次项均可忽略。大O记号体现了对函数总体渐进增长趋势的关注和刻画,例如冒泡排序算法,在每一轮内循环中,需要扫描和比较n - 1对元素,至多需要交换n - 1对元素。元素的比较和交换都属于基本操作,故每一轮内循环至多需要执行2(n - 1)次基本操作,外循环至多执行n - 1轮,因此,总共需要执行的基本操作不会超过2(n - 1)²次。若以此来度量该算法的时间复杂度,则有

T(n) = O(2(n - 1)^2)

根据大O记号的意义,可进一步化简和整理为:

T(n) = O(2n^2 - 4n + 2) = O(2n^2) = O(n^2)

  以大O记号形式表示的时间复杂度,实质上是对算法执行时间的一种保守估计,称作最坏实例或最坏情况,比较而言,“最坏情况复杂度”是人们最为关注且使用最多的,在一切特殊的场合甚至成为唯一的指标

描述T(n)渐进下界的大Ω记号、描述T(n)确界的大Θ记号这里不做介绍。

抽象数据类型

  各种数据结构都可以看作是由若干数据项组成的集合,同时对数据项定义一组标准的操作。现代数据结构普遍遵从“信息隐藏”的理念,通过统一的接口和内部封装,分层次从整体上加以设计、实现和使用。

  所谓封装,就是将数据项与相关的操作结合为一个整体,并将其从外部的可见性划分为若干级别,从而将数据结构的外部特性与其内部实现相分离,提供一致且标准的对外接口,隐藏内部实现细节。于是,数据集合及其对应的操作可超脱于具体的程序设计语言、具体的实现方式,即构成所谓的抽象数据类型 (abstract data type, ADT) 。抽象数据类型的理论催生了现代面向对象的程序设计语言,而支持封装也是此类语言的基本特性。

猜你喜欢

转载自blog.csdn.net/Euphoria_0217/article/details/83541360