图的基本概念
图的概念
图Graph: 顶点Vertex; 弧 Arc。(至少有一个顶点)
有向图Digraph:有向弧; A->B(弧头B/弧尾A); 有向完全图。 G=<v,a>
无向图Undigraph:无向边 Edge; 无向完全图. G=(v,a)
点
邻接点:无向图如A与B互为邻接点,有向图如A邻接到B
无向图顶点的度, TD(A)=2;
有向图顶点度, ID(A)=1,OD(A)=2,TD(A)=3
路径
图的分类
1.DG(有向图)、UDG(无向图)、
2.DN(网络,含权的有向图)和UDN(弧或边含权的无向图)。
3.稀疏图和稠密图
4.连通图 和非连通图
连通图 (无向)
在无向图G=(V, E)中,对任意两顶点u、v,都存在u到v的路径。
强连通图 (有向)
在有向图G=(V, E)中,对任意两顶点u、v,都存在u到v的有向路径。
极大连通子图 (连通分量)
在非连通图中
极小连通子图 (生成树)
在连通图中
条件:
1.图中全部顶点n
2.足以构成一颗树的n-1条边
特点:
没有回路
如图,有5个点,找4条边
图的存储结构
邻接矩阵表示法
方法:顶点表 + 邻接矩阵(存储顶点间关系)
特点:
无向
有向
图的数组(邻接矩阵)存储表示
typedef *** VertexType;//定义顶点类型
#define MVNUM 100 //最大顶点个数
#define INFINITY 32767 //最大值∞
typedef enum{DG, DN, UDG, UDN} GraphKind;//图类型:有向无向等等
// 弧的定义
typedef struct ArcType {
int/double adj; //邻接数,0/1或w/∞
InfoType *info; //弧的附加信息,InfoType可为char和int
}ArcType;
// 图的定义
typedef struct {
VertexType vexs[MVNUM]; //顶点信息
ArcType arcs[MVNUM][MVNUM]; // 邻接矩阵
int vexnum, arcnum; // 顶点数,弧数
GraphKind kind; // 图的类型标记
} MGraph; //矩阵图
创建
//输入图的种类及顶点和边信息构造图G(邻接矩阵表示)
Status CreateGraph(Mgraph &G) {
scanf(“%d”,&G.kind);//枚举值DG,DN…实输入0 1 …
switch (G.kind) {
case DG : return CreateDG(G);
case DN : return CreateDN(G);
case UDG : return CreateUDG(G);
case UDN : return CreateUDN(G);
default : return ERROR;
}
//建立无向网G
Status CreateUDN(Mgraph &G){
cin>>G.vexnum>>G.arcnum>>IncInfo; //IncInfo为0表弧无附加信息
for (i=0 ; i< G.vexnum; ++i) cin>>G.vexs[i];
for (i=0 ; i< G.vexnum; ++i)
for(j=0 ; j< G->vexnum; ++j)
G.arcs[i][j] = {INFINITY, NULL}; //各弧初始化
for (k=0;k< G.arcnum;++k){
scanf(“%c %c %lf”,&v1,&v2,&w); //输入一“边”及其权值
i=LocateVex(G,v1);j= LocateVex(G,v2) ; //确定顶点下标
G.arcs[i][j].adj = w;
if (IncInfo) cin>>G.arcs[i][j].info;
G.arcs[j][i]= G.arcs[i][j]; //无向网需将对称弧加入
}
} //END
优缺点
优点:易于求顶点度(区分有/无向图)、求邻接点,易判断两点间是否有弧或边相连
缺点:不利于稀疏图的存储,因弧不存在时也要存储相应信息;且要预先分配足够大空间
邻接表表示法
方法:顶点数组,每个数组元素包含两部分:data + firstarc(邻接点下标所构成的一个链表)
无向
有向
邻接表存储结构定义
typedef struct ArcNode {
int adjvex;
struct ArcNode * nextarc;
InfoType *info;
} ArcNode;
typedef struct VNode {
VertexType data;
ArcNode *firstarc;
}VNode, AdjList[MVNUM];
typedef struct {
AdjList vertices;
int vexnum, arcnum;
GraphKind kind;
} ALGraph; //邻接表图
创建
Status CreateDN(ALGraph &G){ //建有向网
scanf(“%d%d%d”, &G.vexnum, &G.arcnum, &IncInfo);
for (i=0 ; i< G.vexnum; ++i) {
cin>>G.vertices[i].data; G.vertices[i].firstarc=NULL;
}
for (k=0;k< G.arcnum;k++) {
cin>>v1>>v2>>w;
i=LocateVex(G,v1); j= LocateVex(G,v2);
arc=new ArcNode;
arc->adjvex=j;
if (IncInf) cin>>arc->info;
arc->nextarc = G.vertices[i].firstarc; //插入到开始位置
G.vertices[i].firstarc=arcn;//邻接点与输入逆序排列,正序?
} //邻接点顺序依赖输入顺序和创建程序,默认正序
Return OK;
} //思考无向图的创建还有何不同?
十字链表表示法(略)
总结
图的遍历
图遍历的概念
深度优先遍历(Depth-First Searching)——dfs
原理
和树的先根遍历一样
算法实现
邻接矩阵
邻接表
两种方法的实现代码
int visited[G.vexNum]; // 定义一个全局数组visited,用于记录每个顶点是否被访问过
// DFS函数的声明和实现
void DFS(Graph G, int v, Status (*visit)(ElemType));
void DFS(Graph G, int v, Status (*visit)(ElemType)){
visit(v); // 对当前顶点进行访问操作
visited[v] = TRUE; // 将当前顶点标记为已访问
for(w = FirstAdjVex(G, v); w >= 0; w = NextAdjVex(G, v, w)) {
if (!visited[w]) { // 如果相邻顶点未被访问过
DFS(G, w, visit); // 递归调用DFS函数,继续访问相邻顶点
}
}
return; // 返回上一层递归
}
// DFSTraverse函数的声明和实现
void DFSTraverse(Graph G, Status (*visit)(int v));
void DFSTraverse(Graph G, Status (*visit)(int v)){
for (int v = 0; v < G.vexnum; ++v) { // 初始化visited数组,将所有顶点标记为未访问
visited[v] = FALSE;
}
for (int v = 0; v < G.vexnum; ++v) { // 遍历所有顶点
if (!visited[v]) { // 如果顶点未被访问过
DFS(G, v, visit); // 调用DFS函数,从该顶点开始进行深度优先搜索
}
}
}
复杂度:每个顶点调用一次DFS,DFS主要操作是查找邻接点。
邻接矩阵存储时查找一顶点邻接点的复杂度为O(n),总复杂度O(n2);
邻接表存储时查找所有顶点所有邻接点复杂度为O(e),总复杂度为O(n+e)
广度优先遍历( Breadth-First Searching )——BFS
原理
算法实现
访问出队的邻接点
void BFS(Graph G, int v, Status (*visit)(ElemType){
visit (v); visited[v]=TRUE;
InitQueue(Q); EnQueue(Q, v);
while (!QueueEmpty(Q)) {
DeQueue(Q, v);
for(w=FirstAdjVex(G,v); w>=0;w=NextAdjVex(G,v,w))
if ( ! visited[w] ) {
visit(w); visited[w]=TRUE; EnQueue(Q, w);
}
}
}
void BFSTraverse(Graph G, Status (*visit)(int v)) {
for (v=0; v<G.vexnum; ++v) visited[v] = FALSE;
for (v=0; v<G.vexnum; ++v)
if (!visited[v]) BFS(G, v, visit);
} //教材中将此处两个函数代码合并为了一个函数
复杂度:每个顶点进一次队,出队时主要操作是找邻接点,
邻接矩阵存储时查找某顶点的邻接点复杂度为O(n),总复杂度O(n2);
用邻接表存储时查找所有定点所有邻接点复杂度O(e),总复杂度为O(n+e)
图遍历的应用
1.走出迷宫
2.循环依赖与死锁问题 等同于判断是否存在有向回路
3.应用:迷宫最短路 连通性与连通分量 生成树
结论:dfs过程中,从v出发找到邻接点w,若w是已访问过的祖先结点,则<v,w>必是一条回路的边
总结与推广
总结
回溯实现
推广