图
- 定义
图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V, E),G表示一个图,V是图G中顶点的集合,E是图G中边的集合。
注意:
a.线性表中把数据元素叫元素,树中叫结点,图中称之为顶点(Vertex);
b.线性表没有数据元素叫空表,树中没有结点叫空树,而图中不允许没有顶点,所以定义中才是顶点的有穷非空集合;
c.线性表中相邻数据元素之间具有线性关系,树中相邻两层结点具有层次关系,图中任意两个顶点之间都可能有关系(顶点之间的逻辑关系用边来表示,边集可以为空)。 - 图的分类
a.按有无方向
无向图(Undirected graphs):顶点+边(无向边Edge(vi, vj));
无向完全图:任意两个顶点都存在边,cntEdge = cntVex * (cntVex - 1) / 2。
有向图(Directed graphs):顶点+弧(有向边Arc<vi, vj>),弧有弧尾(vi)与弧头(vj)之分。
有向完全图:任意两个顶点都存在方向互为相反的两条弧,cntEdge = cntVex * (cntVex - 1)。
b.按边(弧)的多少
简单图*:不存在顶点到其自身的边,且同一条边不重复出现,下图为非简单图;
c。其他分类
网:边(弧)带权值的图;
- 图的顶点与边的关系
a.无向图G=(V, {E}),如果边(v, v')∈E,则称顶点v与v'互为邻接点(Adjacent),即v和v'相邻接。边(v, v')依附于顶点v与v'(或者边(v, v')与顶点v与v'相关联);(注:个人觉得这就是废话,我这条边属于整个图所有边的集合,必然v和v'相邻啊,后面的必然也是废话。)
a.1.顶点v的度(Degree)是和v相关联的边的数目,记为TD(v);
b.有向图G=(V, {E}),如果弧<v, v'>∈E,则称顶点v邻接到顶点v',顶点v'邻接自顶点v。弧<v, v'>与顶点v和顶点v'相关联。
b.1.以顶点v为头的弧的数目称为v的入度(InDegree),记为ID(v);以顶点v为尾的弧的数目称为v的出度(OutDegree),记为OD(v)。
c.路径(Path):就简单的理解为一个顶点到另一个顶点走过的边(弧)的集合。
c.1.简单路径:序列中顶点不重复出现的路径;
c.2.环(回路):最后一个顶点和第一个顶点相同的路径;
c.2.1简单环(回路):就是除了最后一个顶点和第一个顶点重复以外,不存在其他顶点重复(左图简单环,右图非简单环)。
- 连通图相关
a.无向图中,顶点v到顶点v'存在路径,那么顶点v与顶点v'是连通的;
b.图中如果对于任意的顶点v与v‘都是连通的,且路径中的每条边都属于E(个人理解),那么该图是连通图。下图1为非连通图,图2为连通图。
c.连通分量:无向图中的极大连通子图;
注:1.要是子图;
2.子图要是连通的;
3.连通子图含有极大顶点数;
4.具有极大顶点数的连通子图包含依附于这些顶点的所有边。
上四图说明:图1是一个无向非连通图,但它有两个连通分量,图2和图3。而图4虽然是图1的子图,但它不满足连通子图的极大顶点数。
d.有向图类似与无向图,只是换了个名字。有向图的连通图叫强连通图,连通分量叫强连通分量。
e.生成树
e.1.无向图中连通且n个顶点n-1条边叫生成树;
e.2.有向图中一顶点入度为0其余顶点入度为1的是有向树;
e.3.一个有向图若干棵有向树构成森林。 - 图的存储结构
a.邻接矩阵
图的邻接矩阵(Adjacency Matrix)是用两个数组来表示图。顶点(一维数组),边或弧(二维数组(称邻接矩阵))。
对于无向图,邻接矩阵是个对称矩阵,某顶点的度=邻接矩阵中vi的第i行(或者列)所有元素之和。
对于有向图,邻接矩阵不是对称矩阵,顶点入度=邻接矩阵中vi的第i列之和,出度=第i行之和。
图的边上有了权值,也就是网图,邻接矩阵的定义如下:
注:因为权值只是一般来说才是正值,有时候也可能是0或者负值,所以使用无穷来表示一个不可能的值。
一个简单的有向网图及其存储结构如下图所示:
邻接绝阵存储结构代码实现// 邻接矩阵方式存储图结构 typedef char VertexType; // 顶点的类型 typedef int EdgeType; // 边上的权值类型 #define MAXVEX 100 // 最大顶点数 #define INFINITY 65535 // 65535 代表 无穷 typedef struct { VertexType vexs[MAXVEX]; // 顶点表 EdgeType arc[MAXVEX][MAXVEX]; // 邻接矩阵,看作边表 int numVertexes, numEdges; // 图中当前的顶点数,边数 }MGraph;
// 创建无向网图的邻接矩阵表示,n个顶点,e条边,时间复杂度O(n+n^2+e) void CreateMGraph(MGraph *G) { int i, j, k, w; //printf("请输入顶点数和边数:\n"); printf("input the number of vertex and edge(v, e):\n"); scanf("%d,%d", &G->numVertexes, &G->numEdges); getchar(); for (i=0; i<G->numVertexes; i++) { scanf("%c", &G->vexs[i]); // printf("%c", G->vexs[i]); } for (i=0; i<G->numVertexes; i++) { for (j=0; j<G->numVertexes; j++) { G->arc[i][j] = INFINITY; } } for (k=0; k<G->numEdges; k++) { // printf("请输入边(vi, vj)上的下标i, 下标j和权值w:"); printf("input edge(vi, vj), index of i, j and the weight(w):"); scanf("%d,%d,%d", &i, &j, &w); G->arc[i][j] = w; G->arc[j][i] = G->arc[i][j]; // 图为无向图,矩阵对称 } }
邻接矩阵的问题:对于边数 相对 顶点数较少的图,该结构对存储空间的浪费较大。
邻接表(Adjacency List),数组与链表相结合的存储方法。一维数组(顶点,指向第一个邻接点的指针)),单链表(vi的所有邻接点)(无向图称为vi的边表,有向图称为vi作为弧尾的出边表)。
一个简单的无向图的邻接表结构如下图:
对于带权值的网图,只需要在邻接表中间添加一个weight的数据域用来存放权值即可。如下图所示:
邻接表存储结构代码实现// 邻接表方式存储图结构 typedef char VertexType; typedef int EdgeType; #define MAXVEX 100 // 最大顶点数 // 边表结点 typedef struct _EdgeNode { int adjvex; // 存储该顶点对应的下标 EdgeType weight; struct EdgeNode *next; }EdgeNode; // 顶点表结点 typedef struct _VertexNode { VertexType data; EdgeNode *firstedge; // 边表头指针 }VertexNode, AdjList[MAXVEX]; // 邻接表图结构 typedef struct { AdjList adjList; int numVertexes, numEdges; // 当前顶点数和边数 }GraphAdjList;
创建无向图代码// 无向图创建邻接表,时间复杂度O(n+e) void createALGraph(GraphAdjList *G) { int i, j, k; EdgeNode *e; printf("Input the number of vertextes and edges:\n"); scanf("%d,%d", &G->numVertexes, &G->numEdges); getchar(); for (i=0; i<G->numVertexes; i++) { scanf("%c", &G->adjList[i].data); G->adjList[i].firstedge = NULL; } for (k=0; k<G->numEdges; k++) { // 建立边表 printf("Input edge (vi, vj), i and j:\n"); scanf("%d,%d", &i, &j); // 其实就是链表的头插法创建链表 e = (EdgeNode *)malloc(sizeof(EdgeNode)); e->adjvex = j; e->next = G->adjList[i].firstedge; G->adjList[i].firstedge = e; e = (EdgeNode *)malloc(sizeof(EdgeNode)); e->adjvex = i; e->next = G->adjList[j].firstedge; G->adjList[i].firstedge = e; } }
c.十字链表、邻接多重表、边集数组等结构先不研究了 - 图的遍历
a.深度优先遍历
b.广度优先遍历