常见的数据结构
1. 线性表
概述:
常见的就是数组,我们只要指定数组的下标(index)就能访问它,大多数情况下,数组的下标都是整数,有时也可以使用非数字的下标,如在C语言中可以指定字符作为下标(在底层依然是数字)。
数组可以实现很多其他结构,如字符串,由0和1组成的字符串称为二进制串,或位串,字符串的操作不同于数组的操作,字符串的操作常见为求字符串的长度、按字典顺序进行排序、以及连接两个字符串构成一个新字符串、等等。
2. 链表
概述:
链表是0个或多个称为节点的元素构成的序列,每个节点包括两类信息,一类是数据,另一类是一个或者多个指向其他节点的指针,每一个节点都有指向的指针。
为了增强链表结构的灵活性,我们可以采取多种方式,例如为了方便起见,链表通常从一个成为表头的特殊节点开始,这个节点常常包括一些关于链表的信息,例如链表的当前长度,它还能包含其他信息,例如除了包含一个指向头元素的指针外,还可以包含一个指向尾元素的指针。
分类:
- 单链表:指节点中只有一个指针指向节点的后继节点
- 双链表:指节点中有两个指针,分别指向该节点的后继和前驱
- 循环链表:头结点的前驱指针指向的是尾结点,尾结点的后继指针指向的是头结点,达到一种循环的结构。
3. 栈
概述:
栈是一种遵循先进后出的特殊列表,我们对栈的操作只能在栈尾进行。栈更类似于一叠盘子,栈的应用广泛,尤其对于实现递归算法来说不可或缺。
4. 队列
概述:
队列是一种遵循先进先出的特殊列表,我们对队列的操作可以在队头和队尾进行,队头只允许拿出元素,队尾只允许进去元素,分别称为出队和入队,队列有很多重要的应用,其中包括一些图的算法。
应用:
要求从一个动态改变的候选结合中选择一个优先级高的元素,我们可以使用优先队列,优先队列是数据项的一个集合,这些数据项都来自于一些全序域。对优先队列的主要操作包括,查找最大元素,删除最大元素,和插入新的元素,当然,实现优先队列时,必须使后两种操作产生一个新的队列,我们可以基于数组或有序数组来实现优先队列,但是这两种都不是最好的解决方案,优先队列最好的实现方式还是堆(heap)
5. 图
概述:
严格来说图的定义是:一个图G=<V,E>由两个集合来定义:一个有限集合V,它的元素成为顶点(vertex);另一个有限集合E,它的元素是一对顶点,成为边(edge)。如果每对顶点之间都没有顺序,也就是说,顶点对(u,v)等同于顶点对(u,v)我们说顶点u和v相互邻接(adjacent),它们通过无向边(u,v)相连接。我们把顶点u和v成为边(u,v)的端点,称u和v和该边相关联(incident);当然,边(u,v)也和它的端点u和v相关联。如果图G中的所有边都是无向的,我们称之为无向图(undirected graph)
如果顶点对(u,v)不等同于顶点对(v,u),我们说边(u,v)的方向是从顶点u到顶点v,其中u成为尾,v成为头。也可以说边(u,v)离开u进入v,如果图的每一条边都是有向的。那么这个有向的图成为有向图
任意两个顶点之间都有边相连的图成为完全图。如果完全图具有V个顶点。如果图中所缺少的边相对较少,我们称它为稠密图,如果图中的边相对顶点来说数量较少,我们称它为稀疏图。我们处理的是稀疏图还是稠密图,可能会影响图的表示方法,从而影响我们所设计或使用的算法的运行时间。
图的表示方法:
- 邻接矩阵
- 邻接链表
邻接矩阵:
邻接矩阵是一个n x n的布尔矩阵,图中的每个顶点都由一行和一列来表示。如果从第i个顶点到第j个顶点之间有连接边,则矩阵中第i行第j列的元素等于1,如果没有这条边,则等于0。例如:
邻接链表:
邻接链表是邻接链表的一个集合,其中每个顶点用一个邻接链表来表示,该链表表示了和这个顶点邻接的所有顶点,(既所有和该顶点有边相连的顶点)。通常,这样一个表由一个表头开始,表头指出该链表表示的是哪一个顶点。例如:
加权图:
- 我们把邻接矩阵或邻接链表中包含有各个边所对的权值,就得到了我们的加权图,这些值称为边的权重,或者成本,这样的矩阵称为权重矩阵,或者成本矩阵。如图:
路径和环:
图的特性:连通性,无环性
路径:
这两个特性都是基于路径的概念。从顶点u到顶点v的路径可以这样定义:它是图G中始于u止于v的邻接顶点序列,如果一条路径上所有的顶点都是互不相同的。我们说这条路径是简单路径,路径的长度就是讲路径代表的顶点序列中的顶点数目减1,恰好和路径所包含的边的数目一致。
如果是优先图,我们常常对有向路径感兴趣。有向路径是顶点的一个序列,序列汇总的每一对连续顶点都被一条边连接起来,边的方向等于从第一个顶点指向下一个顶点的方向。
如果对于图中的每一对顶点u和v,都有一条从u到v的路径,我们说该图是连通的,如果把连通图的模型定义为代表边的绳子连接着代表顶点的小球,那么所有的东西都应该是连接在一起的。如果图是非连通的,这样一个模型会包含几个自我连通的部分,成为该图的连通分量,严格来说,连通分量是给定图的极大连通子图。
环:
回路(cycle)是这样一种路径,它的起点和终点都是同一顶点,长度大于0,而且绝不会将同一条边包含两次。例如:
6. 树
概述:
树:精准的说,自由树就是连通无回路图。无回路但不一定连通的图成为森林:它的每一个连通分量是一棵树。树有一些其他图没有的重要特性,树的边数总是比它的顶点数少1:|E| = |V|-1
要使图成为树,这个特征是必要的,但不是充分的。但对于连通图来说,他就是充分的了,而且它可以作为检测连通图是否包含回路的简便方法。
有根树:
树的另一个非常重要的特征就是:树的任意两个顶点之间总是恰好存在一条从一个顶点到另一个顶点的简单路径。这个性质使得以下的做法成为可能:任选自由树中的一个顶点,将它作为所谓有根树的根。
有根树扮演着重要地位,这个角色远比自由树要重要,实际上,为了简单起见,它们常常简称为树。
一些概念:
对于树T中的任意顶点v,从根到该顶点的简单路径上的所有顶点都成为v的祖先。一般也将顶点本身作为它自己的祖先,顶点本身以外的所有祖先顶点的集合成为真祖先集合,如果(u,v)是从根到顶点v的简单路径上的最后一条边,则u成为v的父母,v成为u的子女。具有相同父母的顶点称为兄弟。没有子女的顶点称为叶子节点,至少有一个子女的顶点称为父节点,所有以顶点v为祖先的顶点称为v的子孙,而v的真子孙则不包括顶点v本身。顶点v的所有的子孙以及连接子孙的边构成了T的以v为根的子树。
顶点v的深度是从根到v的简单路径的长度。树的高度是从根节点到叶节点的最长简单路径的长度。我们规定根的层数是0。
有序树:
有序树是一棵有根树,树的每一个顶点的所有子女都是有序的。也可以把一棵二叉树定义为有序树,但其中所有顶点的子女个数都不超过两个,并且每个子女不是父母的左孩子就是父母的右孩子。
如果一棵二叉树的根是另一棵二叉树的顶点的左(右)子女,则称其为该顶点的左(右)子树。由于左右子树也是二叉树,二叉树其实可以用递归定义,这使得很多涉及到二叉树的问题可以使用递归算法解决。
如果分配给每个父母顶点的数字都比它的左子树的数字大,比右子树的数字小。这种树成为二叉查找树,二叉树和二叉查找树在计算机中有很广泛的应用,尤其是,二叉查找树推广为一种更一般的查找树,成为多路查找树。
表示方法:
在计算机中,任意一棵有序树可以这样表示:简单地在一个父节点中加入与子女相同数量的指针。但如果不同节点的子女数目相差很大,这种表示法将变得很不方便,如果像对待二叉树一样,每个节点只包含两个指针,就能避免这种不便。此时,左指针仍然指向节点的第一个子女,而右指针则指向该节点的下一个兄弟。因此这种方法被称为先子女后兄弟表示法。
显而易见,这种表示法以一种高效的方式将一棵有序树改造成了一棵二叉树,我们说后者是前者的关联二叉树。只要将指针顺指针转动45°。就能把树变成这种表现形式
7. 集合与字典
概述:
我们这样描述一个集合:它是互不相同项的无序组合,这些项被称为集合的元素。一个特定的集合应该这样定义:要个直接列出元素的确切列表,要么指出元素的特殊属性,也就是集合所有元素都满足,并且只有他们才满足的特性。
最重要的集合运算包括:
- 检查给定项是不是给定集合的成员
- 求两个集合的并集
- 求两个集合的交集
集合的表示方法:
- 只考虑一些成为通用集合,的大集合U的子集。如果集合U具有n个元素,那么U的任何子集S能够用一个长度为n的位串[称为位向量]来表示。当且仅当U的第i个元素包含在S中时,向量中第i的元素为1。