概述
数据结构与算法(王卓)
参考于B站视频:数据结构与算法基础(青岛大学-王卓)。笔记仅用于个人复习、专升本备考、考研复习。
第一章 绪论
1.1 数据结构的研究内容
- 早期,计算机主要用于数值计算。
- 例1,求解梁架结构中的应力。
- 例2,预报人口增长情况。
- 随着计算机应用领域的扩展,计算机被越来越多地用于非数值计算。
- 例1,学生学籍管理系统。
- 例2,人机对弈问题。
- 例3,文件系统的系统结构图。
- 例4,地图导航——求最短路径。
- 综上所述:
- 这些问题的共性是都无法用数学的公式或方程来描述,是一些“非数值计算”的程序设计问题。
- 描述非数值计算问题的数学模型不是数学方程,而是诸如表、树和图之类的具有逻辑关系的数据。
数据结构是一门研究非数值计算的程序设计中计算机的操作对象以及它们之间的关系和操作的学科。
1.2 基本概念和术语
1.2.1 数据、数据元素、数据项和数据对象
-
数据(Data)
- 是能输入计算机且能被计算机处理的各种符号的集合。
- 信息的载体;
- 是对客观事物符号化的表示;
- 能够被计算机识别、存储和加工;
- 包括:
- 数值型的数据:整数、实数等;
- 非数值型的数据:文字、图像、图形、声音等。
- 是能输入计算机且能被计算机处理的各种符号的集合。
-
数据元素(Data Element)
- 是数据的基本单位,在计算机程序中通常作为一个整体进行考虑和处理。
- 也简称为元素,或称为记录、结点或顶点。
-
数据项(Data ltem)
- 构成数据元素的不可分割的最小单位。
-
数据、数据元素、数据项三者之间的关系:
- 数据 > 数据元素 > 数据项
- 例:学生表 > 个人记录 > 学号、姓名……
-
数据对象(Data Object)
- 是性质相同的数据元素的集合,是数据的一个子集。
- 例如:
- 整数数据对象是集合N={0,±1,±2,…}
- 字母字符数据对象是集合C={‘A’,B’,…,‘Z’}
- 学籍表也可看作一个数据对象。
-
数据元素——组成数据的基本单位。
- 与数据的关系:是集合的个体。
-
数据对象——性质相同的数据元素的集合。
- 与数据的关系是:集合的子集。
1.2.2 数据结构
- 数据元素不是孤立存在的,它们之间存在着某种关系,数据元素相互之间的关系称为结构(Structure)。
- 是指相互之间存在一种或多种特定关系的数据元素集合。
- 或者说,数据结构是带结构的数据元素的集合。
- 数据结构包括以下三个方面的内容:
- 数据元素之间的逻辑关系,也称为逻辑结构。
- 数据元素及其关系在计算机内存中的表示(又称为映像),称为数据的物理结构或数据的存储结构。
- 数据的运算和实现,即对数据元素可以施加的操作以及这些操作在相应的存储结构上的实现。
- 逻辑结构:
- 描述数据元素之间的逻辑关系。
- 数据的存储无关,独立于计算机。
- 是从具体问题抽象出来的数学模型。
- 物理结构(存储结构):
- 数据元素及其关系在计算机存储器中的结构(存储方式)。
- 是数据结构在许算机中的表示。
- 逻辑结构与存储结构的关系:
- 存储结构是逻辑关系的映象与元素本身的映象。
- 逻辑结构是数据结构的抽象,存储结构是数据结构的实现。
- 两者综合起来建立了数据元素之间的结构关系。
- 逻辑结构的种类:
- 划分方法一:
- 线性结构(一对一关系)
- 有且仅有一个开始和一个终端结点,并且所有结点都最多只有一个直接前趋和一个直接后继。
- 例如:线性表、栈、队列、串。
- 非线性结构(一对多、多对多关系)
- 一个结点可能有多个直接前趋和直接后继。
- 例如:树、图。
- 线性结构(一对一关系)
- 划分方法二:
- (1)集合结构:结构中的数据元素之间除了同属于一个集合的关系外,无任何其它关系。
- (2)线性结构:结构中的数据元素之间存在一对一的线性关系。
- (3)树形结构:结构中的数据元素之间存在一对多的层次关系。
- (4)图状结构或网状结构:结构中的数据元素之间存在着多对多的任意关系。
-
四种基本的存储结构:
-
顺序存储结构
- 用一组连续的存储单依次存储数据元素,数据元素之间的逻辑关系由元素的存储位置来表示。
- C语言中用数组来实现顺序存储结构。
-
链式存储结构
- 用一组任意的存储单依次存储数据元素,数据元素之间的逻辑关系用指针来表示。
- C语言中用指针来实现链式存储结构。
-
索引存储结构
- 在存储结点信息的同时,还建立附加的索引表。
- 索引表中每一项称为一个索引项;
- 索引项的一般形式是:(关键字,地址)。
- 关键字是能唯一标识一个结点的那些数据项。
- 若每个结点在索引表中都有一个索引项,则该索引表称之为稠密索引(Dense Index)。若一组结点在索引表中只对应一个索引项,则该索引表称之为稀疏索引(Sparse Index)。
-
散列存储结构
- 根据结点的关键字直接计算出该结点的存储地址。
-
- 得到散列表如下:
1.2.3 数据类型和抽象数据类型
-
在使用高级程序设计语言编写程序时,必须对程序中出现的每个变量、常量或表达式,明确说明它们所属的数据类型。
-
例如,C语言中:
- 提供int,char,float,double等基本数据类型。
- 数组、结构、共用体、夜举等构造数据类型。
- 还有指针、空(void)类型。
- 用户也可用typedef 自己定义数据类型。
-
一些最基本数据结构可以用数据类型来实现,如数组、字符串等;
-
而另一些常用的数据结构,如栈、队列、树、图等,不能直接用数据类型来表示。
-
高级语言中的数据类型明显地或隐含地规定了在程序执行期间变重表达的所有可能的取值范围,以及在这些数值范围上所允许进行的操作。
- 例如,C语言中定义变量i为int类型,就表示是[-min,max]范围的整数,在这个整数集上可以进行+、一、*、、%等操作。
-
数据类型的作用:
- 约束变量或常量的取值范围;
- 约束变量或常量的操作。
-
数据类型(Data Type)
- 定义:数据类型是一组性质相同的值的集合以及定义于这个值集合上的一组操作的总称。
- 数据类型=值的集合+值集合上的一组操作。
-
抽象数据类型(Abstract Data Type,ADT):
- 抽象:抽取出实际问题的本质。
- 定义:是指一个数学模型以及定义在此数学模型上的一组操作。
- 由用户定义,从问题抽象出数据模型(逻辑结构)。
- 还包括定义在数据模型上的一组抽象运算(相关操作)。
- 不考虑计算机内的具体存储结构与运算的具体实现算法。
-
抽象数据类型的形式定义
-
抽象数据类型可用(D,S,P)三元组表示。其中:
- D是数据对象;
- S是D上的关系集;
- P是对D的基本操作集。
-
一个抽象数据类型的定义格式如下:
ADT 抽象数据类型名{ 数据对象: <数据对象的定义> 数据关系: <数据关系的定义> 基本操作: <基本操作的定义> }ADT 抽象数据类型名
- 其中:数据对象、数据关系的定义用伪代码描述。
- 基本操作的定义格式为:
基本操作名 (参数表) 初始条件: <初始条件描述> 操作结果: <操作结果描述>
-
基本操作定义格式说明:
- 参数表:
- 赋值参数 只为操作提供输入值。
- 引用参数 以&打头,除可提供输入值外,还将返回操作结果。
- 初始条件:
- 描述操作执行之前数据结构和参数应满足的条件,若不满足,则操作失败,并返回相应出错信息。若初始条件为空,则省略之。
- 操作结果:
- 说明操作正常完成之后,数据结构的变化状况和应返回的结果。
- 参数表:
-
抽象数据类型(ADT)定义举例:Circle的定义
ADT Circle{ 数据对象: D={r,x,y | r,x,y 均为实数} 数据关系: R={<r,x,y > | r是半径,<x,y>是圆心坐标} 基本操作: Circle(&C,r,x,y) 操作结果: 构造一个圆。 double Area(C) 初始条件: 圆已存在。 操作结果: 计算面积。 double Circumference(C) 初始条件: 圆已存在。 操作结果: 计算周长。 …… }ADT Circle
- 抽象数据类型(ADT)定义举例:复数的定义
ADT Complex{ D={r1,r2 | r1,r2 都是实数} R={<r1,r2> | r1是实部,r2是虚部} assign(&C,v1,v2) 初始条件: 空的复数C已存在。 操作结果: 构造复数C,r1,r2分别被赋以参数v1,v2的值。 destroy(&C) 初始条件: 复数C已存在。 操作结果: 复数C被销毁。 double Area(C) 初始条件: 圆已存在。 操作结果: 计算面积。 double Circumference(C) 初始条件: 圆已存在。 操作结果: 计算周长。 …… }ADT Complex
-
-
Complex抽象数据类型中的基本操作:
- Assign( &Z,v1,v2)
- 操作结果:构造复数Z,其实部和虚部,分别被赋以参数v1、v2值。
- Destroy( &Z)
- 操作结果:复数z被销毁。
- GetReal(Z, &realPart)
- 初始条件:复数已存在。
- 操作结果:用realPart返回复数Z的实部值。
- Getlmag(Z, &lmagPart)
- 初始条件:复数已存在。
- 操作结果:用ImagPart返回复数Z的虚部值。
- Add(z1,z2, &sum)
- 初始条件:z1,z2是复数。
- 操作结果:sum返回两个复数z1,z2的和。
- Assign( &Z,v1,v2)
1.3 抽象数据类型的表示与实现
-
抽象数据类型如何实现?
- 抽象数据类型可以通过固有的数据类型(如整型、实型、字符型导来表示和实现。
- 即利用处理器中已存在的数据类型来说明新的结构,用已经实现的操作来组合新的操作。
- 抽象数据类型可以通过固有的数据类型(如整型、实型、字符型导来表示和实现。
-
用C语言真正实现抽象数据类型的定义
- 例如:抽象数据类型“复数”的实现
typedef struct{ float realpart; /* 实部 */ float imagpart; /* 虚部 */ }Complex void assign(Complex *A,float real,float imag); /* 赋值 */ void add(Complex *A,float real,float imag); /* A + B */ void minus(Complex *A,float real,float imag); /* A - B */ void multiply(Complex *A,float real,float imag); /* A * B */ void divide(Complex *A,float real,float imag); /* A / B */
void assign(Complex *A,float real,float imag){ A->realpart = real; /* 实部赋值 */ A->imagpart = imag; /* 虚部赋值 */ } void add(Complex *c,Complex A,Complex B){ c->realpart = A.realpart + B.realpart; /* 实部相加 */ c->imagpart = A.imagpart + B.imagpart; /* 虚部相加 */ }
- 完整实现C代码:
#include <stdio.h> typedef struct Complex{ float realpart; float imagepart; }complex; complex assign(float real,float image){ // 构造一个复数 complex c; c.realpart=real; c.imagepart=image; return c; } float Real(complex C){ // 取复数C=x+yi的实部 return C.realpart; } float imag(complex C){ // 取复数C=x+yi的虚部 return C.imagepart; } complex Add(complex C1,complex C2){ // 求两个复数C1和C2的和sum complex sum; sum.realpart=C1.realpart+C2.realpart; sum.imagepart=C1.imagepart+C2.imagepart; return sum; } complex Difference(complex C1,complex C2){ //求两个复数C1和C2的差difference complex sum; sum.realpart=C1.realpart+C2.realpart; sum.imagepart=C1.imagepart+C2.imagepart; return sum; } int main(){ complex c1,c2,c3,c4; c1=assign(1.0,2.0); c2=assign(3.0,5.0); c3=Add(c1,c2); c4=Difference(c1,c2); printf("c1=%.2f+%.2fi n",c1.realpart,c1.imagepart); printf("c2=%.2f+%.2fi n",c2.realpart,c2.imagepart); printf("c1与c2的和为=%.2f+%.2fi n",Add(c1,c2).realpart,Add(c1,c2).imagepart); return 0; }
1.4 算法和算法分析
1.4.1 算法的定义
-
算法的定义
-
对特定问题求解方法和步骤的一种描述,它是指令的有限序列。其中每个指令表示一个或多个操作。
-
简而言之,算法就是解决问题的方法和步骤。
Step1:。。。 Step2:。。。 Step3:。。。 。。。
-
-
算法的描述
- 自然语言:英语、中文
算法,求一元二次方程的恨: 1、输入方程的系数a、b、c。 2、判断a是否等于零。如果等于零,则提示不是一元二次方程。 不等于零,则执行第3步。 3、计算d=b2-4ac 4、判断d。如果d等于零,计算并输出两个相等实根。如果d小于零,输出没有实根。如果d大于零。输出两个不等实根。 5、结束
- 流程图:传统流程图、NS流程图
-
伪代码:类语言:类C语言
-
程序代码:C语言程序、JAVA语言程序……
-
算法与程序
- 算法是解决问题的一种方法或一个过程,考虑如何将输入转换成输出,一个问题可以有多种算法。
- 程序是用某种程序设计语言对算法的具体实现。
- 程序数据结构+算法
- 数据结构通过算法实现操作
- 算法根据数据结构设计程序
1.4.2 算法的性质
-
算法的特性
-
有穷性:一个算法必须总是在执行有穷步之后结束,且每一步都在有穷时间内完成。
- 确定性:算法中的每一条指令必须有确切的含义,没有二义性,在任何条件下,只有唯一的一条执行路径,即对于相同的输入只能得到相同的输出。
- 可行性:算法是可执行的,算法描述的操作可以通过已经实现的基本操作执行有限次来实现。
- 输入:一个算法有零个或多个输入。
- 输出:一个算法有一个或多个输出。
-
算法设计的要求
- 正确性(Correctness) :算法满足问题要求,能正确解决问题。
- 算法转化为程序后要注意:
- 1、程序中不含语法错误;
- 2、程序对于几组输入数据能够得出满足要求的结果;
- 3、程序对于精心选择的、典型、苛刻且带有刁难性的几组输入数据能够得出满足要求的结果;
- 4、程序对于一切合法的输入数据都能得出满足要求的结果。
- 通常以第三层意义上的正确性作为衡量一个算法是否合格的标准。
- 算法转化为程序后要注意:
- 可读性(Readability)
- 1、算法主要是为了人的阅读和交流,其次才是为计算机执行,因此算法应该易于人的理解;
- 2、另一方面,晦涩难读的算法易于隐藏较多错误而难以调试。
- 健壮性(Robustness) (或鲁棒性)
- 1、指当输入非法数据时,算法恰当的做出反应或进行相应处理,而不是产生莫名其妙的输出结果。
- 2、处理出错的方法,不应是中断程序的执行,而应是返回一个表示错误或错误性质的值,以便在更高的抽象层次上进行处理。
- 高效性(Efficiency)
- 要求花费尽量少的时间和尽量低的存储需求。
- 正确性(Correctness) :算法满足问题要求,能正确解决问题。
一个好的算法首先要具备正确性,然后是健壮性,可读性,在几个方面都满足的情况下,主要考虑算法的效率,通过算法的效率高低来评判不同算法的优劣程度。
1.4.3 算法的效率——时间复杂度
-
算法效率以下两个方面来考虑:
- 1、时间效率:指的是算法所耗费的时间;
- 2、空间效率:指的是算法执行过程中所耗费的存储空间。
- 时间效率和空间效率有时候是矛盾的。
-
算法时间效率的度量
-
算法时间效率可以用依据该算法编制的程序在计算机上执行所消耗的时间来度量。
-
两种度量方法:
-
事后统计
- 将算法实现,(测算其间和空间开销)
- 缺点:编写程序实现算法将花费较多的时间和精力;所得实验结果依赖于计算机的软硬件等环境因素,掩盖算法本身的优劣。
-
事前分析
- 对算法所消耗资源的一种估算方法。
-
事前分析方法:
- 一个算法的运行时间是指一个算法在计算机上运行所耗费的时间大致可以等于计算机执行一种简单的操作(如赋值、比较、移动等)所需的时间与算法中进行的简单操作次数乘积。
- 算法运行时间=一个简单操作所需的时间x简单操作次数。
- 也即算法中每条语句的执行时间之和
- 算法运行时间=∑每条语句的执行次数×该语句执行一次所需的时间。
- 每条语句的次数又称为语句频度。
- 又可以表示:算法运行时间=∑每条语句频度×该语句执行一次所需的时间。
每条语句执行一次所需的时间,一般是随机器而异的。取决于机器的指令性能、速度以及编译的代码质量。是由机器本身软硬件环境决定的,它与算法无关。
所以,我们可假设执行每条语句所需的时间均为单位时间。此时对算法的运行时间的讨论就可转化为讨论该算法中所有语句的执行次数,即频度之和了。
-
-
-
例如:求两个n阶矩阵的乘积算法。
for(i=1;i<n;i++){ // 频度为n+1次
for(j=1;j<=n;j++){ // 频度为n*(n+1)次
c[i][j]=0; // 频度为n*n次
for(k=1;k<=n;k++) // 频度为n*n*(n+1)次
c[i][j]=c[i][j]+a[i][k]*b[k][j]; // 频度为n*n*n次
}
}
- 注:i从1~n首先判断条件是否成立,条件满足执行循环体并i++,i=n+1判断条件是否成立条件不满足,退出循环,判断n+1次循环体执行了n次。
- 我们把算法所耗费的时间定义为该算法中每条语句的频度之和,则上述算法的时间消耗T(n)为:
T ( n ) = 2 n 3 + 3 n 2 + 2 n + 1 / / 这 是 一 个 关 于 n 的 函 数 T(n)=2n^3+3n^2+2n+1 // 这是一个关于n的函数 T(n)=2n3+3n2+2n+1//这是一个关于n的函数
为了便于比较不同算法的时间效率,我们仅比较它们的数量级。
-
例如:两个不同的算法,时间消耗分别是:
- T 1 ( n ) = 10 n 3 与 T 2 ( n ) = 5 n 3 T1(n)=10n^3 与 T2(n)=5n^3 T1(n)=10n3与T2(n)=5n3
-
若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n))为算法的渐进时间复杂度(O是数量级的符号),简称时间复杂度。
-
对于求解矩阵相乘问题,算法耗费时间:
-
T ( n ) = 2 n 3 + 3 n 2 + 2 n + 1 T(n)=2n^3+3n^2+2n+1 T(n)=2n3+3n2+2n+1
-
n→∞时,T(n)/n3 → 2,这表示n充分大时,T(n)与n2是同阶或同数量级,引入大“O”记号,则T(n)可记作:
-
T ( n ) = O ( n 3 ) T(n)=O(n^3) T(n)=O(n3)
-
这就是求解矩阵相乘问题的算法的渐进时间复杂度。
-
-
一般情况下,不必计算所有操作的执行次数,而只考虑算法中基本操作执行的次数,它是问题规模n的某个函数,用T(n)表示。
-
算法中基本语句重复执行的次数是问题规模n的某个函数f(n),算法的时间量度记作:
-
T ( n ) = O ( f ( n ) ) T(n)=O(f(n)) T(n)=O(f(n))
-
基本语句
- 算法中重复执衍次数和算法的执行时间成正比的语句;
- 对算法运行时间的贡献最大;
- 执行次数最多人。
-
问题规模n:n越大算法的执行时间越长。
- 排序:n为记录数;
- 矩阵:n为矩阵的阶数;
- 多项式:n为多项式的项数;
- 集合:n为元素个数;
- 树:n为树的结点个数;
- 图:n为图的顶点数或边数。
-
-
它表示随着n的增大,算法执行的时间的增长率和f(n)的增长率相|同,称渐近时间复杂度。
-
若f(n)=amnm+am-1nm-1+……+a1n+a0是m次多项式, 则T(n)=O(nm)。
分析算法时间复杂度的基本方法:
- 1、找出语句频度最大的那条语句作为基本语句;
- 2、计算基本语句的频度得到问题规模n的某个函数f(n);
- 3、取其数量级用符号“O”表示。
时 间 复 杂 度 是 由 嵌 套 最 深 层 语 句 的 频 度 决 定 的 ! ! ! 时间复杂度是由嵌套最深层语句的频度决定的!!! 时间复杂度是由嵌套最深层语句的频度决定的!!!
- 例1,分析如下程序段的时间复杂度。
- 例2,分析如下程序段的时间复杂度。
-
设 语 句 ② 执 行 次 数 为 x 次 , 由 循 环 条 件 i < = n , ∴ 2 x < = n , ∴ p < = l o g 2 n begin{aligned} & 设语句②执行次数为x次,\ & 由循环条件i<=n, \ & ∴2^x<=n, \ & ∴p<=log_2n end{aligned} 设语句②执行次数为x次,由循环条件i<=n,∴2x<=n,∴p<=log2n
-
2f(n) ≤ n;即f(n) ≤ log2n ,取最大值f(n)= log2n 。
-
所以该程序段的时间复杂度T(n)=O( log2n )。
请注意:有的情况下,算法中基本操作重复执行的次数还随问题的输入数据集不同而不同。
- 最好情况:1次;
- 最坏情况:n;
- 平均时间复杂度为:O(n)。
- 最坏时间复杂度:指在最坏情况下,算法的时间复杂度。
- 平均时间复杂度:指在所有可能输入实例在等概率出现的情况下,算法的期望运行时间。
- 最好时间复杂度:指在最好情况下,算法的时间复杂度。
- 一般总是考虑在最坏情况下的时间复杂度,以保证算法的运行时间不会比它更长。
对于复杂的算法,可以将它分成几个容易估算的部分,然后利用大O加法法则和乘法法则,计算算法的时间复杂度:
- 加法法则
T ( n ) = T 1 ( n ) + T 2 ( n ) = O ( f ( n ) ) + O ( g ( n ) ) = O ( m a x ( f ( n ) , g ( n ) ) ) T(n)=T1(n)+T2(n)=O(f(n))+O(g(n))=O(max(f(n),g(n))) T(n)=T1(n)+T2(n)=O(f(n))+O(g(n))=O(max(f(n),g(n)))
- 乘法法则
T ( n ) = T 1 ( n ) × T 2 ( n ) = O ( f ( n ) ) × O ( g ( n ) ) = O ( f ( n ) × g ( n ) ) T(n)=T1(n)×T2(n)=O(f(n))×O(g(n))=O(f(n)×g(n)) T(n)=T1(n)×T2(n)=O(f(n))×O(g(n))=O(f(n)×g(n))
- 当n取得很大时,指数时间算法和多项式时间算法在所需时间上非常悬殊。
1.4.4 算法的效率——空间复杂度
空间复杂度:算法所需存储空间的度量,记作:S(n)=O(f(n))。其中n为问题的规模(或大小)。
- 算法要占据的空间:
- 算法本身要占据的空间,输入/输出,指令,常数,变量等;
- 算法要使用的辅助空间;
- 【例】将一维数组a中的n个数逆序存放到原数组中。
设计好算法的过程:
下一篇:第二章 线性表
最后
以上就是如意金针菇为你收集整理的数据结构与算法-C版(王卓)day01数据结构与算法(王卓)的全部内容,希望文章能够帮你解决数据结构与算法-C版(王卓)day01数据结构与算法(王卓)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复