数据结构(一)目录
复习目标
- 掌握基本的数据结构(如线性表、树、图)和基本的算法(如遍历、查找、排序)
- 能了解、判定对同一个问题采用不同的数据结构带来的性能差异
- 针对问题需求,能够选择或设计合适的数据结构
整体概要
- 基本概念
- 线性表
- 栈和队列
- 串
- 数组和广义表
- 树和二叉树
- 图
- 动态存储管理
- 查找
- 内部排序
- 外部排序
编程练习网站
www.topcoder.com/
https://leetcode.com/
http://acm.hdu.edu.cn/
http://poj.org/
http://bailian.openjudge.cn/
数据结构的重要性
编写程序是设计、开发计算机系统功能的最核心工作。
算法和数据结构是程序设计方法学的核心。
基本概念
数据 Data
数据是对客观事物的符号表示。在计算机科学中是指所有能输入到计算机中并被计算机程序处理的符号的总称(i.e. 数值、文字、图形、图像、视频、声音…)
数据元素 Data Element
数据元素是数据的基本单位,通常作为一个整体来进行考虑和处理。一个数据元素可由若干个数据项(Data Item)(不可分割的最小单位)组成。
数据结构 Data structure
数据结构是指互相之间存在一定关系的数据元素的集合,包括逻辑结构和物理结构
结构 / 逻辑结构
数据元素之间的互相关系为(逻辑)结构。通常为以下4种结构:
- 集合:数据元素除了同属于一个集合外,没有其它关系
- 线性结构:数据元素之间存在一对一的关系(i.e. 身份证号和人)
- 树型结构:数据元素之间存在一对多的关系(i.e. 磁盘目录文件系统)
- 图状结构/网状结构:数据元素之间存在多对多的关系(i.e. 交通网络求最短距离)
物理结构
数据的物理结构(即存储方式)是其逻辑结构在计算机中的表示和实现,要考虑数据元素的存储和元素之间的关系表示。
元素之间的关系表示有顺序表示(数组)和非顺序表示(指针,链式)两种方法→两种存储结构:顺序存储结构和链式存储结构
PS:数据的逻辑结构给出了数据元素之间的逻辑关系的描述。数据的逻辑结构和物理结构是密不可分的两个方面:一个算法的设计取决于所选定的逻辑结构,而算法的实现依赖于所采用的存储结构。
数据类型 Data Type
数据类型是指一个值的集合和定义在该值集合上的一组操作的总称。
抽象数据类型 Abstract Data Type(ADT)
ADT是指一个数学模型以及定义在该模型上的一组操作。
实现:ADT需要通过固有数据类型(高级编程语言中已实现的数据类型)来实现。
使用:ADT的定义只取决于它的逻辑特性,与具体怎么实现和表示无关,不论ADT的内部结构如何变化,只要其数学特性不变,都不影响其外部使用。(类似提供接口,内部功能封装好了不用管)
ADT 特征
① 数据抽象:
用ADT描述程序处理的实体时,强调的是其本质的特征、其所能完成的功能以及它和外部用户的接口(即外界使用它的方法)。
② 数据封装:
将实体的外部特性和其内部实现细节分离,并且对外部用户隐藏其内部实现细节
ADT形式化定义
ADT的形式化定义是三元组:
A
D
T
=
(
D
,
R
,
P
)
ADT=(D,R,P)
ADT=(D,R,P)
其中D是数据对象,R是D上的关系集,P是对D的基本操作集。
ADT的一般定义形式是:
ADT <抽象数据类型名>{
数据对象D: <数据对象的定义>
数据关系R: <数据关系的定义>
基本操作P: <基本操作的定义>
} ADT <抽象数据类型名>
<基本操作名>(<参数表>)
初始条件: <初始条件描述>
操作结果: <操作结果描述>
算法 Algorithm
算法是对特定问题求解步骤的一种描述,是指令的有限序列,其中每一条指令表示一个或多个操作。
算法的特性
- 有穷性:一个算法必须总是在执行有穷步之后结束,且每一步都可在有穷时间内完成
(一个算法不收敛,是否违反了“有穷性”的要求?错)
PS:算法的收敛性就是指某个算法能否在迭代时间趋于无穷的假设下,最终找到问题的全局最优解。算法收敛性是迭代法中的一个概念,所以主要针对跟迭代相关的算法,如进化算法。对于能够一次求解的直接法,就不在算法收敛的讨论范围之内了。 - 确定性:算法中每一条指令必须有确切的含义。不存在二义性。在任何条件下,只有唯一的一条执行路径。
(对于相同的输入,只能得到相同的输出?错,例如存在随机变量) - 可行性: 一个算法是能行的,即算法描述的操作都可以通过已经实现的基本运算执行有限次来实现
- 输入: 一个算法有零个或多个输入,这些输入取自于某个特定的对象集合
- 输出: 一个算法有一个或多个输出,这些输出是同输入有着某些特定关系的量
算法设计的基本思路
- 暴力法(Brute force method)
枚举/穷举(Enumeration):图的遍历(DFS,BFS) - 分而治之法(Divide and conquer):分解成若干独立的子问题
递归:分解后的所有子问题是一个小规模的原问题:Fibonacci数列
对广义表的操作,快速排序,归并排序 - 动态规划法(Dynamic programming algorithm):分解成若干子问题,但子问题之间不相互独立
Fibonacci数列,背包问题 - 贪心法(Greedy algorithm):根据当前已有信息做出最优选择:
背包问题,装箱(Bin packing)问题
Huffman编码,(图)最短路径,(图)最小生成树
算法评价标准
- 正确性(Correctness):算法应满足具体问题的需求;对算法是否“正确”的理解可以有以下四个层次:
① 程序中不含语法错误
② 程序对于几组输入数据能够得出满足要求的结果
③ 程序对于精心选择的、典型、苛刻且带有刁难性的几组输入数据能够得出满足要求的结果
④ 对于一切合法的输入数据都能得出满足要求的结果 - 可读性(Readability): 算法应容易供人阅读和交流。可读性好的算法有助于对算法的理解和修改
- 健壮性(Robustness): 算法应具有容错处理。当输入非法或错误的数据时,算法应能适当地作出反应或进行处理,而不会产生莫名其妙的输出
处理出错的方法不应是中断程序的执行,而应是返回一个表示错误或错误性质的值
出错处理方式举例:
① 用exit语句终止执行并报告错误:其优点是直观、嵌套层次少;缺点是中断函数的执行,故不适宜用在子函数中
② 用函数的返回值区别正确返回或错误返回:其优点是将错误返回给调用环境,由调用环境决定程序的下一步走向
③ 在函数的参数表中设置整形变量,以区别正确返回或错误返回:其优点同上,并可判别多种类型的错误 - 高效率(High efficiency)与低存储量(Low memory space)需求: 效率指的是算法执行的时间;存储量需求指算法执行过程中所需要的最大存储空间
一般地,这两者与问题的规模有关
算法效率的度量
算法的执行时间
执行时间需通过依据该算法编制的程序在计算机上运行所消耗的时间来度量。
通常方法有事前分析法(求出该算法的一个时间界限函数)和事后统计法(计算机内部进行执行时间和实际占用空间的统计)
撇开与软硬件等有关的因素,可以认为算法的时间效率即一个特定算法“运行工作量”的大小只依赖于问题的规模(通常用n表示)。
算法的时间复杂度
算法的渐进分析(Asymptotic analysis):关注算法的时间复杂度的总体变化趋势和增长速度
算法的渐近时间复杂度(Asymptotic Time Complexity,简称时间复杂度) :
T
(
n
)
T(n)
T(n)
表示时间复杂度的阶:
O
(
1
)
O(1)
O(1):常量阶
O
(
n
)
O (n)
O(n):线性阶
O
(
n
2
)
O(n^2)
O(n2):平方阶
O
(
2
n
)
O(2^n)
O(2n):指数阶
O
(
㏒
n
)
O(㏒n)
O(㏒n) :对数阶
O
(
n
㏒
n
)
O(n㏒n)
O(n㏒n):线性对数阶
O
(
n
k
)
O (n^k)
O(nk):k次方阶(k≥2)
O
(
n
!
)
O(n!)
O(n!):阶乘阶
关系为:
O
(
1
)
<
O
(
㏒
n
)
<
O
(
n
)
<
O
(
n
㏒
n
)
<
O
(
n
2
)
<
O
(
n
3
)
<
O
(
2
n
)
<
O
(
n
!
)
<
O
(
n
n
)
O(1)<O(㏒n)<O(n)<O(n㏒n)<O(n^2)<O(n^3) <O(2^n)<O(n!)<O(n^n)
O(1)<O(㏒n)<O(n)<O(n㏒n)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)
算法的空间复杂度
空间复杂度(Space complexity) 是指算法编写成程序后,在计算机中运行时所需存储空间大小的度量
S
(
n
)
=
O
(
f
(
n
)
)
S(n)=O(f(n))
S(n)=O(f(n))
其中: n为问题的规模(或大小)
该存储空间一般包括三个方面:
① 程序本身所占用的存储空间
② 输入数据所占用的存储空间
③ 辅助(存储)空间
一般地,算法的空间复杂度指的是辅助空间
i.e. 一维数组a[n]: 空间复杂度 O(n)
i.e. 二维数组a[n][m]: 空间复杂度 O(n*m)
PS:原地算法——若所需额外空间相对于输入数据量来说是常数,即,所占有的临时空间不随问题的规模而改变,则称此算法为原地(in-place)算法,其空间复杂度为O(1)
若所需存储量依赖于特定的输入,则通常按最坏情况考虑