6.1 图的定义
图G由顶点集V和边集V组成,记为G=(V,E),其中V(G)表示图G中顶点的有限非空集;E(G)表示图G中顶点之间的边集合。
若V={v1,v2,…,vn},则用|V|表示图G中顶点的个数,也称图G的阶;用|E|表示图G中的边数。
有向图:
G1=(V1,E1)
V1={A,B,C,D,E}
E1={<A,B>,<A,C>,<A,D>,<A,E>,<B,A>,<B,C>,<B,E>,<C,D>}
注:<v,w>与<w,v>方向相反
无向图:
G2=(V2,E2)
V2={A,B,C,D,E}
E2={(A,B),(B,D),(B,E),(C,D),(C,E),(D,E)}
简单图:不存在重复边,不存在顶点到自身的边
多重图:图G中某两个结点之间的边数多于一条,又允许顶点通过同一条边和自己关联
顶点的度,入度,出度:
对于无向图,顶点v的度是指依附于该顶点的边的条数,记为TD(v),全部顶点的度的和等于边数的两倍
对于有向图,入度是以顶点v为终点的有向边的条数,记为ID(v);出度是以顶点v为起点的有向边的条数,记为OD(v);顶点v的度等于其入度和出度之和
顶点间的关系描述
若图G中任意两个顶点都是连通的,则称图G为连通图,否则为非连通图。
对于n个顶点的无向图G
若G为连通图,则最少有n-1条边
若G是非连通图,则最多可能有C2n-1条边
若图G中任意两个顶点都是强连通的,则称图G为强连通图。
对于n个顶点的有向图G
若G是强连通图,则最少有n条边(形成回路)
子图:
无向图中极大连通子图称为连通分量
有向图中极大强连通子图称为有向图的连通分量
连通图的生成树是包含图中全部顶点的一个极小连通子图。
若图中顶点数为n,则它的生成树有n-1条边;对生成树而言,若砍去它的一条边,则会变成非连通图,若加上一条边则会形成回路
在非连通图中,连通分量的生成树构成了非连通图的生成森林
边的权:
无向完全图:无向图中任意两个顶点之间都存在边
有向完全图:有向图中任意两个顶点之间都存在方向相反的两条弧
边数很少的图称为稀疏图,反之称为稠密图
树:不存在回路,且连通的无向图
有向树:一个顶点的入度为0,其余顶点的入度均为1的有向图
6.2 图的存储
6.2.1 邻接矩阵
#define MaxVertexNum 100//顶点数目最大值
typedef struct{
char Vex[MaxVertexNum];//顶点表
int Edge[MaxVertexNum][MaxVertexNum];//邻接矩阵,边表
int vexnum,arcnum;//图当前顶点数和边数/弧数
}MGraph;
邻接矩阵存储带权图:
邻接矩阵性能分析:
空间复杂度O(|v|2)——只和顶点数相关,和实际边数无关
适合存储稠密图
无向图的邻接矩阵是对称矩阵,可以压缩存储
邻接矩阵性质:
6.2.2邻接表
#define MaxVertexNum 100//图中顶点数目最大值
typedef struct ArcNode{//边表结点
int adjvex;//该弧所指向的顶点的位置
struct ArcNode *next;//指向下一条弧的指针
//InfoType info;//网的边权值
}ArcNode;
typedef struct VNode{//顶点表结点
VertexType data;//顶点信息
ArcNode *first;//指向第一条依附该顶点的弧的指针
}VNode,AdjList[MaxVertexNum];
typedef struct{
AdjList vertices;//邻接表
int vexnum,arcnum;//图的顶点数和弧数
}ALGraph;//ALGraph是以邻接表存储的图类型
邻接表与邻接矩阵的比较
6.2.3十字链表
十字链表只适合存储有向图
6.2.4邻接多重表
邻接多重表只适合存储无向图
6.3 图的遍历
6.3.1广度优先(BFS)
bool visited[MaxVertexNum];//访问标记数组
void BFSTraverse(Graph G){//对图G进行广度优先遍历
for(i=0;i<G.vexnum;++i)
visited[i]=false;//访问标记 数组 初始化
InitQueue(Q);//初始化辅助队列Q
for(i=0;i<G.vexnum;++i)//从0号顶点开始遍历
if(!visited[i])//if(visited[i]==false)
BFS(G,i);//i未被访问,从i开始访问
}
void BFS(Graph G,int v){//从顶点v出发,广度优先遍历图G
visit(v);//访问初始结点v
visited[v]=true;//对v做以访问标记
EnQueue(Q,v);//v入队
while(!isEmpty(Q)){
DeQueue(Q,v);//顶点v出队
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w))
/*检测v所有邻接点
FirstNeighbor(G,x):求图G中顶点x的第一个邻接点,若有则返回顶点号
NextNeighbor(G,x,y):假设y是x的第一个邻接点,则返回除y外的第一个邻接点*/
if(!visited[w]){//w为v的尚未访问的邻接点
visit(w);//访问顶点w
visited(w)=true;//对w做以访问标记
EnQueue(Q,w);//顶点w入队
}//if
}//while
}
/*第二个函数可访问连通图,两个函数访问非连通图*/
复杂度分析:
空间复杂度:由辅助队列决定最坏O(|V|)
时间复杂度:
广度优先生成树:邻接表存储则表示方式不唯一
非连通图可形成广度优先生成森林。
6.3.2 深度优先(DFS)
bool visited[MaxVertexNum];//访问标记数组
void DFSTraverse(Graph G){//对图G进行深度优先遍历
for(v=0;v<G.vexnum;++v)
visited[v]=false;
for(v=0;v<G.vexnum;++v)
if(!visited[v])
DFS(G,v);
}
void DFS(Graph G,int v){//从顶点v出发深度优先遍历图G
visit(v);//访问顶点v
visited[v]=true;
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w))
if(!visited[w])//w为v的尚未访问的邻接点
DFS(G,w);
}
复杂度分析:
空间复杂度:来自函数调用栈,最坏情况,递归深度O(|V|);
时间复杂度:
广度优先生成序列与生成树:
6.4 图的应用
6.4.1 最小生成树
对于一个带权连通无向图G=(V,E),生成树不同,每棵树的权也可能不同。权最小的数即为最小生成树。
Prim算法 v.s. Kruskal算法:
6.4.2 最短路径
广度优先(BFS)算法求无权图最短路径:
Dijkstra算法求带权图最短路径:
此算法不适合求负权值最短路径
Floyd算法:
求出每一对顶点之间的最短路径
时间复杂度O(|V|3)
6.4.3 有向无环图描述表达式
有向无环图:不存在环的有向图
描述表达式:
6.4.4 拓扑排序
AOV网:用顶点表示活动的网(有向无环图)
拓扑排序的实现:
①从AOV网中选择一个没有前驱(入度为0)的顶点输出
②从网中删除该顶点和所有以它为起点的有向边
③重复①②直到当前的AOV网为空或当前网中不存在无前驱的顶点为止
逆拓扑排序的实现:
①从AOV网中选择一个没有后继(出度为0)的顶点输出
②从网中删除该顶点和所有以它为终点的有向边
③重复①②直到当前的AOV网为空为止
6.4.5 关键路径
求关键路径步骤: