第 1 章 绪论
第 2 章 线性表
2.1 线性表的逻辑结构
线性表是由类型相同的数据元素组成的有限序列
2.2 线性表的顺序存储结构
2.3 线性表的链式存储结构
2.3.1 单链表
2.3.2 循环链表
循环链表与单链表不同的是在循环链表中表尾结点的 next指针指向头结点,如图(a)所示,循环链表为空的条件为 head->next==head,如图(b)所示。
2.3.3 双向链表
双向链表的结点中有两个指针,分别指向前驱和后继,back是指向前驱的指针,next 是指向后继的指针。
第 3 章 栈和队列
3.1 栈
3.1.1 栈的基本概念
栈是限定只在表头进行插入与删除操作的线性表,表头端称为栈顶,表尾端称为栈底。
3.1.2 顺序链
3.1.3 链式栈
3.2 队列
3.2.1 队列的基本概念
队列是一种先进先出的线性表,只允许在一端进行插入(入队)操作,在另一端进行删除(出队)操作。 允许入队操作的一端称为队尾,允许出队操作的一端称为队头。
3.2.2 链队列
3.2.3 循环队列——队列的顺序存储结构
第 4 章 串
4.1 串类型的定义
串由零个或多个字符构成的有限序列,通常记为:
其中,s 是串名,用双引号括起来的部分称为串值,每个 为字符。串值中字符个数(也就是 n)称为串长,长度为 0 的串称为空串,各个字符全是空格字符的串(n不为0)称为空格串。
4.2 字符串的实现
4.3 字符串模式匹配算法
第 5 章 数组和广义表
5.1 数组
5.2 矩阵
5.2.3 稀疏矩阵
如一个矩阵中有许多元素为 0,则称该矩阵为稀疏矩阵。在稀疏矩阵和稠密矩阵之间并没有一个精确的界限。假设 m 行 n 列的矩阵含 t 个非零元素,一般称 为稀疏因子。
5.3 广义表
5.3.1 基本概念
广义表通常简称为表,是由 n(n≥0)个元素组成的有限序列,记作:
广义表的例子:,这个广义表的表名为 G,长度为3,元素包括 (a,(b,c))、x、(y,z),其中的 x 是原子元素,(a,(b,c)) 和 (y,z) 是子表元素,它们本身又分别是一个广义表。
若广义表 GL 中某元素含有广义表 GL 自身,则称 GL 为递归表。
第 6 章 树与二叉树
6.1 树的基本概念
6.2 二叉树
6.2.1 二叉树的定义
二叉树或为空树,或是由一个根结点加上两棵分别称为左子树和右子树的、互不相交的二叉树组成。可以看出,二叉树的特点是每个结点至多只有两棵子树(即二叉树中不存在度大于 2 的结点并且,二叉树的子树有左右之分,次序不能任意颠倒。因此,二叉树是有序树。
6.2.2 二叉树的性质
6.2.3 二叉树的存储结构
6.3 二叉树遍历
6.3.1 遍历的定义
先序遍历(根左右)
(1)访问根结点(D);
(2)先序遍历左子树(L);
(3)先序遍历右子树(R)。
中序遍历(左根右)
(1)中序遍历左子树(L);
(2)访问根结点(D);
(3)中序遍历右子树(R)。
后序遍历(左右根)
1)后序遍历左子树(L);
(2)后序遍历右子树(R);
(3)访问根结点(D)。
都是先左再右,区别在于根的顺序。
层次遍历
按照二叉树的层次,从上到下、从左至右的次序访问各结点
6.3.2 遍历算法
6.4 线索二叉树
6.4.1 线索的概念
通常称表示前驱和后继的指针叫做“线索”,而这种使树中结点的空指针成员存放前驱或后继信息的过程叫做“线索化”,加上了线索的二叉树叫做线索二叉树。
6.4.2 线索二叉树的实现
6.5 树和森林
6.5.1 树的存储表示
6.5.2 树的显示
6.5.3 森林的存储表示
可以采用树的存储表示法来表示森林,只是树只有一个根结点,而森林可以有多个根结点。
6.5.4 树和森林的遍历
6.5.5 树和森林与二叉树的转换
6.6 哈夫曼树与哈夫曼编码
6.6.1 哈夫曼树的基本概念
树的路径长度:从树根到每一结点的路径长度之和。
树的带权路径长度:树中所有叶子结点的带权路径长度之和。
哈夫曼树:根据给定的 n 个值 ,可以构造出多棵具有 n 个叶子且叶子结点权值分别为这 n 个给定值的二叉树,其中带权路径长度 WPL 最小的二叉树叫做最优树,即哈夫曼树。
6.6.2 哈夫曼树构造算法
6.6.3 哈夫曼编码
6.6.4 哈夫曼树的实现
6.7 树的计数
第 7 章 图
7.1 图的定义和术语
7.2 图的存储表示
7.2.1 邻接矩阵
7.2.2 邻接表
邻接表就是将矩阵中的行用链表来存储,并且只存储非 0 元素。
各边在链表中的顺序是任意的,视边的输入次序而定,在画图时通常按邻接点的编号大小排序。
7.3 图的遍历
7.3.1 深度优先搜索
深度优先搜索在搜索过程中,每当访问某个顶点 v 后,DFS 将递归地访问它的所有未被访问到的相邻的顶点,实际结果是沿着图的某一分支进行搜索,直至末端为止,然后再进行回溯,沿另一分支进行搜索,依此类推,深度优先搜索的搜索过程将产生一棵深度优先搜索树,此树由图遍历过程中所有连接某一新(未被访问的)顶点边所组成,并不包括那些连接已访问顶点的边,DFS算法适合于所有类型的图。
7.3.2 广度优先搜索
广度优先搜索类似于树的层将遍历,如从顶点 v 出发进行搜索,在访问了 v 之后依次访问 v 的未被访问的邻接点,然后再从这些邻接点出发依次访问它们的邻接点,直至图中所有被访问的顶点的邻接点都已被访问完为止,如果这时图中还有未被访问的顶点,将选择一个未被访问的顶点作起始点继续进行搜索,直到图中所有顶点都被访问到为止。
实际上广度优先搜索是以顶点 v 为起始点,由近至远依次访问和 v 有路径相通且路径长度为1,2,…的顶点,如下图所示,从顶点 出发进行搜索,首先访问
,然后再访问
的未被访问过的邻接点
、
和
,然后依次访问
的未被 访问过的邻接点
,
的未被访问过的邻接点
,依此类推,直到所有顶点都被访问到为止,由此完成图的遍历,得到的顶点序列如下所示:
7.4 连通无向网的最小代价生成树
7.4.1 Prim算法
从边界选最小
7.4.2 Kruskal算法
从全局选最小
7.5 有向无环图及应用
7.5.1 拓扑排序
对一个有向无环图 G=(V, {E}) 进行拓扑排序,就是将 G 中所有顶点排成一个线性序列,使得图中任意一对顶点 u 和 v,若 ,则 u 在线性序列中出现在 v 之前,这样的线性序列称为拓扑有序的序列,简称拓扑序列。
AOV 图:用顶点表示活动,有向边表示活动间的优先关系,这样的有向图称为顶点表示活动的图,简称为AOV图
7.5.2 关键路径
AOE 网是一种边带有权值的有向无环图,用顶点来表示事件,边表示活动,边上的权值表示活动的持续时间。
在AOE网中,有些活动可以并行地进行,最短完成时间应是从源点到汇点的最长路径长度(指路径上所有权值之和),称这样的路径为关键路径。
7.6 最短路径
7.6.1 单源点最短路径问题
7.6.2 所有顶点之间的最短路径
第 8 章 查找
8.1 查找的基本概念
查找表是由同一类型的数据元素(或记录)所组成的集合。对查找表通常有如下4种操作:
(1)查询某个数据元素是否在查找表中;
(2)检索某个数据元素的各种属性;
(3)在查找表中插入一个数据元素;
(4)从查找表中删除某个元素。
前两种操作统称为“查找”的操作,如果只对查找表进行前两种“查找”的操作,也就是查找表的元素不发生变化,这样的查找表称为静态查找表;
如在查找过程中同时还要插入查找表中不存在的数据元素或从查找表中删除已存在的某个数据元素,也就是查找表的数据元素要发生变化,这样的查找表称为动态查找表。
8.2 静态表的查找
8.2.1 顺序查找
是从第 1 个记录开始逐个地对记录的关键字的值进行比较,如某个记录的关键字的值和给定值相等,则查找成功,返回此记录的序号;如果直到最后一个记录的关键字的值都和给定值不相等,则表示查找表中没有所查的记录,查找失败,返回 -1。
8.2.2 有序表的查找
二分法查找
8.3 动态查找表
8.3.1 二叉排序树
二叉排序树或者是一棵空树,或者是具有下列性质的二叉树。
(1)若它的左子树不空,则左子树上所有结点的关键字值均小于它的根结点的关键字值;
(2)若它的右子树不空,则右子树上所有结点的关键字值均大于它的根结点的关键字值;
(3)它的左、右子树也分别为二叉排序树。
左 > 根 > 右
8.3.2 二叉平衡树
二叉平衡树(二叉搜索树,AVL树)是具有以下特征的二叉排序树
(1)根的左子树和右子树的高度差的绝对值的最大值为1;
(2)根的左子树和右子树都是 AVL树。
8.3.3 B树和B+树
一棵 m 阶 B 树定义为有以下特性 m 树:
(1)根或者是一个叶子结点,或者至少有两个孩子。(不是只有两个孩子)
(2)除了根结点以外,每个内部结点有 到 m 个孩子。
(3)所有叶子结点在树结构的同一层,并且不含任何信息(可看成是外部结点或查找失败的结点),因此m阶B树结构总是树高平衡的。
m 阶 B+ 树有如下特征:
(1)每个结点的关键字个数与孩子个数相等,所有非最下层的内层结点的关键字是对应子树上的最大关键字,最下层内部结点包含了全部关键字。
(2)除了根结点以外,每个内部结点有 到 m 个孩子。
(3)所有叶子结点在树结构的同一层,并不含任何信息(可看成是外部结点或查找失败的结点),因此树结构总是树高平衡的。
8.4 散列表
8.4.1 散列表的概念
我们对关键字 key 应用一个叫做散列函数的函数 H(key) 来确定具有此关键字 key 的特定数据元素是否在表中,即计算 H(key) 的值。H(key) 给出数据元素在散列表中的位置。
设散列表 ht 的大小为 m,0≤H(key)<m,为了确定关键字值为 key 的数据元素是否在表中,只需要在散列表中查看数据元素 ht[H(key)]。这样一个数据元素的地址通过一个函数来计算,所以数据元素并不需要按照特定的次序存放。
散列函数 H() 将关键字 key 对应为一个整数,满足 0≤H(key)<m,两个关键字 key1 和 key2,如果 key1≠key2,H(key1)=H(key2),也就是不同关键字得相同的散列地址,这种现象称为冲突,这时 key1与 key2 称为同义词。 选择一个散列函数时,考虑的主要因素是:
(1)选择一个易于计算的散列函数。
(2)尽量减少冲突发生的次数。
8.4.2 构造散列函数的方法
平方取中法
先计算关键字的平方,然后用结果的中间几位来获得散列表元素的地址
除留余数法
用关键字 key 除以不大于散列表大小的数 p 的一个余数,此余数表示 key 在 ht 中的地址,也就是:H(key)= key%p,为减少冲突,在一般情况下,p 最好为素数或不包含小于20的素数因子的合数。
随机数法
取关键字的随机函数值为它的散列地址,也就是 H(key)= Random(key),其中 Random 为伪随机函数,其取值在 0 到 m-1 之间。
8.4.3 处理冲突的方法
开放定址法
设散列地址为 0 ~ m-1,冲突是指关键字 key 得到的地址为 h 的位置上已存放有数据元素,处理冲突的方法就是为此关键字寻找另一个空的散列地址,处理冲突的过程可能得到一个地址序列: 也就是在处理冲突时,得到另一个散列地址
,如果
还有冲突,则求下一个散列地址
,依此类推,直到
不发生冲突为止。
开放定址法的一般形式为:
其中 H(key) 为散列函数,m为表长, 为增量序列,
有两种常见的取法:
(1) 也就是
,这种取法称为线性探测法。
(2) = 随机数,这种取法称为随机探测法。
链地址法
散列表 ht 是一个指针数组,对于每个 h,0≤h<m,ht[h] 是指向链表的一个指针。对数据元素中的每个关键字 key,首先计算 h= H(key),然后将含此关键字的数据元素插入到 ht[h] 指向的链表中,所以对于不相同的关键字 key1 和 key2,如果 H(key1)=H(key2),带有关键字 key1 和 key2 的数据元素将被插入到相同的链表,使得冲突的处理更为快捷和高效。
8.4.4 散列表的实现
第 9 章 排序
9.1 概述
对于可以重复出现的关键字,则排序结果可能不唯一。假如对于任意 ,排序前数据元素
在
的前面,
如果排序后数据元素 也在
的前面,这样的排序方法称为稳定的;
反之如可能排序后数据元素 在
的后面,则称所用的排序方法是不稳定的。
内部排序:待排序的数据元素全部存入计算机的内存中,在排序过程中不需要访问外存。
外部排序:待排序的数据元素不能全部装入内存,在排序过程中需要不断访问外存。
9.2 插入排序
9.2.1 直接插入排序
将第 1 个数据元素看成是一个有序子序列,再依次从第 2 个数据元素起逐个插入到这个有序的子序列中。一般地,在第 i 步上,将 elem[i] 插入到由 elem[0]~elem[i-1] 构成的有序子序列中。
例如,已知待排序的一组数据元素序列 {18,8,56,9,68,8}
首先可假设有序子序列中只包含一个数据元素,也就是 {18}
将 8 插入在有序子序列中,由于 8<18,显然插入后的有序子序列为: {8,18}
然后再将 56 插入到上述有序子序列中,由于 18<56,可知插入后的新有序子序列如下: {8,18,56}
按这种过程继续下去。
9.2.2 Shell排序
先将整个待排序数据元素序列分割成若干子序列,分别对各子序列进行直接插入排序,等整个序列中的数据元素“基本有序”时,再对全体数据元素进行一次直接插入排序。
9.3 交换排序
9.3.1 起泡排序
9.3.2 快速排序
任选序列中的一个数据元素(通常选取第1个数据元素)作为枢轴,以它和所有剩余数据元素进行比较,将所有较它小的数据元素都排在它前面,将所有较它大的数据元素都排在它之后,经过一趟排序后,可按此数据元素所在位置为界,将序列划分为两个部分,再对这两个部分重复上述过程直至每一部分中只剩一个数据元素为止。
9.4 选择排序
选择排序的基本思想是每一趟在 n-i(i=1,2,…,n-1) 个数据元素 (elem[i], elem[i+1], …, elem[n-1]) 中选择最小数据元素作为有序序列中第 i 个数据元素。
9.4.1 简单选择排序
简单选择排序的第 i 趟是从 (elem[i], elem[i+1], …, elem[n-1]) 选择第 i 小的元素,并将此元素放到 elem[i] 处,也就是说简单选择排序是从未排序的序列中选择最小元素,接着是次小的,依此类推,为寻找下一个最小元素,需检索数组整个未排序部分,但只一次交换即将待排序元素放到正确位置上。
9.4.2 堆排序
对于有 n 个元素的序列 (elem[0], elem[1], …, elem[n-1]),当且仅当满足如下条件时,称为堆:
上面第 1 组关系定义的堆称为小顶堆,第 2 组关系定义的堆称为大顶堆
如将序列对应的数组看成是完全二叉树,则堆的定义表明完全二叉树所有非终端结点的值均不大于(或不小于)其左、右孩子的值
对于大顶堆,堆顶元素最大,在输出堆顶元素后,如果能使剩下的 n-1个元素重新构建成一个堆,则可得到次大的元素,如此继续可得到一个有序序列,这种排序方法称为堆排序。
实现堆排序需要实现如下的算法:
(1)将一个无序序列构建成一个堆。
(2)在输出堆顶元素后,调整剩余元素成为一个新的堆。
设输出堆顶元素后,以堆中最后一个元素代替,这时根结点的左、右子树是大顶堆,需自上而下进行调整,首先以堆顶元素的最大的左、右孩子之值进行比较,由于最大孩子为左孩子,左孩子之值90大于双亲的值36,将 36 和 90进 行交换,由36代替 90后破坏了左子树的堆特性,需进行同上面相似的调整,直至叶结点或遇到调整后仍是堆时为止。
9.5 归并排序
归并是指将两个有序子序列合并为一个新的有序子序列
设在初始序列中有n个元素,归并排序的基本思想是,将序列看成 n 个有序的子序列,每个序列的长度为1,然后两两归并,得到 个长度为 2 或 1 的有序子序列,然后两两归并……这样重复下去,直到得到一长度为 n 的有序子序列,这种排序方法称为2-路归并排序
如果每次将3个有序子序列合并为一个新的有序序列,则称为3-路归并排序
对于内部排序来讲,2-路归并排序就能完全满足实现需要,只有外部排序才需要多路归并
归并排序的时间代价并不依赖于待排序数组的初始情况,也就是归并排序的最好、平均和最坏情形的时间复杂度都为 O(nlogn),比冒泡排序法好。
9.6 基数排序
9.6.1 多关键字排序
假设有 n 个元素序列:
元素 含有 d 个关键字
其中 称为最主位关键字,
称为最次位关键字
如果对任意两个元素 和
都满足:
则称序列按关键字 有序。
多关键字序列的排序最常见的方法是最低位优先法,先对最低位 进行排序,再对高一位关键字
进行排序,依此类推,直到对
进行排序为止,不是先比较最高位。
9.6.2 基数排序
基数排序的本质是借助于“分配”和“收集”算法对单关键字进行排序,基数排序是将关键字 在逻辑上看成是 d 个关键字
,如
有 radix 种值,称 radix 为基数,例如关键字是整数,并且关键字取值范围是 0≤j≤99,则可认为关键字 K 由 2 个关键字
组成,其中
是十位数,
是个位数,并且每位关键字可取 10 个值,即基数 radix=10,在算法实现时可将数据按关键字“分配”到线性链表中,然后再对所得的线性链表进行“收集”。
9.7 各种内部怕排序方法讨论
9.8 外部排序
9.8.1 外部排序基础
9.8.2 外部排序的方法
第 10 章 文件
第 11 章 算法设计与分析
11.1 算法设计
11.1.1 递归算法
直接或间接地调用自身的算法称为递归算法,直接或间接地调用自身的函数称为递归函数。
11.1.2 分治算法
分治算法与软件设计的模块化方法类似。为了解决一个大的问题,将一个规模为 n 的问题分解为规模较小的子问题,这些子问题一般和原问题相似。分别解这些子问题,最后将各个子问题的解合并得到原问题的解。子问题通常与原问题相似,可以递归地使用分治策略来解决。
11.1.3 回溯算法
回溯法可以系统地搜索一个问题的所有解。包含问题的所有解的解空间一般组织成树,也可以组织成图结构,按照先根遍历策略(解空间为树)或深度优先策略(解空间为图),从根结点出发搜索解空间树或从某结点出发搜索解空间图。算法搜索至解空间树(或图)的任一结点时,总是先判断该结点是否肯定不包含问题的解。如果肯定不包含,则跳过对该结点的系统搜索,逐层向其祖先结点回溯,否则,继续按先根遍历策略或深度优先策略进行搜索。回溯法用来求问题的所有解时,要回溯到根(或起始结点),且根结点的所有子树(或从起始结点出发的所有路径)都已被搜索过才结束。
11.2 算法分析
11.2.1 递归分析
11.2.2 利用生成函数进行分析
附录
(1)有一个 n 个元素的数组 x,判断是否存在两个元素和为100
常规方法:用两个 for 循环逐个判断任意两个元素的和,时间复杂度为
更快方法:先用归并排序对原数组进行排序,复杂度为 ,从第一个元素和最后一个元素开始,即最小元素和最大元素,若它们的和大于一百,则后面的元素往前移一位,若它们的和小于一百,则前面的元素往后移一位,最坏的情况是左右两个元素相遇,复杂度为
,所以总的时间复杂度为