图
图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为G(V, E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。
ps:
- 图中的数据元素称为顶点(Vertex)
- 在图中,不允许没有顶点。定义中强调了V有穷非空
- 图中,任意两个顶点之间都可能有关系,顶点之间的逻辑关系用边来表示,边集可以是空的
各种图的定义
-
(1) 无向边:若顶点vi 到vj 之间的边没有方向,则称这条边为无向边(Edge),用无序偶对(vi, vj) 来表示。
(2)无向图:若图中 任意 两个顶点之间的边都是无向边,则称改图为无向图(undirected graphs)。
无向图 G1 = (V1,{E1}),顶点集合V1 ={A,B,C,D},边集合E1 ={(A,B), (B,C), (C,D), (D,A)}
(3)无向完全图:在无向图中,如果任意两个顶点之间都存在边,则称该图为无向完全图。n个顶点的无向完全图有n*(n - 1)/2条边。 -
(1) 有向边:若从顶点vi 到vj 的边有方向,则称该条边为有向边,也称为弧(Arc),用有序偶对(vi,vj) 来表示。vi 称为弧尾,vj 称为弧头,由A指向B的有向边就是弧,A是弧尾,B是弧头,<A,B>表示弧,注意不能写成<B,A>。
(2)有向图:如果图中 任意 两个顶点之间的边都是有向边,则称该图为有向图。
有向图 G2 = (V2,{E2}),顶点集合V2 ={A,B,C,D},边集合E2 ={<A,B>, <B,C>, <C,D>, <D,A>}
(3)有向完全图:在有向图中,如果任意两个顶点之间都 存在方向相反的两条弧 ,则称该图为有向完全图。n个顶点的有向完全图有n*(n - 1)条边。
注意:无向边用小括号“()”表示,有向边用尖括号“<>”表示。
-
在图中,若不存在顶点到其自身的边,且同一条边不重复出现,则称这样的图为简单图。 (下图即不是简单图)
-
有很少条边或弧的图称为稀疏图,反之称为稠密图。
-
有些图的边或弧具有与他相关的数字,这种与图的边或弧相关的数叫做权(Weight)。这种带权的图通常称为网(Netwrok)。
-
假设有两个图G=(V,{E})和G’=(v’,{E’}),如果V’是V的子集且E’是E的子集,则称G’为G的子图(Subgraph)。
图的顶点与边间的关系
- 对于无向图G=(V,{E}),如果(v, v’)属于E,则称顶点v和v互为邻接点(Adjacent),即v和v’相邻接。边(v, v’)依附(incident)于v和v‘,或者说(v, v’)与顶点v和v’相关联。顶点v的度(Degree)是和v相关联的边的数目,记为TD(v)。并且边数其实就是各顶点度数和的一半,简记为:e=1/2*∑nTD(vi)。
- 对于有向图G=(V, {E}),如果弧<v, v’>∈E,则称顶点v邻接到 顶点v’,顶点v’邻接自 顶点v。弧<v, v’>和顶点v, v’相关联。以顶点v为头的弧的数目称为v的入度(InDegree),记为ID(v);以v为尾的弧的数目称为v的出度(OutDegree),记为OD(v);顶点v的度为TD(v) = ID(v) + OD(v)。有向图的度即各顶点的出度和或入度和。
- 无向图G=(V,{E})中从顶点v到顶点v’的路径(Path)是一个顶点序列。如果G是有向图,则路径也是有向的(所经顶点都必须在顶点集合中)。在树中根结点到任意结点路径是唯一的,但是图中顶点与顶点之间的路径确实不唯一的。
- 路径上的长度是路径上的边或弧的数目。
- 第一个顶点和最后一个顶点相同的路径称为回路或环(Cycle)。序列中顶点不重复出现的路径称为简单路径。除了第一个顶点和最后一个顶点之外,其余顶点不重复出现的回路,称为简单回路或简单环。(下图,左边是简单环,右边不是简单环)
连通图及其相关术语
在无向图G中,如果从顶点v到顶点v’有路径,则称v和v’是连通的。如果对于图中任意两个顶点vi 、vj ∈V,vi 和vj 都是连通的,则称G是连通图(Connected Graph)。
无向图中的极大连通子图称为连通分量。它强调:
- 要是连通子图;
- 子图要是连通的;
- 连通子图含有极大顶点数;
- 具有极大顶点数的连通子图包含依附于这些顶点的所有边。
在有向图G中,如果对于每一对vi 、vj 、vi ≠vj ,从vi 到vj 和从vj 到vi 都存在路径,则称G是强连通图。有向图中的极大强连通子图称做有向图的强连通分量。
连通图的生成树
所谓一个连通图的生成树是一个极小的连通子图,它含有图中的全部的n个顶点,但只有足以构成一棵树的n-1条边。下图中,1为普通图,删除两条边后的2,3,即为生成树,不过满足定义的n-1条边的图并不一定是生成树如4。由此引申出,如果一个图有n个顶点和小于n-1条边,则是非连通图,如果他的边数多于n-1条边,则必定构成一个环(连通图)。
如果一个有向图恰有一个顶点的入度为0,其余顶点的入度均为1,则是一个有向树。其所谓入度为0其实就相当于树中的根结点,其余顶点的入度为1则是树种除根结点以外的结点只有一个双亲。
一个有向图的生成森林由若干棵有向树组成,含有图中的全部顶点,但只有足以构成若干棵不相交的有向树的弧。
(下图:2、3为1有向图的生成森林)
总结
- 图按照有无方向分为有向图和无向图。有向图由顶点和弧构成,无向图由顶点和边构成。弧由弧头和弧尾之分。
- 图按照边或弧的多少分为稠密图和稀疏图。如果任意两个顶点之间都存在边叫完全图,有向的叫有向完全图。若无重复的边或顶点到自身的边则叫简单图。
- 图中的顶点由邻接点、依附的概念。无向图的顶点的边数叫做度,有向图的顶点有入度和出度。
- 图上的边或权带数值则成为网。
- 图中顶点之间存在路径,两顶点存在路径则说明是连通的,如果路径最终回到起始点则称为环,当中不重复叫简单路径。若任意两顶点都是连通的,则图就是连通图,有向的则称强连通图。
- 图中有子图,若子图极大连通就是连通分量,有向的则是强连通分量。
- 无向图中联通且n个顶点n-1条边叫生成树。有向图中一顶点入度为0其余顶点入度为1的叫有向树。一个有向图有若干棵有向树构成生成森林。
图的储存结构
图不可能用简单的顺序储存结构来表示。而多重链表的方式,尽管可以实现图结构,但是由于各顶点的度数相差很大,会导致空间的浪费或者操作不便。下为五种不同的储存结构。
邻接矩阵
图的邻接矩阵(Adjacency Matrix)储存方式使用两个数组来表示图。一个一维数组储存图中的顶点信息,一个二维数组(称为邻接矩阵)储存图中的边或弧的信息。顶点数组为vertex[4] = {},边数组arc[4][4]={}。
-
在无向图中,矩阵的主对角线的值全为0,因为不存在顶点到自身的边,并且由于是无向图,v1 到v2 的边存在即v2 到v1 的边存在,所以无向图的边数组是一个对称矩阵。
有了这个矩阵,很容易可以得知图中的信息:
(1)可以轻易判断两顶点是否有边
(2)某个顶点的度,就是这个顶点vi 在邻接矩阵中第i行(或第i列)的元素之和
(3)顶点vi 的所有邻接点就是将矩阵中第i行元素扫描一遍,a[i][j]为1就是邻接点。 -
在有向图中,邻接矩阵并不对成。
(1)有向图讲究入度和出度,顶点v1 的入度为第v1 列各数之和,顶点v1 的出度为第v1 行的各数之和
(2)判断顶点vi 到vj 是否存在弧,只需要查找arc[i][j]是否为1即可。。 -
在图中,每条边上带有权值的图叫做网。我们用wij 表示(vi , vj )或<vi, vj>上的权值。
(1)在网的邻接矩阵中,用∞表示两点不相连,因为权值wij 大多数情况下是正值,但是也有可能是0,或者为负值,因此要用一个不可能的值来代表不存在,即∞。
(2)网的邻接矩阵中,主对角线上的值依旧是0.
typedef char VertexType;//顶点类型
typedef int EdgeType;//边上的权值类型
#define MAXVEX 100//最大顶点数
#define INFINITY 65535//用65535代表∞
typedef struct
{
char vexs[MAXVEX];//顶点表
int arc[MAXVEX][MAXVEX];//边表
int numNodes, numEdges;//当前图的顶点数和边数
}MGraph;
void CraeteMGraph(MGrapg *G)
{
int i, j, k, w;
printf("intput nodes and edges' nums:\n");
scanf("%d, %d", &G->numNodes, &G->numEdges);//输入顶点数和边数
for(i = 0; i < G->numNodes; i++)//建立顶点表
scanf(&G->vex[i]);
for(i = 0; i < G->numNodes; i++)//初始化邻接矩阵
for(j = 0; j < G->numNodes; j++)
G->arc[i][j] = INFINITY;
for(k = 0; k < numEdges; k++)//读入numEdges条边,建立邻接矩阵
{
printf("Intput edge(vi, vj)'s 'i' , 'j' and weight");
scanf("%d %d %d", &i, &j, &w);
G->arc[i][j] == w;
G->arc[j][i] == w;//无向图中,邻接矩阵关于主对角线对称
}
}
从代码可以看出,建立完整邻接表的时间复杂度为O(n + n2 + e),其中邻接矩阵G->arc的初始化耗费了O(n2)的时间。
邻接表
邻接表的思路与树的孩子储存法相同。在图中,把数组与链表相结合的储存方式称为邻接表(Adjacency List)。 利用邻接表储存稀疏图可以避免空间浪费。
邻接表的处理方法:
(1)图中的一个顶点用一维数组储存(也可以使用单链表,但是一维数组可以较容易地读取顶点信息)。另外,对于顶点数组中,每个数据元素还需要储存指向第一个邻接点的指针,以便查找该顶点的边信息
(2)图中每一个顶点vi 的所有邻接点构成一个线性表,由于邻接点个数不定,所以用单链表储存,无向图称为顶点vi 的边表,有向图称为顶点vi 作为弧尾的出边表。
通过邻接表获取信息:
(1)要想知道某个结点的度,就去查找这个顶点的边表的结点个数
(2)判断顶点vi 和vj 是否存在边,只需要测试顶点vi 的边表中adjvex是否存在结点vj 对应的下标j即可
(3)求顶点的所有邻接点,其实就是对此顶点的边表进行遍历,得到的adjvex域对应的顶点就是邻接点
(4)有向图建立的邻接表,很容易可以得知某个结点的出度。
有时为了便于确定顶点的入度或以顶点为弧头的弧,我们可以建立一个有向图的拟邻接表,即对每个顶点vi 都建立一个链接为vi 为弧头的表,如上。
对于带权值的网图,在边表结点定义中再加一个weight的数据域用于储存权值信息即可。
typedef char VertexType;//顶点类型
typedef int EdgeType;//边上的权值类型
typedef struct EdgeNode//边表结点
{
int adjvex;//邻接点域,储存该顶点对应的下标
int info;//储存权值,非网图非必要
struct EdgeNode *next;//指向下一个邻接点
}EdgeNode;
typedef struct VertexNode//顶点表结点
{
char data;//顶点域,储存顶点信息
EdgeNode *firstedge;//边表头指针
}VertexNode, AdjList[MAXVEX];
typedef struct
{
AdjList adjList;
int numNodes, numEdges;/当前图中的顶点数和边数
}GraphAdjList;
void CreateALGrapg(GraphAdjList *G)
{
int i, j, k;
EdgeNode *e;
printf("Intput vertex and edge's number:\n");
scanf("%d %d", &G->numNodes, &G->numEdges);//顶点数和边数
for(i = 0; i < G->numNodes; i++)//建立顶点表
{
scanf(&G->adjList[i].data);//输入顶点信息
G->adjList[i].firstedge = NULL;//将边表置空
}
for(k = 0; k < G->numEdges; k++)//建立边表
{
printf("Intput edge's vertex number:\n");
scanf("%d %d",&i, &j);//输入边的序号信息
//即头插法
e = (*EdgeNode *)malloc(sizeof(EdgeNode));//给边表结点申请空间
e->adjvex = j;//邻接序号
e->next = G->adjList[i].firstedge;//将e指针指向当前顶点指向的结点
G->adjList[i].firstedge = e;//将当前结点的指针指向e
e = (EdgeNode *)