07-图
图的逻辑结构
图的存储结构
顺序存储结构(有向图为例)
//邻接矩阵的结构型定义
typedef struct{
int no; //顶点编号
char info; //顶点其他信息,一般默认char型
}VertexType; //顶点类型
//图的定义
typedef struct{
int edges[maxSize][maxSize]; //邻接矩阵定义,如果是有权图,则在此句中将int改为float
int n,e; //分别为顶点数和边数
VertexType vex[maxSize]; //存放结点信息
}MGraph; //图的邻接矩阵类型
链式存储结构
//邻接表存储表示的定义如下
//边
typedef struct ArcNode{
int adjvex; //该边所指向的结点的位置
struct ArcNode *nextarc; //指向下一条边的指针
int info; //该边的相关信息(如:权值)
}ArcNode;
//顶点
typedef struct{
char data; //顶点信息
ArcNode *firstarc; //指向第一条边的指针
}VNode;
//邻接表
typedef struct{
VNode adjlist[maxSize]; //邻接表
int n,e; //顶点数和边数
}AGraph; //图的邻接表类型
十字链表
邻接多重表
图的基础算法
遍历
深度优先遍历(DFS)
int visit[maxSize]; //v是起点编号,visit[]是一个全局数组,作为顶点的访问标记,初始时元素全为0,表示所有顶点均未访问
void DFS(int v,AGraph *G){
visit[v]=1; //设置已访问标记
Visit(v); //访问顶点v的操作
ArcNode *q=G->adjList[v].first; //q指向顶点v的第一条边
while(q!=NULL){
if(visit[q->adjV]==0){ //若顶点未访问则递归访问
DFS(q->adjV,G);
}
q=q->next; //q指向顶点v的下一条边的终点
}
}
广度优先遍历(BFS)
void BFS(AGraph *G,int v,int visit[maxSize]){
//visit[]数组被初始化为全0
ArcNode *p;
int que[maxSize],front=0,rear=0; //这是队列定义的简单写法
int j;
Visit(v); //任意访问顶点v的函数
visit[v]=1;
rear=(rear+1)%maxSize; //当前顶点v进队
que[rear]=v;
while(front!=rear){ //队空的时候说明遍历完成
front=(front+1)%maxSize; //顶点出队
j=que[front];
p=G->adjlist[j].firstarc; //p指向出队顶点j的第一条边
while(p!=NULL){
if(visit[p->adjvex]==0){ //当前邻接顶点未被访问,则进队
Visit(p->adjvex);
visit[p->adjvex]=1;
rear=(rear+1)%maxSize; //该顶点进队
que[rear]=p->adjvex;
}
p=p->nextarc; //p指向j的下一条边
}
}
}
最小(代价)生成树
普里姆(Prim)和克鲁斯卡尔(Kruskal)算法都是针对“无向图”
普里姆(Prim)算法
以顶点为操作单位,在邻接矩阵存储结构下,时间的复杂度为O(n2)
普里姆算法执行过程:
从树中的某一个顶点v0开始,构造生成树的算法执行过程如下:
1)将v0到其他顶点的所有边当做候选边;
2)重复一下步骤n-1次,使得其他n-1个顶点被并入到生成树中
a.从后选边中挑选出权值最小的边输出,并将与该边另一端相接的顶点v并入生成树中;
b.考查所有剩余顶点vi,如果(v,vi)的权值比lowcost[vi]小,则用(v,vi)的权值更新lowcost[vi]。
//n是顶点个数,MGraph是带权图,v0代表起始顶点,sum代表最小代价
void Prim(int n,float MGraph[][n],int v0,float &sum){
int lowCost[n],vSet[n]; //lowCost[]代表当前生成树到图中其余顶点其他边的最小权值,vSet[]被置为1则代表该顶点已经并入生成树中
int v,k,min; //v指向当前刚并入的顶点,
for(int i=0;i<n;++i){
lowCost[i]=MGraph[v0][i]; //初始化lowCost
vSet[i]=0;
}
v=v0;
vSet[v]=1; //将v0并入树中
sum=0; //sum清零用来累计树的权值
for(int i=0;i<n-1;++i){
min=INF; //INF是一个已经定义的比图中所有边权值都大的常量
for(int j=0;j<n;++j){ //选出候选边的最小者
if(vSet[j]==0&&lowCost[j]<min){ //选出当前生成树到其余顶点最短边中最短的一条
min=lowCost[j];
k=j;
}
}
vSet[k]=1;
v=k;
sum+=min; //用sum记录最小生成树的权值
for(int j=0;j<n;++j){ //以刚入选的顶点v为媒介更新候选边
if(vSet[j]==0&&MGraph[v][j]<lowCost[j]){
lowCost[j]=MGraph[v][j];
}
}
}
}
克鲁斯卡尔(Kruskal)算法
以边为操作单位,适用于稀疏图
普里姆算法执行过程:
将图中边按权值大小从小到大排序,从权值最小边开始扫描各边,并检测当前边是否为候选边,即是否该边的并入会构成回路,如不构成回路,则将该边并入当前生成树中,直到所有边都被检测完毕;
typedef struct{
int a,b; //a和b为一条边所连的两个顶点
int w; //边的权值
}Road;
Road road[maxSize];
int v[maxSize]; //定义并查集数组
int getRoot(int a){ //在并查集中查找根结点的函数
while(a!=v[a])
a=v[a];
return a;
}
void Kruskal(Road road[],int n,int e,int &sum){
int a,b;
sum=0;
for(int i=0;i<n;++i){
v[i]=i;
}
sort(road,e); //对road数组里中的e条边按其权值从小到大排序
for(int i=0;i<e;++i){
a=getRoot(road[i].a);
b=getRoot(road[i].b);
if(a!=b){
v[a]=b;
sum+=road[i].w;
}
}
}
最短路径
迪杰斯特拉(Dijkstra)算法
有向带权图求某一顶点到其余顶点的最短路径
迪杰斯特拉算法执行过程:
dist[vi]:表示当前以找到已找到的从v0到每个终点vi的最短路径长度,他的初态为:若从v0到vi有边,则dist[vi]为边上的权值,若没边则置dist[vi]为无穷大;
path[vi]:保存从v0到vi的前一个顶点,他的初态为:如果v0到vi有边,则path[vi]=v0,否则path[v-i]=-1;
set[vi]:set[vi]=0表示没有并入最短路径,set[vi]=1表示已经并入最短路径,他的初态为:set[v0]=1,其余元素全为0;
1)从当前dist[]数组中选出最小值,假设为dist[vn],将set[vn]设置为1,表示当前新并入的顶点为vn;
2)循环扫描图中顶点,对每个顶点进行以下检测:
假设当前顶点为vj,检测vj是否已经并入最短路径中,如果set[vj]=1则表示已并入则什么也不做,如果set[vj]=0,则比较dist[vj]和dist[vn]+w(w表示边<vn,vj>的权值),这个比较就是要看v0经过旧的最短路径到达vj和v0经过含有vn的新的最短路径到达vj那个更短,如果dist[vj]>dist[vn]+w,则用新的路径长度来更新旧的,并把顶点vn加入路径中,且作为路径上vj之前的哪个顶点,否则什么都不做;
3)对1)和2)循环执行n-1次,即可得到v0到其余所有顶点的最短路径;
//n为顶点个数,MGraph为边的信息,vo代表最短路径的起始顶点,dist存最短路径长度,path存储最短路径
void Dijkstra(int n,float MGraph[][n],int v0,int dist[].int path[]){
int set[maxSize];
int min,v;
for(int i=0;i<n;++i){
dist[i]=MGraph[v0][i];
set[i]=0;
if(MGraph[v0][i]<INF)
path[i]=0;
else
path[i]=-1;
}
set[v0]=1;path[v0]=-1; //以上为初始化
for(int i=0;i<n-1;++i){
min=INF;
for(int j=0;j<n;++j){ //每次从剩余顶点选出一个顶点,通往这个顶点的路径在通往所有剩余顶点的路径长度中是长度最短的
if(set[j]==0&&dist[j]<min){
v=j;
min=dist[j];
}
}
set[v]=1; //将选出的顶点并入最短路径中
for(int j=0;j<n;++j){ //这个循环以刚并入的顶点作为中简点,对所有通往剩余顶点的路径进行检测
//判断顶点v的加入是否会出现通往顶点j的更短的路径,若出现则改变原来路径及长度,否则什么都不做
if(set[j]==0&&dist[v]+MGraph[v][j]<dist[j]){
dist[j]=dist[v]+MGraph[v][j];
path[j]=v;
}
}
}
}
//函数结束时dist中存放了v点到其余顶点的最短路径长度,path中存放了v点到其余各顶点的最短路径
弗洛伊德(Floyd)算法
有向带权图求任意一对顶点之间的最短路径,时间复杂度为O(n3)
弗洛伊德(Floyd)算法执行过程:
1)设置两个矩阵A和Path,初始时将图的邻接矩阵赋值给A,将矩阵Path中元素全部设置为-1。
2)以顶点k为中间顶点,k取0到n-1(n为图中顶点个数),对图中所有顶点对儿{i,j}进行如下检测与修改:
如果A[][][][][][][i] [j]**>**A[i] [k]+A[k] [j],则将A[][][][][][][i] [j]改为A[i] [k]+A[k] [j]的值,将Path[i] [j]改为k,否则什么都不做。
//弗洛伊德伪代码
void print Path(int u,int v,int path[][max]){
if(path[u][v]==-1)
直接输出;
else{
int mid=path[u][v];
printPath(u,mid,path);
printPath(mid,v,path);
}
}
//弗洛伊德算法代码
void Floyd(int n,float MGraph[][n],int Path[][n]){
int i,j,v;
int A[n][n];
for(i=0;i<n;i++){ //双循环用来对A和Path进行初始化
for(j=0;j<n;j++){
A[i][j]=MGraph[i][j];
Path[i][j]=-1;
}
}
for(v=0;v<n;v++){ //下面三层循环完成了以v为中间点对所有的顶点对儿{i,j}进行了检测和修改
for(i=0;i<n;i++){
for(j=0;j<b;j++){
if(A[i][j]>A[j][v]+A[v][j]){
A[i][j]=A[j][v]+A[v][j];
Path[i][j]=v;
}
}
}
}
}
拓扑排序
AOV网(活动在顶点上的网)
AOV网是一种以顶点表示活动,以边表示活动的先后次序且没有回路的有向图
拓扑排序核心算法
1)从有向图中选择一个入度为0的顶点输出;
2)删除1)中的顶点,并且删除从该顶点发出的全部边;
3)重复上述两步,直到剩余的图中不存在入度为0的顶点为止;
//邻接表稍作修改加一个count用来记录顶点当前的入度
typedef struct{
char data;
int count;
ArcNode *first;
}VNode;
int TopSort(AGraph *G){
int i,j,n=0;
int stack[maxSize],top=-1; //定义并初始化栈
ArcNode *p;
/*这个循环将图中入度为0的顶点入栈*/
for(i=0;i<G->n;i++){ //图中顶点从0开始编号
if(G->adjList[i].count==0)
stack[++top]=i;
}
while(top!=-1){
i=stack[top--]; //顶点出栈
++n; //计数器+1,统计当前顶点
std::out<<i<<""; //输出当前顶点
p=G->adjList[i].first;
/*这个循环实现将所有顶点引出的边所指向的顶点的入度减少1,并将这个过程中入度变为0的顶点入栈*/
while(p!=NULL){
j=p->adjvex;
(G->adjlist[j].count)--;
if(G->adjlist[j].count==0)
stack[++top]=j;
p=p->next;
}
}
if(n==G->n)
return 1;
else
return 0;
}
逆拓扑排序
1)从有向图中选择一个出度为0的顶点输出;
2)删除1)中的顶点,并且删除指向该顶点的全部边;
3)重复上述两步,知道剩余的图中不存在出度为0的顶点为止;
用深度优先遍历实现逆拓扑排序
int visit[maxSize]; //v是起点编号,visit[]是一个全局数组,作为顶点的访问标记,初始时元素全为0,表示所有顶点均未访问
void DFS(int v,AGraph *G){
visit[v]=1; //设置已访问标记
ArcNode *q=G->adjList[v].first; //q指向顶点v的第一条边
while(q!=NULL){
if(visit[q->adjV]==0){ //若顶点未访问则递归访问
DFS(q->adjV,G);
}
q=q->next; //q指向顶点v的下一条边的终点
}
Visit(v); //访问顶点v的操作
}
关键路径(重点在手工求解上)
AOE网(活动在边上的网)
**AOV:**顶点表示活动,边无权值,边代表活动之间的先后关系;
**AOE:**边代表活动,边有权值,边代表活动持续时间;顶点表示事件,事件是图中新活动的开始或旧活动结束的标志;
**源点:**对于一个表示工程的AOE网,只存在一个入度为0的顶点,则称为源点;
**汇点:**对于一个表示工程的AOE网,只存在一个出度为0的顶点,则称为汇点;
1)求拓扑排序序列和逆拓扑排序序列
1 3 4 6 和 6 4 3 1
2)事件最早:
ve的e代表Early
ve(1)=0; ve(3)=2; ve(4)=1; ve(6)=ve(4)+a7(取最长)=9;
3)事件最迟:
vl的l代表late
vl(6)=ve(6)=9; vl(4)=vl(6)-a7=1; vl(3)=vl(6)-a6=3; vl(1)=vl(4)-a3(取最短)=0;
4)活动最早:
e(a2)=ve(1)=0; e(a3)=0; e(a6)=2; e(a7)=1;
5)活动最迟:
l(a2)=vl(3)-a2=1; l(a3)=vl(4)-a3=0; l(a6)=vl(6)-a6=3; l(a7)=vl(6)-a7=1;
6)关键活动:
即活动最早和活动最迟重合的那些,也就是a3和a7;
7)关键路径:
关键活动所形成的路径为关键路径;
举例说明: