数据结构与算法自学笔记
基础概念
这是一个用于记录我学习数据结构的系列,其中总结了一些我对于各部分内容重点的划分,对一些内容加入了一些自己的理解,用于加深自己的学习。
数据、数据对象、数据元素、数据项
数据元素:组成数据的、有一定意义的基本单位
数据项:一个数据元素可由若干个数据项组成,数据项是数据不可分割的最小单位。比如人这样的元素,可以有眼、耳、鼻、嘴等数据项,也可以有姓名、年龄、性别等数据项。
表A:人员表
姓名 | 性别 | 身高 | 课程代号 |
---|---|---|---|
小明 | 男 | 175 | 1 |
表B:课程表
课程代号 | 课程名 |
---|---|
1 | 语文 |
这两张表就是数据
而单独的一张表就称为数据对象,即人员表是一个数据对象,课程表也是一个数据对象
而每张表中的每一行就称为数据元素
而姓名,性别,身高,课程代号,课程名就称为数据项
数据结构
数据结构:是相互之间存在一种或者多种特定关系的数据元素的集合。简单理解就是关系。
包含三方面内容:
- 数据元素之间的逻辑关系,也称为逻辑结构。
- 数据元素及其关系在计算机内存中的表示(又称映像),称为数据的物理结构或者存储结构。
- 数据的运算和实现,即对数据元素可以施加的操作以及这些操作在相应的存储结构上的实现。
存储结构和逻辑结构的关系:
-
存储结构是逻辑关系的映象和元素本身的映象;
-
逻辑结构是存储结构的抽象,存储结构是数据结构的实现。(物质决定意识,意识是对物质的反映)
-
两者综合建立了数据元素之间的结构关系。
逻辑结构是所有数据中有相同逻辑关系的数据元素集合,存储结构是数据元素以及数据元素之间的对应关系是如何在放在计算机内存中的。
逻辑结构的种类
划分方法一:
线性结构:有且仅有一个开始和一个终端节点,并且所有的节点都最多有一个直接前趋和直接后继。
例如:线性表、栈、队列、串、链表
非线性结构:一个节点有多个直接前趋和直接后继
例如:树、图
划分方法二——四类基本逻辑结构:
集合结构:结构当中的数据元素除了同属于一个集合外,无任何其他关系。
线性结构:结构中的数据元素存在着一对一的的线性关系。
树形结构:结构中的数据元素存在着一对多的的层次关系。
图状结构或网状结构:结构中的数据元素存在着一对多的的任意关系。
存储结构的种类
四种基本的存储结构:
◎顺序存储结构:用一组连续的存储单元依次存储数据元素,数据元素之间发逻辑关系由关系的存储位置来表示。
C语言中用数组来实现顺序存储结构。
◎链式存储结构:用一组任意的存储单元存储数据元素,数据元素之间的逻辑关系用指针来表示。
C语言中用链表来表示链式存储结构。
索引存储结构:在存储节点信息的同时,创建索引表。
散列存储结构:哈希表(散列表)。
数据类型和抽象数据类型
使用高级程序设计语言编写程序时,必须对程序中出现的每个变量、常量或表达式明确说明它们所属的数据类型。
C语言中:
-
提供int、float、double、char等基本数据类型
-
数组、结构、共用体、枚举等构造数据类型
-
还有 指针、空(void)类型
-
也可以使用typedef自定义数据类型(对数据类型进行重命名)
一些基本的数据结构可以用数据类型来实现,如:数组、字符串等。而另一些常用的数据结构,如:栈、队列、树、图等,不能直接用数据类型来表示。
抽象数据类型(Abstract Data Type,ADP)
抽象(共性提炼)
是指一个数学模型以及定义在此数学模型上的一组操作。
由用户定义,从问题抽象出数据模型(逻辑结构)
还包括定义在数据模型上的一组抽象运算(相关操作)
抽象数据类型的形式定义:
抽象数据类型可用(D、S、P)三元组表示。
其中:
- D 是数据对象
- S 是D上的关系集
- P 是对D的基本操作
一个抽象数据类型的定义格式
以Java为例
ADT 抽象数据类型名{ 类
数据对象:<数据对象的定义> 类中的参数
数据关系:<数据关系的定义> 构造方法,将参数封装到对象中
基本操作:<基本操作的定义> 方法
}ADT 抽象数据类型名
其中:
数据对象、数据关系的定义用伪代码描述
基本操作的定义格式为:
基本操作名(参数表) 方法名(形参)
初始条件<初始条件描述> 形参的条件
操作结果<操作结果描述> 返回值
引用:
算法和算法分析
算法与程序:
- 算法是解决问题的一种方法或者一个过程,考虑如何将输入转化为输出,一个问题可以有多种算法。
- 程序使用某种程序语言对算法的具体实现。
程序 = 数据结构 + 算法
数据结构通过算法实现操作,算法通过数据结构设计程序
算法特性:一个算法必须具备五个重要特性
- 有穷性:必须总是在执行又穷步后结束,并且每一步都在又穷时间内完成。
- 确定性:每一条指令必须有确切的含义,不能有二义性,在任何条件下,只有唯一的一条执行路径,即对于相同的输入只能得到相同的输出。
- 可行性:算法是可执行的,算法描述的操作可以通过已经实现的基本操作执行有限次来实现。
- 输入:有零个或多个输入。
- 输出:有一个或多个输出。
算法设计的要求:
- 确定性(Correctness)
- 可读性(Readability)
- 健壮性(Robustness) 鲁棒性
- 高效性(Efficiency) 要求花费尽量少的时间和尽量低的储存需求
算法效率:
- 时间效率:算法所耗费的时间
- 空间效率:算法执行过程中所耗费的储存空间
(二者有时是矛盾的)
算法时间效率的度量
算法时间效率可以用依据该算法编制的程序在计算机上执行所消耗的时间来度量。
两种度量方法:
- 事后统计:将算法实现,测算其时间和空间的开销
缺点: 编写程序实现算法将花费较多的时间和精力;所得实验结果依赖于计算机的软硬件等环境因素,掩盖算法本身的优劣。 - 事前分析:对算法所消耗资源的一种估算方法。
事前分析方法:
一个算法的运行时间是指一个算法在计算机上运行所耗费的时间大致可以等于计算机执行一种简单的操作(如赋值、比较、移动等)所需的时间与算法中进行的简单操作次数的乘积。
算法运行时间 = 一个简单操作所需时间 简单操作次数
也即:
算法运行时间 = 每条语句的执行次 * 该语句执行一次所需时间
(每条语句的执行次又称语句频度)
(随机器而异。取决于机器的指令性能、速度以及编译的代码质量。是由机器本身软硬件环境决定的,与算法无关)
(假设执行每条语句所需时间均为单位时间,就不再考虑语句的执行时间,转化为讨论该算法中所有语句的执行次数,即频度之和。这样就可以脱离软硬件环境来分析算法的时间性能)
将算法所耗费时间定义为该算法中每条语句的频度之和
算法时间复杂度的渐进表示法:
仅比较数量级
若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不为零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n))为算法的渐近时间复杂度(O是数量级的符号),简称时间复杂度。
如图所示。
算法中基本语句重复执行的次数:
- 算法中重复执行次数和算法的执行时间成正比的语句
- 对算法运行时间贡献最大的
- 执行次数最多
问题规模n:
n越大算法的执行时间越长
- 排序:n为记录数
- 矩阵:n为矩阵阶数
- 多项式:n为多项式项数
- 集合: n为元素个数
- 树:n为树的结点个数
- 图:n为图的定点数或边数
- 嵌套循环: n为最深层的层数
分析算法时间复杂度的基本方法:
- 找出语句频度最大的作为基本语句;
- 计算基本语句的频度得到问题规模n的某个函数f(n);
- 取其数量级用符号“O”表示。
例题:
i = 1; ①
While(i<=n)
i = i*2; ②
当执行第x次后 i = 2^x
因为 i ≤ n
所以 2^x≤ n
即 x ≤ n
故 f(n) ≤ log2 n 取最大值 f(n) = log2 n
算法时间复杂度:
- 最坏时间复杂度:最坏情况下,算法的时间复杂度
- 平均时间复杂度:指在所有可能输入实例在等概率出现的情况下,算法的期望运行时间
- 最好时间复杂度:最好情况下,算法的时间复杂度
(一般只考虑最坏时间复杂度,以保证算法的运行时间不会比它更长)
对于复杂的算法,可以将其分为几个容易估算的部分,然后计算算法的时间复杂度
A)加法法则
T(n) = T1(n) + T2(n) = O(f(n)) + O(g(n)) = O(max(f(n),g(n)))
B)乘法法则
T(n) = T1(n) * T2(n) =O(f(n)) * O(g(n)) = O(f(n) * g(n))
算法时间效率的比较:
当n取值很大时,指数时间算法和多项式时间算法在所需时间上非常悬殊
时间复杂度T(n)按数量级递增顺序为:
常数阶 | 对数阶 | 线性阶 | 线性对数阶 | 平方阶 | 立方阶 | …… | K次方阶 | 指数阶 |
---|
渐近空间复杂度
空间复杂度:算法所需存储空间的度量。
记作:S(n) = O(f(n))
其中n为问题的规模(或大小)
算法要占据的空间:
- 算法本身要占据的空间、输入/输出、指令、常数、变量等
- 算法要使用的辅助空间