数据结构(第二版)笔记

一、绪论

       早期的计算机主要用于数值计算,现在,计算机主要用于非数值计算,包括处理字符,表格和图像等具有一定结构的数据。这些数据内容存在着某种联系,只有分清楚数据的内在联系,合理地组织数据,才能对它们进行有效的处理,设计出高效的算法。如何合理地组织数据、高效地处理数据,这就是“数据结构”主要研究的问题。本章简要介绍有关数据结构的基本概念和算法分析方法。

1.1   数据结构的研究内容

        计算机主要用于数值计算时,一般有如下步骤:首先从具体问题抽象出数学模型,然后设计一个解此数学模型的算法,最后编写程序, 进行测试、调试,直到解决问题。

例:最短路径问题的数学模型就是图模型,算法是求解两点之间的最短路径。诸如此类的图结构还有网络工程图和网络通信图等。在这类问题中,元素之间是多对多的网状结构,施加于对象上的操作依然有查找、插入和删除等。这类数学模型称为“图”的数据结构。

       由例子可以看出,非数值计算问题的数学模型不再是数学方程,而是诸如线性表,树和图的数据结构。因此,简单地说,数据结构是一门研究非数值计算程序中的操作对象,以及这些对象之间的关系和操作的学科

1.2   基本概念和术语

下列概念和术语会反复出现:

1.2.1    数据、数据元素、数据项、数据对象

数据(Data)客观事物的符号表示,是所有能输入到计算机中并被计算机程序处理的符号的总称。例:文本编辑用到的字符串;多媒体程序处理的图形、图像、声音等通过特殊编码定义后的数据;数学计算中用到的正数和实数......

数据元素(Data Element) 数据的基本单位,在计算机中通常作为一个整体进行考虑和处理。在某些情况下,数据元素也称为元素、记录等。数据元素用于完整地描述一个对象,例如一名学生的记录等等。

数据项(Data Item)组成数据元素的、有独立含义的、不可分割的最小单元。例:学生信息表中的学号、姓名、性别等都是数据项。

数据对象(Data Object):是性质相同的数据元素的集合,是数据的一个子集。例如:整数数据对象是集合N={0,+-1,+-2,...},学生基本信息表也可以是一个数据对象。

1.2.2    数据结构

数据结构(Data Structure):是相互之间存在一种或多种特定关系的数据元素的集合。换句话说:数据结构是带“结构”的数据元素的集合,“数据”就是指数据元素之间存在的关系。

数据结构包括:逻辑结构、存储结构

1、逻辑结构:

从逻辑关系上描述数据,它与数据的存储方式无关,是独立于结算及的。因此,数据的逻辑结构可以看作是从具体问题抽象出来的数学模型

四类基本结构(复杂程度依次递进):

1)集合结构:

数据元素之间除了”同属于一个集合”的关系外,别无其他关系。例:确定某学生是否在某班级里

2)线性结构:

数据元素之间存在一对一的关系。例:将学生信息数据按照其入学报道时间前后顺序进行排列,将组成一个线性结构。

3)树结构:

数据元素之间存在一对多的关系。例:在班级的管理中,班长管理多个组长,组长管理多名组员。

4)图结构或网状结构:

数据元素之间存在多对多的关系。例:多位同学之间的朋友关系,任何两个同学都可以是朋友,从而形成图状结构或网状结构。

不重要:

其中集合结构、树结构和图结构都属于非线性结构;

线性结构包括线性表(典型)、栈和队列(具有特殊限制的线性表,数据操作只能在表的一端或两端进行)、字符串(也是特殊线性表,其特殊性表现在它的数据元素仅由一个字符组成)、数据(是线性表的推广,它的数据元素是一个线性表)、广义表(也是线性表的推广,它的数据元素是一个线性表,但不同构,即或者是单元素,或者是线性表)等等...

2、存储结构

数据对象在计算机中的存储表示称为数据的存储结构,也称为物理结构。

数据元素在计算机中有两种基本的存储结构:

1)顺序存储结构:

借助元素在存储器中的相对位置来表示数据元素之间的逻辑关系,通常借助程序设计语言的数组类型来描述。顺序存储结构要求所有的元素一次存放在一片连续的存储空间中

2)链式存储结构:

链式存储结构无需占用一整块存储空间。但为了表示节点之间的关系,需要给每个节点附加指针字段,用于存放后继元素的存储地址。所以链式存储结构通常借助于程序设计语言的指针类型来描述

1.2.3    数据类型和抽象数据类型

1、数据类型(Data Type)

是高级程序设计语言中的一个基本概念,数据类型和数据结构的概念密切相关

是一个值的集合和定义在这个值集上的一组操作的总称。 例:C语言中的整型变量,其值集为某个区间上的正数(区间大小依赖于不同的机器),定义在其上的操作为加减乘除等算数运算。

2、抽象数据类型(Abstract Data Type, ADT)

一般指由用户定义的、表示应用问题的数学模型,以及定义在这个模型上的一组操作的总称,具体包括三部分:数据对象、数据对象上关系的集合以及对数据对象的基本操作的集合。

抽象数据类型的定义格式如下:

ADT 抽象数据类型名{

        数据对象:<数据对象的定义>

        数据关系:<数据关系的定义>

        基本操作:<基本操作的定义>

}ADT 抽象数据类型名

其中,数据对象和数据关系的定义采用数学符号和自然语言描述,基本操作的定义格式为:

基本操作名(参数表)

        初始条件:<初始条件描述>

        操作结果:<操作结果描述>

基本操作有两种参数:赋值参数只为操作提供输入值;引用参数以“&”打头,除可提供输入值外,还将返回操作结果。“初始条件“描述了操作执行之前数据结构和参数应满足的条件,若初始条件为空,则省略。”操作结果“说明了操作正常完成之后,数据结构的变化情况和应返回的结果。

1.3 抽象数据类型的表示与实现(见书P7,没啥意思,略)

1.4    算法和算法分析

1.4.1    算法的定义及其特性

算法(Algorithm)是为了解决某类问题而规定的一个有限长的操作序列

一个算法必须满足以下五个重要特性:

1)有穷性。一个算法必须总是在执行有穷步后结束,且每一步都必须在又穷时间内完成。

2)确定性。对于每种情况下所应执行的操作,在算法中都有确切的规定,不会产生二义性,使算法的执行者或阅读者都能明确其含义及如何执行

3)可行性。算法中的所有操作都可以通过已经实现的基本操作运算执行有限次来实现。

4)输入。一个算法有零个或多个输入。当用函数描述算法时,输入往往是通过形参表示的,在她们被调用时,从主调函数获得输入值

5)输出。一个算法有一个或多个输出,它们是算法进行信息加工后得到的结果,无输出的算法没有任何意义。当用函数描述算法时,输出多用返回值或引用类型的形参表示。

1.4.2   评价算法优劣的基本标准

一个算法的优劣应该从以下几方面来评价:

1)正确性。在合理的数据输入下,能够在有限的运行时间内得到正确的结果。

2)可读性。一个好的算法,首先应便于人们理解和互相交流,其次才是及其可执行性。可读性强的算法有助于人们对算法的理解,而难懂的算法易于隐藏错误,且难于调试和修改。

3)健壮性。当输入的数据非法时,好的算法能适当地做出正确反映或进行相应处理,而不会产生一些莫名其妙的输出结果。

4)高效性。高效性包括时间和空间两个方面。时间搞笑是指算法设计合理,执行效率高,可以用时间复杂度来度量;空间高效是指算法占用储存容量合理,可以用空间复杂度来度量。时间复杂度和空间复杂度是衡量算法的两个主要指标。

1.4.3   算法的时间复杂度

衡量算法效率的方法主要有两类:事后统计法和事前分析估算法。事后统计法需要先将算法实现,然后测算其时间和空间开销。这种方法的缺陷很显然,一是必须把算法转换成可执行的程序,而是时空开销的测算结果依赖于计算机的软硬件等环境因素,这容易掩盖算法本身的优劣。

所以我们通常使用事前分析估算法,通过计算算法的渐近复杂度来衡量算法的效率。

1、问题规模和语句频度

问题规模:算法求解问题输入量的多少,是问题大小的本质表示,一般用整数 n 表示。

例:在排序运算中,n为参加排序的记录数;在矩阵运算中,n为矩阵的阶数......

显然,n越大,算法执行时间越长

一个算法的执行时间大致上等于其所有语句执行时间的总和,而语句的执行时间则为该条语句的重复执行次数和执行一次所需时间的乘积。

语句频度(Frequency Count):一条语句的重复执行次数

2、算法的时间复杂度定义

一般情况下,算法中基本语句重复执行的次数是问题规模 n 的某个函数 f(n) ,算法的时间量度记作:        T(n) = O( f(n) )          他表示随问题规模 n 的增大,算法执行时间的增长率和 f(n)

的增长率相同,称作算法的渐近时间复杂度,简称时间复杂度(Time Complexity)

数学符号”O“的严格定义为:(没人话,不好懂)

若T(n)和f(n)是定义在正整数集合上的两个函数,则T(n) = O( f(n) )表示存在正的常数C和n0,使得当n>=n0时刻都满足 0 <= T(n) <= C ( f(n) )

该定义说明了函数T(n)和f(n)具有相同的增长趋势,且T(n)的增长趋势至多趋向于f(n)的增长

3、算法的时间复杂度分析举例

常量阶  复杂度:O(1):{x++; s = 0;}

解析:两条语句频度均为1,算法的执行时间是一个与问题规模n无关的常数,实际上,如果算法的执行时间不随问题规模n而增长,即使这个常数再大,算法的时间复杂度都是O(1);

例:   for ( i = 0; i < 10000; i++)   {x++; s = 0} 其时间复杂度仍为O(1)

线性阶  复杂度:O(n):for (i = 0; i < n; i++) {x++; s=0;}

解析:循环体内两条基本语句的频度均为n,所以算法的时间复杂度为T(n) = O(n)

平方阶  复杂度:O(n*n)

int x = 0, y = 0;
for(k = 1; k <= n; k++)
{
    x++;
}
for(i = 1; i <=n; i++)
{
    for(j = 1; j <=n; j++)
        y++;
}

对循环语句只需考虑循环体中语句的执行次数,以上程序段中频度最大的语句是 ”y++“,其频度为n的平方,故该算法的时间复杂度为O(n*n),称为平方阶

注意:多数情况下,当有若干个循环语句时,算法的时间复杂度是由最深层循环内的基本语句的频度f(n)决定的

对数阶  复杂度O(\log {_{2}}^{n})    for(i = 1; i <= n; i=i*2)  {x++; s=0; }

解析:设循环体内的两条基本语句的频度为  f(n)  ,则有:2^{f(n))}\leq n, f(n)\leq \log {_{2}}^{n}

所以算法的时间复杂度为O(\log {_{2}}^{n}),称为对数阶


4、最好、最坏和平均时间复杂度

对于某些问题的算法,其基本语句的频度不仅仅与问题的规模相关,还依赖于其它因素。举例说明之:

在一维数组 a 中顺序查找某个值等于 e 的元素,并返回其所在位置。

for(i = 0; i < n; i++)            ————语句(1)
    if(a[i] == e) return i+1;     ————语句(2)
return 0;                         ————语句(3)

容易看出:此算法中语句(2)的频度不仅与问题规模 n 有关,还与输入示例中数组 a[i] 的各元素值及 e 的取值有关。 假设在数组 a[i] 中必定存在值等于 e 的元素,则查找必定成功,且 for循环内的语句的频度将随被找到的元素在数组中出现的位置的不同而不同,最好情况是,每次要找的值与 e 相同的元素恰好就是数组中的第一个元素,则不论数组的规模多大,语句(2)的频度为1;最坏情况是,每次待查找的都是数组中最后一个元素,则语句(2)的频度为n。而对于一个算法来说,需要考虑各种情况出现的情况,以及每一种情况出现的频率,一般情况下,可假设待查找的元素在数组中所有位置上出现的可能性均相同,则可取语句(2)的频度在最好情况与最坏情况下的平均值,即 2/n ,作为它的度量。

最好时间复杂度:称算法在最好情况下的时间复杂度,指的是算法计算量可能到达的最小值;

最坏时间复杂度:称算法在最坏情况下的时间复杂度,指的是算法计算量可能到达的最大值;

平均时间复杂度:值算法在所有可能情况下,按照输入实例以等概率出现时,算法计算量的加权平均值

注意:本文章讨论的时间复杂度,若无特殊指明,均指最坏情况下的时间复杂度。

1.4.4    算法的空间复杂度

关于算法的存储空间需求,类似于算法的时间复杂度,我们采用渐近空间复杂度(Space Complexity)作为算法所需存储空间的量度,简称空间复杂度,他也是问题规模 n 的函数,

记作:        S(n) = O( f(n) )

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

举例:

数组逆序,将一维数组 a 中的 n 个数逆序存放到原数组中
算法1:
for(i = 0; i < n/2; i++)
{
    t = a[i];
    a[i] = a[n/i/1];
    a[n-i-1] = t;
}

算法2:
for(i = 0; i < n; i++)
{
    b[i] = a[n-i-1];
}
for(i = 0; i < n; i++)
    a[i] = b[i];

算法1仅需要另外借助一个变量 t ,与问题规模 n 大小无关,所以其空间复杂度为O(1)

算法2需要另外借助一个大小为 n 的辅助数组 b ,所以其空间复杂度为O(n)










二、线性表

2.1    线性表的定义和特点

      在日常生活中,线性表的例子比比皆是。例如,26个英文字母的字母表:(A,B,C,...,Z)是一个线性表,表中的数据元素是单个字母。在稍复杂的线性表中,一个数据元素可以包含若干个数据项。例如第一章中的学生基本信息表,每个学生为一个数据元素,包括学号、姓名、性别、籍贯、专业等数据项。

        由以上事例可以看出,它们的数据元素虽然不同,但同一线性表中的元素必定具有相同的特性,即属于同一数据对象,相邻数据元素之间存在着序偶关系

        诸如此类由 n (n>=0) 个数据特性相同的元素构成的有限序列称之为线性表

        线性表中元素的个数 n 定义为线性表的长度, n = 0时称为空表

对于非空的线性表或线性结构,其特点是:

1)存在唯一的一个被称作“第一个”的数据元素

2)存在唯一的一个被称作“最后一个”的数据元素

3)除第一个之外,结构中的每个数据元素均只有一个直接前驱

4)出最后一个之外,结构中的每个数据元素均只有一个直接后继

2.2    案例引入(见书P19,略)

2.3    线性表的类型定义

线性表是一个相当灵活的数据结构,其长度可根据需要增长或缩短,即对线性表的数据元素不仅可以访问,而且可以进行插入和删除等操作。为不失一般性,此处采用1.2节抽象数据类型格式对各种数据结构进行描述。下面给出线性表的抽象数据类型定义:

ADT List{

数据对象:D = {ai | ai ∈ ElemSet, i=1,2,...,n, n ≥0}

数据关系:R = {< a(i-1), ai >  |  a(i-1), a ∈ D, i = 1,2,...,n}

基本操作:

        InitList(&L)

                操作结果:构造一个空的线性表L

        DestoryList(&L)

                初始条件:线性表L已存在

                操作结果:销毁线性表L

        ClearList(&L)

                初始条件:线性表L已存在

                操作结果:将L重置为空表

        ListEmpty(L)

                初始条件:线性表L已存在

                操作结果:若L为空表,则返回true,否则返回false

        ListLength(L)

                初始条件:线性表L已存在

                操作结果:返回L中元素的个数

        GetElem(L,i,&e)

                初始条件:线性表L已存在,且1 ≤ i ≤ ListLength(L)

                操作结果:用 e 返回 L 中第 i 个数据元素的值

        LocateElem(L,e)

                初始条件:线性表L已存在

                操作结果:返回 L 中第一个值与 e 相同的元素在 L 的位置。若不存在,返回 0

        PriorElem(L,cur_e,&pre_e)

                初始条件:线性表 L 已存在

                操作结果:若 cur_e 是 L 的数据元素,且不是第一个,则用 pre_e返回其前驱,否则操  

                                  作失败,pre_e无定义

        NextElem(L,cur_e,&next_e)

                初始条件:线性表L已存在

                操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回其后继,否则操

                                  作失败,next_e无定义

        ListInsert(&L,i,e)

                初始条件:线性表L已存在,且 1 ≤ i ≤ ListLength(L) + 1

                操作结果:在 L 中第 i 个位置之前插入新的数据元素 e ,L的长度+1

        ListDelete(&L,i)

                初始条件:线性表 L 已存在且非空,且 1 ≤ i ≤ ListLength(L)

                操作结果:删除 L 的第 i 个数据元素,L 的长度减1

        TraverseList(L)

                初始条件:线性表 L已存在

                操作结果:对线性表 L 进行遍历,在遍历过程中对 L 的每个结点访问一次

}ADT List

未完待续。。。。。。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值