数据结构与算法 第一章 概论(笔记)

文章目录

  • 第一章 概论
    • 1.1 为什么要学习数据结构
    • 1.2 什么是数据结构
      • 一、数据与数据结构
      • 二、数据的逻辑结构
      • 三、数据的存储结构
      • 四、数据的运算
    • 1.3 抽象数据类型
      • 一、抽象数据类型的概念
      • 二、ADT 的两个重要特征
      • 三、抽象数据类型的描述方法
    • 1.4 算法及其特性
      • 一、算法的概念
      • 二、计算复杂性和算法的效率
    • 1.5 算法的执行效率及其度量
      • 一、算法的执行效率及其度量
      • 二、渐进分析法
        • 1、大O表示法
        • 2、$\Omega$表式法
        • 3、 $\Theta$表式法
      • 三、最坏、最好和平均情况
      • 四、时间和空间资源开销
    • 1.6 数据结构的选择与评价

第一章 概论

1.1 为什么要学习数据结构

算法与数据结构在知识体系中的位置是非常重要的。
数据结构与算法 第一章 概论(笔记)_第1张图片
算法与数据结构知识体系
数据结构与算法 第一章 概论(笔记)_第2张图片
概括地说:
算法与数据结构是一门讨论“描述现实世界实体的数学模型(非数值计算)及其上的操作在计算机中如何表示和实现”的学科。
——唐·欧·克努特(美国)

1.2 什么是数据结构

一、数据与数据结构

1、数据:
所有能被输入到计算机中,且能被计算机处理的符号(数值、字符等)的集合。是计算机操作的对象的总称。是计算机处理的信息的某种特定的符号表示形式。

2、数据元素(结点):
是数据(集合)中的一个“个 体”,在计算机中通常作为一个整体进行考虑和处理。是数据结构中讨论的基本单位。
如:整数“5”,字符“N”等。(是不可分割的“原子”)

数据元素也可以由若干款项构成。
例如:描述一个学生的数据元素,其中每个款项称为一个“数据项”,它是数据结构中讨论的最小单位

姓 名 学号 班号 性别 出生日期 入学成绩
年 月 日

其中第一排是原子项,第二排的“年 月 日”是组合项,组合成出生日期这个原子项。

3、结点(数据元素)的类型
(1)基本数据类型

  1. 整型
  2. 实型
  3. 布尔型
  4. 字符型
  5. 指针型

(2)复合数据类型

  1. 结构体
  2. 数组

4、数据结构:
有一个特性相同的数据元素的集合,如果在数据元素之间存在一种或多种特定的关系,则称为一个数据结构

带结构的数据元素的集合——指的是数据元素之间存在的关系
例如:
3214, 6587, 9345 ─ a1(3214), a2(6587), a3(9345)
则在数据元素 a1、a2 和 a3 之间存在着“次序”关系 ,

3214 6587 9345
a1 a2 a3
6587 3214 9345
a2 a1 a3

又例,在2行3列的二维数组中六个元素{a1, a2, a3, a4, a5, a6}
之间存在两个关系:

a1 a2 a3
a3 a4 a5

行的次序关系:
row = {,,,}

列的次序关系:
col = {,,}

若在 6 个数据元素{a1, a2, a3, a4, a5, a6} 之间存在如下的次序关系:{| i=1, 2, 3, 4, 5}则构成一维数组的定义。
可见,不同的“关系”构成不同的“结构”。

数据结构是相互之间存在着某种逻辑关系的数据元素的集合。

5、从关系或结构分类
数据结构可归结:
为以下四类:

  • 线性结构
  • 树形结构
  • 图状结构
  • 集合结构

数据结构包括“逻辑结构” 和“物理结构”两个方面(层次):
逻辑结构:
是对数据元素之间的逻辑关系的描述,它可以用一个数据元素的集合和定义在此集合上的若干关系来表示;
物理结构:
是逻辑结构在计算机中的表示和实现,故又称“存储结构” 。

二、数据的逻辑结构

数据结构的形式定义描述为:
数据结构是一个二元组
Data_Structures = (K, R)
例如:定义 “班集体”为一个数据结构
Class = (K, R)
K = { a, b1,…,bn, c1,…cn, d1,…dn }
R = { R1, R2 }
R1 = { ,,}
R2 = { , , | j = 2, 3, …, n }
其中:K 是数据结点的有限集, R 是 K上关系的有限集。

三、数据的存储结构

——逻辑结构在存储器中的映象

数据元素(结点)的映象方法:
用二进制位(bit)的位串表示数据元素
(321)10 = (501)8 = (101000001)2
A = (101)8 = (001000001)2
关系的映象方法:(表示的方法)
1、顺序的方法
以相对的存储位置表示后继关系
例如:令 y 的存储位置和 x 的存储位置之间差一个常量 C ,而 C 是一个隐含值,整个存储结构中只含数据元素本身的信息在这里插入图片描述
数据结构与算法 第一章 概论(笔记)_第3张图片
顺序存储结构常用于线性数据结构,将逻 辑上相邻的数据元素存储在物理上相邻的存储单元里。

顺序存储结构的三个弱点:

  • 作插入或删除操作时,需移动大量元数。
  • 长度变化较大时,需按最大空间分配。
  • 表的容量难以扩充。

2、链接的方法
以附加信息(指针)表示后继关系需要用一个和 x 在一起的附加信息指示 y 的存储位置。
数据结构与算法 第一章 概论(笔记)_第4张图片
每个节点都由两部分组成:数据域和指针域。
数据域存放元素本身的数据,
指针域存放指针。
数据结构与算法 第一章 概论(笔记)_第5张图片
数据元素之间逻辑上的联系由指针来体现。数据结构与算法 第一章 概论(笔记)_第6张图片
链接存储结构特点:

  • 比顺序存储结构的存储密度小
    (每个节点都由数据域和指针域组成)。
  • 逻辑上相邻的节点物理上不必相邻。
  • 插入、删除灵活(不必移动节点,只要改变节点中的指针)。

3、索引的方法
索引法是顺序存储法的一种推广,它也使用整数编码来访问数据结点位置。
索引方法是要建造一个由整数域Z映射到存储地址域D的函数Y:Z–>D,把结点的整数索引值z∈Z映射到结点的存储地址d∈D。它称为索引函数,一般而言它并不象数组那样,是简单的线性函数。当数据结点长度不等的情况下,索引函数就无法用线性表达式给出。
数据结构与算法 第一章 概论(笔记)_第7张图片
4、散列的方法
散列方法是索引方法的一一种延伸和扩展。利用一种称为散列函数(hash functions)进行索引值的计算,然后通过索引表求出结点的指针地址。
散列函数是将字符串s映射到非负整数z的一类函数h: S–> Z,对任意的s∈S,散列函数h(s)=z, z∈Z。
数据的逻辑结构与存储结构密切相关

  • 算法设计 —> 逻辑结构
  • 算法实现 —>存储结构

四、数据的运算

检索、排序、插入、删除、修改等。
数据结构与算法 第一章 概论(笔记)_第8张图片

1.3 抽象数据类型

一、抽象数据类型的概念

(Abstract Data Type 简称ADT)
1、定义:是指一个数学模型以及定义在此数学模型上的一组操作。
2、特点:将数据和操作封装在一起。
3、目的 :隐藏运算实现的细节和内部数据结构;提高复用的力度和粒度。

二、ADT 的两个重要特征

1、数据抽象:
用ADT描述程序处理的实体时,强调的是其本质的特征、其所能完成的功能以及它和外部用户的接口(即外界使用它的方法)。
2、数据封装:
将实体的外部特性和其内部实现细节分离,并且对外部用户隐藏其内部实现细节。(使用与实现相分离)

三、抽象数据类型的描述方法

抽象数据类型可用(D,S,P)三元组表示
其中,

  • D 是数据对象,
  • S 是 D 上的关系集,
  • P 是对 D 的基本操作集。

ADT 抽象数据类型名 {
数据对象:〈数据对象的定义〉
数据关系:〈数据关系的定义〉
基本操作:〈基本操作的定义〉
} ADT 抽象数据类型名

抽象数据类型

	template <class Type> // 模板参数为类型Type
	class className {
		private: // 数据结构的的取值类型和取值空间
		Type dataList; // 定义数据及其存储方式
		……
		public: // 运算集
		methodName(); // 定义对数据的操作
		……
	};

1.4 算法及其特性

一、算法的概念

算法是为了求解问题而给出的指令序列。
1、算法的五个重要特性:

  • 有穷性
    算法的执行必须在有穷步骤内结束,即:算法不能含有死循环。
  • 确定性
    算法每执行一步之后,关于它的下一步,应该有明确的指示。下一步动作可以是条件判断、分支指令、或顺序执行一条指令、或者指示整个算法的结束等。算法的确定性要保证每一步之后都有关于下一步动作的指令,不能缺乏下一步指令(被锁住)或仅仅含有模糊不清的指令。
  • 有效性
    算法中每一条指令都必须是能够被确切执行的,被人或机器所执行。指令的类型应该明确规定,仅限于若干明确无误的指令动作,是一个有限的指令集。其结果应具有确定的数据类型,是能够预期的。
  • 通用性
    对于那些符合输入类型的任意输入数据,都能根据算法进行问题求解,并保证计算结果的正确性。
  • 输入与输出:
    0个或多个输入,1个或多个输出。

2、算法分类

  • 算法设计与算法分析是计算机科学的核心问题
  • 常用的设计方法
    穷举法、贪心法 (Huffman树、Prim等)、递归法,、分治法(二分检索、快速排序等) 、回溯法(树、图等的深度优先搜索)、动态规划法(最佳二叉排序树) 、α-β裁剪和分枝界限法、并行算法等等。

二、计算复杂性和算法的效率

1、计算复杂性理论(computational complexity theory)指出,理论上存在一大类难解问题,它们虽然存在着求解算法,但是在算法的计算时间上,都是组合爆炸型的求解算法。
2、组合爆炸型是指随着问题的规模n的增大,算法的时间开销不能约束在n的k阶多项式数量范围内。
3、比较常见的难解问题有:图论中的求最优巡游路径问题,判定命题逻辑公式是否为恒真等。

1.5 算法的执行效率及其度量

一、算法的执行效率及其度量

1、解决同一个问题存在多种算法,评估各算法的好坏或据此设计出更好的算法的依据是:运行该算法所需要的计算机资源的多寡,所需越大复杂性越高。
最重要的资源:时间(处理器)和空间(存储器)
2、评价一个算法优劣的重要依据是看执行该算法的程序需要占用多少机器资源,即程序所用算法运行时所要花费的时间代价和程序中使用的数据结构占有的空间代价。
3、算法时间复杂性
不能用诸如微秒、纳秒这样的真实时间单位。
例如:

  • 一个运行在Cray机上的算法若放在PC机会慢很多;
  • 一个运行在Cray机上的效率极差的算法也许比一个运行在PC上的效率很高的算法花费更少的时间;
  • 同样的算法运行在同样的机器上也会因使用不同的程序语言而存在差异;( C vs LISP)
  • 两个不同的算法也许在输入规模为100时表现不相上下,而在输入规模扩大10倍后却表现迥异算法复杂性分析;
  • 算法分析感兴趣的不是具体的资源占用量,而是算法与输入规模之间的关系。

二、渐进分析法

算法的渐近分析

  • 算法的渐进分析,简称算法分析。算法在计算机上实际执行时需要消耗时间资源(归结为CPU执行指令的总数),和使用空间资源(归结为所需占用的存储单元数量,字节数)。
  • 由于算法分析和它所求解的问题规模直接有关,因此通常将问题规模n作为一个参照量,求算法的时空开销和n的关系。
  • 估算算法的时间复杂度
    • 算法 = 控制结构 + 原操作(固有数据类型的操作)
    • 算法的执行时间 = ∑ \sum 原操作(i)的执行次数×原操作(i)的执行时间
    • 由此公式得出:算法的执行时间与原操作执行次数之和成正比。
    • 算法的时间复杂度:是该算法中所有语句的执行频度之和(执行次数之和)。
    • 语句的频度(Frequency Count),即该语句重复执行的次数。

1、大O表示法

(1)算法的渐进分析(大O表示法)就是要估计,当数据规模n逐步增大时,资源开销T(n)的增长趋势。即得到一个大 O渐进表达式,简写为:
r a t e n → + ∞ T ( n ) = O ( F ( n ) ) rate_{n\rightarrow+\infty} T(n) = O(F(n)) raten+T(n)=O(F(n))

  • 其中F(n)是自变量为n的某个具体的整函数表达式,例如F(n)= n 2 n^2 n2;

严格的数学定义:

  • 若T(n) 和 f(n) 是定义在正整数集合上的两个函数,当存在两个正常数c和n0时,使得对所有的n≥ n0,
    • 都有︱T(n) ︳≤c|f(n) ︳ •
    • 则记作 T(n)=O(f(n))(O表示计算时间的上界函
      数)

这时称T(n)的时间复杂度为f(n)数量级。(或称为:算法具有O(f(n))的计算时间)
(2)大O表示法的运算规则

  • 单位时间
    • 简单布尔或算术运算
    • 简单I/O
    • 函数返回
  • 加法规则: T1(n)+T2(n)=O(max(T1(n), T2(n)))
    • 顺序结构,if 结构,switch结构
  • 乘法规则: T1(n)·T2(n) =O(T1(n)·T2(n))
    • for, while, do-while结构

例1:

1. x=0; //1 T(n)=1
2. y=0;//1 T(n)=1
3. for (k=1;k<=n;k++) //n
		x++; //n
   //T(n)=1+2n 该算法时间复杂度为: T(n)=O(n)
4. for (i=1;i<=n;i++) //n 
	for (j=1;j<=n;j++) //n*n
		y++; //n*n
  //T(n)=2n+2n2+1
  //当n充分大时, T(n)与n2是同阶的。
  //该算法时间复杂度为:T(n)=O(n2)

例2:

for (i=1; i<=n; ++i) // n 
	for (j=1; j<=n; ++j) // n*n 
		c [i][j]=0; // n2
		//T(n)=2n2+2n+1
  • 当n充分大时, T(n)与n2是同阶的。
  • 该算法时间复杂度为:T(n)=O(n2)

定理:若A(n)=a m nm +a m-1 nm-1 +…+a1n+a0是一个m次多项式,则A(n)=O(nm)
例3:

{++x;s=0;}

将x自增看成是基本操作,则语句频度为1,即时间复杂度为O(1)
如果将s=0也看成是基本操作,则语句频度为2,其时间复杂度仍为O(1),即常量阶
例4:

for(I=1;I<=n;++I)
	{++x;s+=x;}

循环体中语句频度为:2n, 其时间复杂度为:O(n),即时间复杂度为线性阶。
例5:

for(I=1;I<=n;++I)
	for(j=1;j<=n;++j)
		{++x;s+=x;}

循环体中语句频度为: 2 n 2 2n^2 2n2其时间复杂度为:O($n^2S) ,即时间复杂度为平方阶。
例6:
求两个方阵的乘积 C=A*B

#define n 自然数
MATRIXMLT(float A[n][n],float B[n][n],float C[n][n])
{
	int i,j,k;
	for(i=0;i<n;i++) //n
		for(j=0;j<n;j++) //n*n
		{
			C[i][j]=0; //n*n
			for( k=0;k<n;k++) //n*n*n
				C[i][j]=A[i][k]*B[k][j] //n*n*n
		} 
} 

分析:由于是一个三重循环,每个循环从1到n,则总次数为: 2 n 3 + 3 n 2 + 2 n + 1 2n^3+3n^2+2n+1 2n33n22n+1。所以,时间复杂度为T(n)=O( n 3 n^3 n3),即时间复杂度为立方阶。
例7:
若只对每个子数组的前5个元素求和,则相应的代码可采用下面的方式:

for ( i=4; i<n; i++) 
	for (j = i-3, sum = a[i-4]; j <= i; j++) 
		sum += a[j]; 
  • 外层循环进行n-4次
  • 对每个 i 而言,内层循环只执行4次,每次的操作次数和 i 的大小无关:8次赋值操作
  • 整个代码总共进行1+ 3(n-4) +8(n-4)=O(n)次
  • 看似双重循环,其实线性时间

(3)用于渐进分析的常见的F(n)

  • F(n)=1,常数函数,不依赖于n;
  • F(n)= l o g n logn logn,对数函数,它比线性函数n增长慢;
  • F(n)= n,线性增长,随着问题规模n而增长;
  • F(n)= n 2 n^2 n2, 二阶增长;
  • F(n)= l o g n logn logn,其增长率的阶数低于二阶,但高于一阶线性;
  • F(n)= a n a^n an,指数增长,随着问题规模n而增长极快。

以下六种计算算法时间的多项式是最常用的。
其关系为:
O(1) l o g n logn logn) n l o g n nlogn nlogn) n 2 n^2 n2) n 3 n^3 n3)
指数时间的关系为:
O( 2 n 2^n 2n) n n n^n nn)
当n取得很大时,指数时间算法和多项式时间算法在所需时间上非常悬殊。因此,只要有人能将现有指数时间算法中的任何一个算法化简为多项式时间算法,那就取得了一个伟大的成就。
(4)大O表式法的性质
若符号a是不依赖于n的任意常数

  1. 如果函数 T(n) 是O(g(n))的,g(n)是O(h(n)),那么T(n)是O(h(n))的
  2. 如果函数T(n)是O(h(n))的,g(n)是O(h(n)),那么T(n) +g(n) 是O(h(n))的
  3. 函数 a n k an^k ank是O( n k n^k nk)的
  4. 若T(n) = cg(n),则T(n) 是O(g(n))的
  5. 对于任何正数a和b,且b ≠ \neq ̸= 1,函数 log ⁡ a n \log_a{n} logan 是O( l o g b n log_b{n} logbn)的。即,任何对数函数无论底数为何,都具有相同的增长率。
  6. 对任何正数 a ≠ 1 a\neq1 a̸=1,都有 l o g a n log_a{n} logan是O( l o g n logn logn)的,其中 l o g n logn logn = l o g 2 n log_2{n} log2n

2、 Ω \Omega Ω表式法

  • 定义:如果存在正数c和N,使得对所有的 n ≥ N n\geq N nN,都有 T ( n ) ≥ c g ( n ) T(n)\geq cg(n) T(n)cg(n),则称T(n)在集合 Ω ( g ( n ) ) \Omega (g(n)) Ω(g(n))中,或简称 T ( n ) T(n) T(n) Ω ( g ( n ) ) \Omega (g(n)) Ω(g(n))的。
  • 说明了 c g ( n ) cg(n) cg(n)是函数 T ( n ) T(n) T(n)取值的下限(lower bound),或说函数f的增长最终至少是趋同于函数g的增长。
  • 正如大O表示法,表示法也是在函数增值率的所有下限中那个最“紧”(即最大)的下限。

3、 Θ \Theta Θ表式法

  • 当上、下限相同时则可用 Θ \Theta Θ表示法。定义如下:如果一个函数既在集合 O ( g ( n ) ) O (g(n)) O(g(n))中又在集合 Ω ( g ( n ) ) \Omega(g(n)) Ω(g(n))中,则称其为 Θ ( g ( n ) ) \Theta (g(n)) Θ(g(n))。 也即,存在正常数c1, c2,以及正整数 n 0 n_0 n0,使得对于任意的正整数 n > n 0 n > n_0 n>n0 ,有下列两不等式同时成立 c 1 g ( n ) ≤ T ( n ) ≤ c 2 g ( n ) c1 g(n) \leq T(n) \leq c2g(n) c1g(n)T(n)c2g(n)

三、最坏、最好和平均情况

1、最坏、最好和平均情况

  • 在进行算法增长率估计时,有些算法,即使问
    题规模相同,若输入数据不同,其时间复杂度
    也不同
  • 由于算法实际执行的操作往往依赖于分支条件的走向,而输入数据的取值又影响这些分支走向,因此很多算法都无法得出独立于输入数据的渐进估计
  • 针对这一情况,提出了最坏情况估计、平均情况估计、最好情况估计等三种方法

例一:
求一个数组的所有有序子数组中最长的一个:

for (i = 0, length=1; i<n-1; i++) { 
	for (j1 = j2 = k = i; k < n-1 && a[k] < a[k+1]; k++,j2++);
		if (length < j2 - j1 +1)
			length = j2 - j1 + 2;
}

譬如,在数组[1, 8, 1, 2, 5, 0, 11, 9]中,这个最长的有序子数组为[1,2,5],长度为3。时间代价和数组 a 中元素的实际取值状态很相关。
示例:最好
数组a的所有元素是以降序方式输入

  • 外层循环执行n-1次,
  • 每次内层循环只执行1次
  • 整个的时间开销为O(n)

min{ complexity(size(y)) | y ϵ \epsilon ϵInput}
示例:最坏

  • 数组 a 的所有元素是以升序方式输入;
  • 外层循环执行n-1次;
  • 对每个 i,内层循环需要执行 (n - 1 - i) 次  整个的时间开销为 O ( n 2 ) O(n^2) O(n2)

max {complexity(size(y)) | y ϵ \epsilon ϵInput }
示例:平均

  • 随机情况下,数组的元素是无序的,既非升序也
    非降序
  • 计算平均情况的复杂度应该考虑算法的所有输入
    情况,确定针对每种输入情况算法所需的操作数
    • 简单情况:每种输入出现的概率相同
    • 复杂情况:每种输入的出现概率并非相同
      C a v g ∑ i p ( i n p u t i ) s t e p s ( i n p u t i ) C_{avg} \sum_i p(input_i)steps(input_i) Cavgip(inputi)steps(inputi)
      ∑ i P ( i n p u t i ) = 1 \sum_i P(input_i) = 1 iP(inputi)=1

2、最好、最坏、平均情况

  • 对于时间开销,一般不注意算法的“最好估计” 。特别是处理应急事件,计算机系统必须在规定的响应时间内做完紧急事件处理。这时,最坏估计是唯一的选择
  • 对于多数算法而言,最坏情况和平均情况估计两者,它们的时间开销的公式虽然不同,但是往往只是常数因子大小的区别,或者常数项的大小区别。因此不会影响渐进分析的增长率函数估计

四、时间和空间资源开销

1、空间开销的概念

  • 对于空间开销,也可以实行类似的渐进分析方法。
  • 很多算法使用的数据结构是静态的存储结构,即存储空间在算法执行过程中并不发生变化。对于静态数据结构,空间开销的估计往往是容易的,它们或者与所涉及的问题规模成正比(空间开销为线性增长),或者不随问题的规模而增大,空间开销为常数。使用动态数据结构算法的存储空间是变化的,在算法运行过程中有时会有数量级的增大或缩小。对于这种情况,空间开销的分析和估计是十分必要的。

2、时空资源的折中原理

  • 对于同一个问题求解,一般会存在多种算法。而这些算法在时空开销上的优劣往往表现出‘时空折中’的性质。
  • ‘时空折中’,是指为了改善一个算法的时间开销,往往可以通过增大空间开销为代价,而设计出一个新算法来。
  • 有时也可以为了缩小算法的空间开销,而牺牲计算机的运行时间,通过增大时间开销来换取存储空间的节省。

1.6 数据结构的选择与评价

1、仔细分析所要解决的问题,特别是求解问题所涉及的数据类型和数据间逻辑关系。
2、数据结构的初步设计往往在算法设计之先。
3、注意数据结构的可扩展性。包括考虑当输入数据的规模发生改变时,数据结构是否能够适应。同时,数据结构应该适应求解问题的演变和扩展。
4、数据结构的设计和选择也要比较算法的时空开销的优劣。

你可能感兴趣的:(算法与数据结构)