【黑马程序员-学习笔记】数据结构-树与图

2021年6月20日 1点热度 0条评论 来源: suzizhan

一、树

树的基本操作通常有以下几种:
(1)Initiate(t)初始化一棵空树t。
(2)Root(x)求结点x 所在树的根结点。
(3)Parent(t,x)求树t 中结点x 的双亲结点。
(4)Child(t,x,i)求树t 中结点x 的第i 个孩子结点。
(5)RightSibling(t,x)求树t 中结点x 的第一个右边兄弟结点。
(6)Insert(t,x,i,s)把以s 为根结点的树插入到树t 中作为结点x 的第i 棵子树。
(7)Delete(t,x,i)在树t 中删除结点x 的第i 棵子树。
(8)Tranverse(t)是树的遍历操作,即按某种方式访问树t 中的每个结点,且使每个结点只被访问一次。

存储结构

在计算机中,树的存储有多种方式,既可以采用顺序存储结构,也可以采用链式存储结构,但无论采用何种存储方式,都要求存储结构不但能存储各结点本身的数据信息,还要能唯一地反映树中各结点之间的逻辑关系。下面介绍几种基本的树的存储方式。
1.双亲表示法
由树的定义可以知道,树中的每个结点都有唯一的一个双亲结点,根据这一特性,可用一组连续的存储空间(一维数组)存储树中的各个结点,数组中的一个元素表示树中的一个结点,数组元素为结构体类型,其中包括结点本身的信息以及结点的双亲结点在数组中的序号,树的这种存储方法称为双亲表示法。其存储表示可描述为:
#define MAXNODE <树中结点的最大个数>
typedef struct {
elemtype data;
int parent;
}NodeType;
NodeType t[MAXNODE];
图7.1(a)所示的树的双亲表示如图7.3 所示。图中用parent 域的值为-1 表示该结点无双亲结点,即该结点是一个根结点。
树的双亲表示法对于实现Parent(t,x)操作和Root(x)操作很方便,但若求某结点的孩子结点,即实现Child(t,x,i)操作时,则需要查询整个数组。此外,这种存储方式不能反映各兄弟结点之间的关系,所以实现RightSibling(t,x)操作也比较困难。在实际中,如果需要实现这些操作,可在结点结构中增设存放第一个孩子的域和存放第一个右兄弟的域,就能较方便地实现上述操作了。



2.孩子表示法
(1)多重链表法
由于树中每个结点都有零个或多个孩子结点,因此,可以令每个结点包括一个结点信息域和多个指针域,每个指针域指向该结点的一个孩子结点,通过各个指针域值反映出树中各结点之间的逻辑关系。在这种表示法中,树中每个结点有多个指针域,形成了多条链表,所以这种方法又常称为多重链表法。在一棵树中,各结点的度数各异,因此结点的指针域个数的设置有两种方法:
① 每个结点指针域的个数等于该结点的度数;
② 每个结点指针域的个数等于树的度数。

对于方法①,它虽然在一定程度上节约了存储空间,但由于树中各结点是不同构的,各种操作不容易实现,所以这种方法很少采用;方法②中各结点是同构的,各种操作相对容易实现,但为此付出的代价是存储空间的浪费。图7.4 是图7.1(a)所示的树采用这种方法的存储结构示意图。显然,方法②适用于各结点的度数相差不大的情况。树中结点的存储表示可描述为:
#define MAXSON <树的度数>
typedef struct TreeNode {
elemtype data;
struct TreeNode *son[MAXSON];
}NodeType;
对于任意一棵树t,可以定义:NodeType *t;使变量t 为指向树的根结点的指针。


图7.4 图7.1(a)所示树的孩子表示法示意
(2)孩子链表表示法
孩子链表法是将树按如图7.5 所示的形式存储。其主体是一个与结点个数一样大小的一维数组,数组的每一个元素有两个域组成,一个域用来存放结点信息,另一个用来存放指针,该指针指向由该结点孩子组成的单链表的首位置。单链表的结构也由两个域组成,一个存放孩子结点在一维数组中的序号,另一个是指针域,指向下一个孩子。



在孩子表示法中查找双亲比较困难,查找孩子却十分方便,故适用于对孩子操作多的应用。
这种存储表示可描述为:
#define MAXNODE <树中结点的最大个数>
typedef struct ChildNode{
int childcode;
struct ChildNode *nextchild;
}
typedef struct {
elemtype data;
struct ChildNode *firstchild;
}NodeType;
NodeType t[MAXNODE];

3.双亲孩子表示法

双亲表示法是将双亲表示法和孩子表示法相结合的结果。其仍将各结点的孩子结点分别组成单链表,同时用一维数组顺序存储树中的各结点,数组元素除了包括结点本身的信息和该结点的孩子结点链表的头指针之外,还增设一个域,存储该结点双亲结点在数组中的序号。图7.6 所示图7.1(a)的树采用这种方法的存储示意图。


4.孩子兄弟表示法
这是一种常用的存储结构。其方法是这样的:在树中,每个结点除其信息域外,再增加两个分别指向该结点的第一个孩子结点和下一个兄弟结点的指针。在这种存储结构下,树中结点的存储表示可描述为:
typedef struct TreeNode {
elemtype data;
struct TreeNode *son;
struct TreeNode *next;
}NodeType;
图7.7 给出了图7.7(a)所示的树采用孩子兄弟表示法时的存储示意图。

二、图

1.图的定义
图(Graph)是由非空的顶点集合和一个描述顶点之间关系――边(或者弧)的集合组成,其形式化定义为:
G=(V,E)
V={vi| vi∈dataobject}
E={( vi,vj)| vi, vj ∈V ∧P(vi, vj)}
其中,G 表示一个图,V 是图G 中顶点的集合,E 是图G 中边的集合,集合E 中P(vi,vj)表示顶点vi 和顶点vj 之间有一条直接连线,即偶对(vi,vj)表示一条边。图8.1 给出了一个图的示例,在该图中:
集合V={v1,v2,v3,v4,v5};
集合E={(v1,v2),(v1,v4),(v2,v3),(v3,v4),(v3,v5),(v2,v5)}。


2.图的相关术语
(1)无向图。在一个图中,如果任意两个顶点构成的偶对(vi, vj)∈E 是无序的,即顶点之间的连线是没有方向的,则称该图为无向图。如图8.1 所示是一个无向图G1。
(2)有向图。在一个图中,如果任意两个顶点构成的偶对(vi, vj)∈E 是有序的,即顶点之间的连线是有方向的,则称该图为有向图。如图8.2 所示是一个有向图G2:
G2=(V2,E2)
V2={v1,v2,v3,v4}
E2={<v1,v2>,<v1,v3>,<v3,v4>,<v4,v1>}
(3)顶点、边、弧、弧头、弧尾。图中,数据元素vi 称为顶点(vertex );P(vi, vj)表示在顶点vi 和顶点vj 之间有一条直接连线。如果是在无向图中,则称这条连线为边;如果是在有向图中,一般称这条连线为弧。边用顶点的无序偶对(vi, vj)来表示,称顶点vi和顶点vj 互为邻接点,边(vi, vj)依附于顶点vi 与顶点vj;弧用顶点的有序偶对<vi, vj>来表示,有序偶对的第一个结点vi 被称为始点(或弧尾),在图中就是不带箭头的一端;有序偶对的第二个结点vj 被称为终点(或弧头),在图中就是带箭头的一端。
(4)无向完全图。在一个无向图中,如果任意两顶点都有一条直接边相连接,则称该图为无向完全图。可以证明,在一个含有n 个顶点的无向完全图中,有n(n-1)/2 条边。
(5)有向完全图。在一个有向图中,如果任意两顶点之间都有方向互为相反的两条弧相连接,则称该图为有向完全图。在一个含有n 个顶点的有向完全图中,有n(n-1)条边。
(6)稠密图、稀疏图。若一个图接近完全图,称为稠密图;称边数很少的图为稀疏图。
(7)顶点的度、入度、出度。顶点的度(degree)是指依附于某顶点v 的边数,通常记为TD (v)。在有向图中,要区别顶点的入度与出度的概念。顶点v 的入度是指以顶点为终点的弧的数目。记为ID (v);顶点v 出度是指以顶点v 为始点的弧的数目,记为OD (v)。
有TD (v)=ID (v)+OD (v)。
例如,在G1 中有:
TD(v1)=2 TD(v2)=3 TD(v3)=3 TD(v4)=2 TD(v5)=2
在G2 中有:
ID(v1)=1 OD(v1)=2 TD(v1)=3
ID(v2)=1 OD(v2)=0 TD(v2)=1
ID(v3)=1 OD(v3)=1 TD(v3)=2
ID(v4)=1 OD(v4)=1 TD(v4)=2
可以证明,对于具有n 个顶点、e 条边的图,顶点vi 的度TD (vi)与顶点的个数以及边的数目满足关系:


(8)边的权、网图。与边有关的数据信息称为权(weight)。在实际应用中,权值可以有某种含义。比如,在一个反映城市交通线路的图中,边上的权值可以表示该条线路的长度或者等级;对于一个电子线路图,边上的权值可以表示两个端点之间的电阻、电流或电压值;对于反映工程进度的图而言,边上的权值可以表示从前一个工程到后一个工程所需要的时间等等。边上带权的图称为网图或网络(network)。如图8.3 所示,就是一个无向网图。如果边是有方向的带权图,则就是一个有向网图。
(9)路径、路径长度。顶点vp 到顶点vq 之间的路径(path)是指顶点序列vp,vi1,vi2, …,vim,vq.。其中,(vp,vi1),(vi1,vi2),…,(vim,.vq)分别为图中的边。路径上边的数目称为路径长度。图8.1 所示的无向图G1 中,v1→v4→v3→v5 与v1→v2→v5 是从顶点v1 到顶点v5 的两条路径,路径长度分别为3 和2。
(10)回路、简单路径、简单回路。称vi 的路径为回路或者环(cycle)。序列中顶点不重复出现的路径称为简单路径。在图8.1 中,前面提到的v1 到v5 的两条路径都为简单路径。除第一个顶点与最后一个顶点之外,其他顶点不重复出现的回路称为简单回路,或者简单环。如图8.2 中的v1→v3→v4→v1。
(11)子图。对于图G=(V,E),G’=(V’,E’),若存在V’是V 的子集,E’是E的子集,则称图G’是G 的一个子图。图8.4 示出了G2 和G1 的两个子图G’和G’’。


(12)连通的、连通图、连通分量。在无向图中,如果从一个顶点vi 到另一个顶点vj(i≠j)有路径,则称顶点vi 和vj 是连通的。如果图中任意两顶点都是连通的,则称该图是连通图。无向图的极大连通子图称为连通分量。图8.5 (a)中有两个连通分量,如图8.5 (b)所示。
(13)强连通图、强连通分量。对于有向图来说,若图中任意一对顶点vi 和vj(i≠j)均有从一个顶点vi 到另一个顶点vj 有路径,也有从vj 到vi 的路径,则称该有向图是强连通图。有向图的极大强连通子图称为强连通分量。图8.2 中有两个强连通分量,分别是{v1,v2,v3}和{v4},如图8.6 所示。
(14)生成树。所谓连通图G 的生成树,是G 的包含其全部n 个顶点的一个极小连通子图。它必定包含且仅包含G 的n-1 条边。图8.4(b)G”示出了图8.1(a)中G1 的一棵生成树。在生成树中添加任意一条属于原图中的边必定会产生回路,因为新添加的边使其所依附的两个顶点之间有了第二条路径。若生成树中减少任意一条边,则必然成为非连通的。
(15)生成森林。在非连通图中,由每个连通分量都可得到一个极小连通子图,即一棵生成树。这些连通分量的生成树就组成了一个非连通图的生成森林。

基本操作

(1) CreatGraph(G)输入图G 的顶点和边,建立图G 的存储。
(2)DestroyGraph(G)释放图G 占用的存储空间。
(3)GetVex(G,v)在图G 中找到顶点v,并返回顶点v 的相关信息。
(4)PutVex(G,v,value)在图G 中找到顶点v,并将value 值赋给顶点v。
(5)InsertVex(G,v)在图G 中增添新顶点v。
(6)DeleteVex(G,v)在图G 中,删除顶点v 以及所有和顶点v 相关联的边或弧。
(7)InsertArc(G,v,w)在图G 中增添一条从顶点v 到顶点w 的边或弧。
(8)DeleteArc(G,v,w)在图G 中删除一条从顶点v 到顶点w 的边或弧。
(9)DFSTraverse(G,v)在图G 中,从顶点v 出发深度优先遍历图G。
(10)BFSTtaverse(G,v)在图G 中,从顶点v 出发广度优先遍历图G。
在一个图中,顶点是没有先后次序的,但当采用某一种确定的存储方式存储后,存储结构中顶点的存储次序构成了顶点之间的相对次序,这里用顶点在图中的位置表示该顶点的存储顺序;同样的道理,对一个顶点的所有邻接点,采用该顶点的第i 个邻接点表示与该顶点相邻接的某个顶点的存储顺序,在这种意义下,图的基本操作还有:
(11)LocateVex(G,u)在图G 中找到顶点u,返回该顶点在图中位置。
(12)FirstAdjVex(G,v)在图G 中,返回v 的第一个邻接点。若顶点在G 中没有邻接顶点,则返回“空”。
(13)NextAdjVex(G,v,w)在图G 中,返回v 的(相对于w 的)下一个邻接顶点。若w 是v 的最后一个邻接点,则返回“空”。

    原文作者:suzizhan
    原文地址: https://blog.csdn.net/suzizhan/article/details/47416151
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系管理员进行删除。