数据结构

数据结构

线性结构

线性结构是一种基本的数据结构,是对客观世界中具有单一的前驱和后继的数据关系的描述。

线性表,简单而常见的一种线性结构,有顺序和链式两种存储结构。

栈和队列,是同线性表有相同逻辑结构的两种数据结构。但是,他们的存取受到限制,栈要遵循先进后出的存取方式,队列则遵循先进先出的存取方式。

字符串,由字符组成的一种特殊的线性表。

字符串的模式匹配有两种,一种朴素的模式匹配算法,也称为布鲁特-福斯算法,就是简单的从主串的第一个字符开始同模式串的第一个字符比较,如果相等,继续比较,否则,就从主串第二个字符开始同模式串的第一个字符相比较,直到模式串同主串中的字符都相匹配或找不到完全匹配模式串的子串为止。

另一种改进的模式匹配算法,即 KMP 算法,这种算法先对模式串本身进行处理,为每一个字符得到一个索引值,该值表示当前字符同主串的字符不匹配时,进而将主串中的字符同该索引值在模式串中所表示的字符相比较。

使用下面的算法计算模式串的索引:

void get_next(char *p,int next[]) {

	int i=0,j=-1;
	int slen = strlen(p);
	next[0] = -1;
	
	while(i < slen) {
		if (j == -1 || p[i] == p[j]) {
			i++;
			j++;
			next[i] = j;
		}else {
			j = next[j];
		}
	}
}

这个索引值,实际是当前字符之前的模式子串中能够找到的两个相匹配的字符串的最大长度,但是注意这两个子串,一个必须以第一个字符开始,另一个必须以最后一个字符结束,长度相同,但不等于模式子串全长。当然,第一个索引值为 -1 。

如模式串为:abababbab ,主串为:abacbcabababbabc ,先处理模式串如下,

j012345678
模式串abababbab
next[j]-100123401

然后根据上面的处理结果进行匹配,使模式串向右滑动,而主串不用回退。

i0123456789101112131415
主串abacbcabababbabc
j012345678
模式串abababbab
j012345678
模式串abababbab
j012345678
模式串abababbab

模式串首个字符同主串字符不匹配,那么就向右移动匹配主串的下一个字符。

i0123456789101112131415
主串abacbcabababbabc
j012345678
模式串abababbab
i0123456789101112131415
主串abacbcabababbabc
j012345678
模式串abababbab

如此,直到在主串中找到模式串,或找不到模式串,结束匹配。

i0123456789101112131415
主串abacbcabababbabc
j012345678
模式串abababbab

数组、矩阵和广义表

数组是定长线性表在维数上的扩展,即线性表中的元素又是一个线性表。

计算机的内存结构在逻辑上是一维线性的,所以存储多维数组时必须按某种方式进行降维处理。

降维的过程就是将数组元素排成一个线性序列,这就需要先约定存储的次序。

对于二维数组而言,就需要确定是行优先存储还是列优先存储。

在一个矩阵中,如果值相同的元素或值为 0 的元素在其中的分布有一定的规律,那么就称此类矩阵为特殊矩阵,否则称其为稀疏矩阵。

对于特殊矩阵,可以根据规律将非零元素存储在一维数组中,而对于稀疏矩阵,则使用三元组来存储每一个非零元素。

广义表是线性表的推广,由 0 个或多个单元素或子表组成的有限序列。

其同线性表的区别是它的元素可以是不可分的单元素,也可以是有结构的表。

广义表的长度是指其中元素的个数,深度则是指其展开后所含括号的最大层数。

广义表的表头指的是其中的第一个元素,剩余的所有元素称为表尾。

树是 n(n >= 0)个结点的有限集合,当 n = 0 时,称为空树。

结点的度,是指一个结点的子树的个数,而度为 0 的结点称为终端结点。度不为 0 的结点称为分支结点或非终端结点,除根结点外,分支结点也称为内部结点。

结点的层次,从根结点开始算起,根结点算第一次,根的孩子结点为第二层,以此类推。而一棵树的最大层次称为该树的高度或深度,另外,如果树的各个子树从左到右是有次序的,那么这棵树为有序树,否则为无序树。

二叉树

二叉树不同于树,其的度最大为 2 ,并且结点的子树是明确的左子树或右子树,即使只有一个子树,也需要表面是左子树还是右子树。

满二叉树是指二叉树的每一层中的结点都是非空的,那么对于 k 层的树,其结点个数为 2 k − 1 2^k-1 2k1 个。可以知道,满二叉树不存在度数为 1 的结点。

完全二叉树是指二叉树的每个结点在每一层都是按从左到右依次排列的,即不存在有右子树而没有左子树的结点。

二叉树的遍历,按照先遍历左子树,后遍历右子树的约定,根据访问根结点位置的不同,可得到二叉树的先序、中序和后序 3 种遍历方法。

除了按深度优先来遍历二叉树外,还可以按广度优先来遍历,从根结点开始,从上倒下,从左到右依次遍历每一个结点,即为层次遍历。

线索二叉树

二叉树的遍历实际上是对一个非线性结构进行线性化的过程,但是在使用二叉链表存储结构进行存储时,保存的只是结点的左右孩子,并没有保存遍历后的线性关系。
不过,使用该结构进行存储时,存储 n 个结点会空出 n+1 个指针域,利用该指针域可以存储遍历后的线性关系。

需要注意的是,这种方式存储的线性关系是不完整的,要得到一个结点的直接前驱或后继,需要根据数据结构的标识进行判断,如果标识表明指针域表示的不是直接前驱或后继,那么就需要根据遍历方式来找寻该结点的前驱或后继。

如一个非根结点拥有左右孩子结点,且孩子结点的度为 0 ,那么该结点的指针域指向的是这两个孩子,按先序遍历方式查找,该结点的直接前驱是其父结点,直接后继是其左孩子。

如果按中序遍历方式查找,该结点的直接前驱是其左孩子,直接后继是其右孩子。

最优二叉树

最优二叉树也称为哈夫曼树,是一类带权路径长度最短的树。

两个结点间的路径分支数为两个结点间的路径长度。

树的路径长度是根结点到每个叶子结点的路径长度的和。

结点的带权路径长度是从该结点到根结点的路径长度与该结点权值的乘积。

树的带权路径长度为树中所有叶子结点的带权路径长度的和。

构造最优二叉树的哈夫曼算法:

  1. 用给定的 n 个权值,构成 n 棵只有根结点的二叉树集合。
  2. 从集合中选择两棵权值最小的树构成一棵新的二叉树,根结点权值为左右孩子结点的权值之和。
  3. 从集合中删除用到的两颗树,将新的树放入其中。
  4. 重复上面两个步骤,直到集合中只有一棵树为止。

哈夫曼编码,对于等长编码方式使得最后的码串过长的情况,使用哈夫曼编码方式实现长度不等的编码。这种编码方式必须满足任意一个字符的编码都不是另一个字符编码的前缀,所以也称为前缀码。

用给定的字符集作为根结点,字符的使用频率作为权值,构造一棵最优二叉树,然后在每个结点的左分支上标 0 ,右分支标 1 ,那么每个字符的编码就是从根结点到叶子结点的路径上的 0 、1 组成的串。

树和森林

树的存储方法:

  • 双亲表示法,用一组连续的单元存储树的结点,并且每个存储单元中保存该结点的双亲结点的位置。
  • 孩子表示法,存储的结点后同时保存一个单链表,链表中保存的是该结点的所有孩子结点的位置。
  • 孩子兄弟表示法,也称为二叉链表表示法,结点中包含指向第一个孩子和下一个兄弟结点的指针域。

树、森林和二叉树之间的相互转换,则是基于孩子兄弟表示法。

图是比树结构更复杂的一种数据结构,其中任意两个结点之间都可能存在直接的关系,所以图中的一个结点的前驱结点和后继结点的数目是不确定的。

在图中,数据元素用顶点表示,数据元素间的关系用边表示。

有向图,图中的所有边都是有方向的,这个边也叫弧。

无向图,图中的边是没有方向的。

完全图,图中任意两个顶点间都有边或弧相连接。

度、出度、入度,图中与顶点相连的边的数量,有向图的度是出度和入度的和。

路径,两个顶点相连的边或弧,都属于图的边的集合。如果两个顶点之间存在路径,那么这两个顶点则是连通的。

连通图和连通分量,图中的任意两个顶点都是连通的,那么这个图称为连通图。无向图 G 的极大连通子图称为该 G 的连通分量。

强连通图和强连通分量,有向图 G 中任意两个顶点相互是连通的,那么称图 G 是强连通图。有向图中的极大连通子图称为有向图的强连通分量。

网,边或弧带权值的图称为网。

有向树,只有一个顶点的入读为 0 ,其余顶点入度为 1 的有向图称为有向树。

使用邻接矩阵存储图,有 n 个顶点,则用 n 阶矩阵表示,其中的值为 1 表示存在边或弧,为 0 表示不存在。可知,无向图的邻接矩阵是对称的。

使用邻接链表表示法为图的每个顶点建立一个单链表,单链表保存的是所有依附于该顶点的边或弧的边的另一个顶点的位置。

图的遍历包括深度优先搜索(Depth First Search,DFS)和广度优先搜索(Breath First Search,BFS)。

深度优先搜索,从一个顶点开始,优先搜索该顶点的一个邻接点,而后在搜索邻接点的邻接点,直到没有邻接点可查询,然后回退到上一个顶点,查询其另外一个未访问的邻接点,如此重复,完成所有顶点的搜索。

广度优先搜索,从一个顶点开始,优先搜索该顶点的所有邻接点,完毕后,在搜索该顶点第一个邻接点的所有邻接点,如此重复,直到完成所有顶点的查询。

如果图是非连通的,那么,遍历时需要多次重新从一个顶点出发。

生成树及最小生成树

连通图的生成树是该图的极小连通子图,任意添加一条边,必然形成回路。另外,图的生成树是不唯一的。

连通网的生成树是带权值的,其中生成树的权值最小的树称为最小生成树。

最小生成树求解算法:

普里姆(Prim)算法,以一个只包含一个顶点的顶点集合为初态,不断寻找同该集合中相邻且权值最小的边的另一个顶点,扩充集合直到其包含所有顶点为止。

克鲁斯卡尔(Kruskal)算法,令最小生成树为只有 n 个顶点而无边的非连通图状态,即边的集合 T 为空,每个顶点为一个连通分量。然后在连通网的边集合中选择权值最小且不在集合 T 中的边,将该边加入集合 T 中,重复该步骤,直到所有顶点在一个连通分量上为止。

拓扑排序和关键路径

在有向图中,用顶点表示活动,用有向边表示活动间的优先关系,则称这样的有向图为以顶点表示活动的网(Activity On Vertex network,AOV 网)。

不存在回路的有向图称为有向无环图,或 DAG(Directed Acyclc Graph)图。

如果 AOV 网中存在环,那么表示这个工程是不可行的。可以通过构造有向图的顶点拓扑序列进行检查,如果所有顶点都在拓扑的有序序列中,那么表示 AOV 中不存在环。

拓扑排序,在 AOV 网中,删除一个入度为 0 的顶点,以及与其相关联的所有弧,记录该顶点。重复该步骤,直到记录下所有顶点为止。如果图中还存在入度不为 0 的顶点,那么表示该 AOV 网中存在环。否则,最后得到的记录是该 AOV 网表示的工程的一个可行方案。

用深度优先遍历有向无环图,可以得到该图的逆拓扑排序,同样,逆拓扑排序也不是唯一的。

若在带权有向图 G 中用顶点表示事件,用有向边表示活动,用边上的权值表示该活动持续的时间,那么这种带权有向图称为用边表示活动的网(Activity On Edge network,AOE 网)。

AOE 网中的一些活动是可以并行地进行的,所以完成整个工程最少需要的时间是从开始顶点(源点,入度为 0)到结束顶点(汇点,出度为 0)的最长路径的长度。

而这个最长路径则称为关键路径,该路径上的活动都是关键活动。可知,整个工程的进度受到关键活动的影响。

对 AOE 网进行拓扑排序,求出一个顶点的每个前驱顶点事件的最早发生时间,这样才可以得到该顶点的最早发生时间。

对 AOE 网进行逆拓扑排序,求出一个顶点的所有后继顶点事件的最晚发生时间,这样才能得到该顶点的最晚发生时间。

对于关键活动而言,其最早开始时间和最晚开始时间是相等的,这样才会影响整个工程的进度。

在计算顶点的最晚开始时间时,首先要确定的是汇点的最晚也就是最早开始时间,然后向源点方向递推得到其他顶点的最晚开始时间。

最短路径

单源点最短路径,是指给定带权有向图 G 和源点 v 0 v_0 v0 ,求从 v 0 v_0 v0 到 G 中其余各顶点的最短路径。

迪杰斯特拉(Dijkstra)算法,先确定 v 0 v_0 v0 到各个顶点的路径长度,如果没有则用 ∞ 表示,而后在这些路径中选择一条最短路径,然后在该路径基础上,更新到各个顶点的路径长度,然后再次选择最短的路径,如此重复,知道所有顶点都判断完毕为止。

弗洛伊德(Floyd)算法,在求取 v i v_i vi v j v_j vj 的最短路径时,依次添加 v 0 v_0 v0 v 1 v_1 v1 v n v_n vn 顶点,构成路径 ( v i v_i vi, v j v_j vj) ( v i v_i vi, v 0 v_0 v0, v i v_i vi)
( v i v_i vi,…, v k v_k vk,…, v j v_j vj)
( v i v_i vi,…, v n − 1 v_n-1 vn1,…, v j v_j vj),比较这些路径,取得最短路径即可。

查找

静态查找表的查找方法:顺序查找、折半查找、分块查找(索引顺序查找)

动态查找表的查找方法:

二叉排序树(二叉查找树),构造二叉排序树的过程就是对无序序列进行排序的过程,对得到的排序树进行中序遍历便可以得到一个有序序列。

如果使用一个有序序列构造二叉排序树,那么最后得到的树是一个单支树。

平衡二叉树(AVL 树),其左右子树的高度差的绝对值不超过 1 ,并且左右子树都是平衡二叉树。

在构造平衡二叉树插入结点或从平衡二叉树中删除结点的过程中,如果破环了二叉树的平衡,需要通过旋转进行调整。

哈希表,不同于基于比较关键字的查找,而是通过一个哈希函数将关键字映射到一个有限连续的地址集上,而地址集中的 “像” 作为记录在表中存储地址。

获取一个哈希表主要要考虑哈希函数的构造以及如何解决冲突,哈希函数要具有较大的压缩性,来节约空间,有较好的散列性,来减少冲突。

处理冲突的方法:

  • 开放定址法,对于不同的记录,如果具有相同的哈希值,那么就为哈希值添加增量,根据添加增量的不同,分为线性探测再散列、二次探测再散列和随机探测再散列。

  • 链地址法,将具有相同哈希值的记录存储在一个链表中。

  • 再哈希法,当发生冲突时,使用不同的哈希函数重新计算哈希值,直到没有冲突为止。

  • 公共溢出区,一旦发生冲突,就将记录填充到公共溢出区中。

排序

待排序列经过排序方法排列后,关键字符合递增或递减的要求,即为排序过程。

在这个过程中,如果两个元素关键字相同,而经过排序后其相对位置保持不变,那么这种排序方法是稳定的,否则为不稳定的。

在这个过程中,如果待排序记录全部在内存中进行,则为内部排序,如果要访问外存,则为外部排序。

直接插入排序,用当前记录同已经排好的记录相比较,找到插入的位置并插入,然后移动插入位置后的记录。

冒泡排序,从序列的第一位置开始,比较第一个记录与其相邻的记录,如果逆序就交换,然后继续比较相邻元素,直到将记录交换到最后一个位置,再从第一位置重新开始比较,直到所有记录都处于正确位置为止。

直接选择排序,从待排序列的第一个记录开始,同其余的记录进行比较,找到最小/大值,然后同第一记录交换,之后再从第二个记录重复该步骤,直到最后一个记录。

希尔排序,是对直接插入排序的改进,也称缩小增量排序。指定增量序列,根据第一个增量将待排序列分成多个组,在组内进行直接插入排序,而后根据第二个增量重复进行该排序,直到增量为 1 ,进行最后一次排序。

快速排序,设置一个枢轴记录,从待排序列的最后一个记录开始,将记录同枢轴记录比较,发现逆序后,将两者交换,然后从待排序列的第一个记录开始,同枢轴记录比较,发现逆序后,将两者交换,重复这两个步骤直到遍历完整个序列为止。然后对枢轴记录两边的两个序列做快速排序,如此重复,直到整个序列有序。

堆排序,堆是指在一个序列中,第 i 个记录不大/小于第 2*i 个和第 2*i+1 个记录,那么这个序列称为堆。堆排序,则是将待排序列看作一个完全二叉树,然后根据堆的定义,将待排序列构建为一个小根堆或大根堆,之后,取走堆顶记录作为输出,然后调整堆为一个新堆,再取走堆顶记录,如此反复,得到最终的有序序列。

归并排序,将待排序列中的记录两两归并为一个有序序列,而后再两两归并,如此重复,直到归并为一个完整序列。

基数排序,将待排记录的关键字看成一个 d 元组,d 的值为关键字中位数最大的值,设立 r 个队列,r 的值等于关键字的进制数,r 即为基数。

如待排序列:3、2、10、25、300、3457,则 d = 4 ,r = 10 ,基数排序过程,先将每个关键字元组用 0 补齐,然后按最低有效位将关键字分配到相应的队列中,再按从小到大的顺序收集为一个序列,再按次低有效位分配,再收集,如此反复到最高有效位,最终得到一个从小到大的序列。

待排序列3210253003457
补齐元组000300020010002503003457

分配过程:

分配次数
队列
1234
00010、03000300、0002、00030002、0003、0010、00250002、0003、0010、0025、0300
10010
200020025
3000303003457
43457
500253457
6
73457
8
9
收集结果0010、0300、0002、0003、0025、34570300、0002、0003、0010、0025、34570002、0003、0010、0025、0300、34570002、0003、0010、0025、0300、3457

所以最终的序列为:2、3、10、25、300、3457

小结

不稳定排序:简单排序、希尔排序、快速排序、堆排序

稳定排序:直接插入排序、冒泡排序、归并排序、基数排序

如果待排序列的记录数目较小,可以采用直接插入排序和简单选择排序,另外,前者的移动操作比后者多,如果记录本身信息量大时,选择后者较好。

如果待排序列已经基本有序,宜采用直接插入排序和冒泡排序。

如果记录很多,且关键字位数较小时,可采用链式基数排序。

如果记录很多,可以使用快速排序、堆排序、归并排序,堆排序可以避免快排序中关键字不均匀的情况,而只需要一个辅助内存空间,如果要求稳定排序,可以将归并排序同志街插入排序结合起来使用。

内排序大概分为以下几种:

分类排序
插入排序直接插入排序、Shell 排序
选择排序直接选择排序、堆排序
交换排序冒泡排序、快速排序
归并排序归并排序
分配排序桶式排序、基数排序
索引排序索引排序(地址排序)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值