csdn_spider/blog/ds19991999/原创-- 第七章 图.md

548 lines
12 KiB
Markdown
Raw Normal View History

2021-02-27 15:01:45 +00:00
# 原创
第七章 图
# 第七章 图
2024-07-03 09:49:47 +00:00
## 一、图的存储结构
2021-02-27 15:01:45 +00:00
>
2024-07-03 09:49:47 +00:00
### 1.邻接矩阵
2021-02-27 15:01:45 +00:00
>
2024-07-03 09:49:47 +00:00
### 2.邻接表
2021-02-27 15:01:45 +00:00
>
2024-07-03 09:49:47 +00:00
### 3.邻接多重表(有点看不懂…)
2021-02-27 15:01:45 +00:00
>
2024-07-03 09:49:47 +00:00
## 二、图的遍历
2021-02-27 15:01:45 +00:00
2024-07-03 09:49:47 +00:00
### 1.深度优先搜索遍历
2021-02-27 15:01:45 +00:00
>
```
//以邻接表存储结构为例
int visit[maxSize];
//visit[]作为顶点的访问标记的全局数组初始化为0
void DFS(AGraph *G,int v)
{
ArcNode *p;
visit[v]=1; //置已访问标记
Visit(v);
p=G->adjlist[v].firstarc; //p指向顶点第一条边
while(p!=NULL)
{
if(visit[p->adjvex]==0) //若顶点未访问,则递归访问它
{
DFS(G,p->adjvex); //(1)
p=p->nextarc; //p指向顶点v的下一条边的终点
}
}
}
//与二叉树对比
void preorder(BTNode *p)
{
if(!p=NULL)
{
visit(p);
preorder(p->left); //(2)
preorder(p->right); //(3)
}
}
```
>
2024-07-03 09:49:47 +00:00
### 2.广度优先搜索遍历
2021-02-27 15:01:45 +00:00
>
```
//以邻接表为例
void BFS(AGraph *G, int v, int visit[maxSize])
//visit[]数组全部初始化为0
{
ArcNode *p;
int que[maxSize],front=rear=0;
int j;
Visit(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指向出队顶点的第一条边
while(p!=NULL) //将p的所有邻接顶点中未被访问的入队
{
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的下一条边
}
}
}
```
```
//上述是针对连通图的,对于非连通图,只需用一个循环检测图中每个顶点即可
//1.深度优先搜索遍历
void dfs(AGraph *g)
{
int i;
for(i=0;i<g->n;++i) //n是顶点数
{
if(visit[i]==0)DFS(g,i);
}
}
//2.广度优先搜索遍历
void bfs(AGraph *g)
{
int i;
for(i=0;i<g->n;++i)
{
if(visit[i]==0)BFS(g,i,visit);
}
}
```
2024-07-03 09:49:47 +00:00
### 3.例程
2021-02-27 15:01:45 +00:00
```
//1.求不带权无向连通图G距离顶点v最远的一个顶点
//采用广度优先搜索遍历,返回最后一个结点即可
int BFS(AGraph *G,int v)
{
ArcNode *p;
int que[maxSize],front=rear=0;
int visit[maxSize];
int i,j;
for(i=0;i<G->n;++i)visit[i]=0; //初始化visit
rear=(rear+1)%maxSize; //顶点v入队
que[rear]=v;
visit[v]=1;
while(front!=rear)
{
front=(front+1)%maxSize; //出队
j=que[front];
p=G->adjlist[j].firstarc; //p指向出队结点p的第一条边
while(p!=NULL)
{
front=(front+1)%maxSize;
j=que[front];
p=G->adjlist[j].firstarc;
while(p!=NULL)
{
if(visit[p->adjvex]==0)
{
visit[p->adjvex]=1;
rear=(rear+1)%maxSzie;
que[rear]=p->adjvex;
}
p=p->nextarc;
}
}
}
return j; //队空时保存了遍历过程中的最后一个顶点
}
//2.判断无向图G是否是一棵树
//满足树的条件是有n-1条边的连通图,n为图中顶点的个数
void DFS2(AGraph *G,int v,int &vn,int &en)
{
ArcNode *p;
visit[v]=1;
++vn;
p=G->adjvex[v].firstarc;
while(p!=NULL)
{
++en;
if(visit[p->adjvex]==0)DFS2(G,p->adjvex);
p=p->nextarc;
}
}
int GisTree(AGraph *G)
{
int vn=0,en=0,1;
for(i=0;i<G->n;++i)visit[i]=0;
DFS2(G,1,vn,en);
if(vn==G->n&&(G->n-1)==en/2)return 1;
//遍历过程中访问过的顶点数和图中的顶点数相等,且边数等于顶点数减1,则是树
//注意en,每个顶点都算了一次,最后总和相当于边数的两倍,所以要除以2
else return 0;
}
//3.图采用邻接表存储,判断顶点i和顶点j(i!=j)之间是否有路径
//分析:从顶点i开始进行一次深度搜索遍历,遍历过程中遇到j说明i,j有路径
int DFSTrave(AGraph *G, int i, int j)
{
int k;
for(k=0;k<G->n;++n)visit[k]=0; //初始化visit[]数组
DFS(G,i);
if(visit[j]==1)return 1;
else return 0;
}
```
2024-07-03 09:49:47 +00:00
## 三、最小(代价)生成树
2021-02-27 15:01:45 +00:00
>
2024-07-03 09:49:47 +00:00
### 1.普里姆算法
2021-02-27 15:01:45 +00:00
>
```
//形参写成MGraph会使得参数传入因复制了一个较大的变量而变得低效
//用引用型避免函数参数复制
void Prim(MGraph g,int v0,int &sum)
{
int lowcost[maxSize],vset[maxSize],v;
int i,j,min;
v=v0;
for(i=0;i<g.n;++i)
{
lowcost[i]=g.edges[v0][i];//顶点v0的边的权值
vset[i]=0;
}
vset[v0]=1; //将v0并入树中
sum=0; //sum清零用来累计树的权值
for(i=0;i<g.n-1;++i)
{
min=INF; //INF是一个已经定义的比图中所有边权值都大的常量
for(j=0;j<g.n;++j)
{
if(vset[j]==0&&lowcost[j]<min)
{
min=lowcost[j];
k=j;
}
}
vset[k]=1;
v=k;
sum+=min;//sum即是最小生成树的权值
//循环以刚并入的顶点v为媒介更新候选边
for(j=0;j<g.n;++j)
{
if(vset[j]==0&&g.edges[v][j]<lowcost[j])
lowcost[j]=g.edges[v][j];
}
}
}
//时间复杂度O(n^2),普里姆算法适用于稠密图
```
2024-07-03 09:49:47 +00:00
### 2.克鲁斯卡尔算法
2021-02-27 15:01:45 +00:00
>
```
//假设road[]数组中存放了图中各边及其所连接的两个顶点的信息,且排序函数已经存在
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(MGraph g,int &sum,Road road[])
{
int i;
int N,E,a,b;
N=g.n; //顶点数
E=g.e; //边数
sum=0;
for(i=0;i<N;++i)v[i]=i;
sort(road,E); //对并查集数组进行从小到大权值排序
for(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; //此处生成树的权值可以改为其他写法
}
}
}
//克鲁斯卡尔算法时间复杂度主要花费在选取的排序算法上
//排序算法的规模由图的边数e决定
//克鲁斯卡尔算法适用于稀疏矩阵
```
2024-07-03 09:49:47 +00:00
## 四、最短路径
2021-02-27 15:01:45 +00:00
2024-07-03 09:49:47 +00:00
### 1.迪杰斯特拉算法
2021-02-27 15:01:45 +00:00
>
```
void printfPath(int path[],int a)
{
int stack[maxSize],top=-1;
//这个栈以由叶子结点到根结点的顺序将其入栈
while(path[a]!=-1)
{
stack[++top]=a;//先将叶子顶点入栈
a=path[a];
}
stack[++top]=a; //源点
while(top!=-1)
cout<<stack[top--]<<" ";//出栈并打印出栈元素,实现顶点逆序打印
cout<<endl;
}
```
```
//迪杰斯特拉算法
void Dijkstra(MGraph g,int v,int dist[],int path[])
{
int set[maxSize];
int min,i,j,u;
//对各数组初始化
for(i=0;i<g.edges[v][i])
{
dist[i]=g.edges[v][i];
set[i]=0;
if(g.edges[v][i]<INF)path[i]=v;
else path[i]=-1;
}
set[v]=1;path[v]=-1;
//初始化结束
//关键操作
for(i=0;i<g.n-1;++i)
{
min=INF;
//从剩余顶点选取一个顶点,通往这个顶点的路径在通往所有剩余结点中路径最短
for(j=0;j<g.n;++j)
{
if(set[j]==0&&dist[j]<min)
{
u=j;
min=dist[j];
}
}
set[u]=1; //将选出的顶点并入最短路径
//将选出的顶点作为中间点,对所有通往剩余顶点的路径进行检测
for(j=0;j<g.n;++j)
{
//判断顶点u的加入是否会出现通往顶点j的更短的路径
//如果出现,则改变路径长度,否则什么都不做
if(set[j]==0&&dist[u]+g.edges[u][j]<dist[j])
{
dist[j]=dist[u]+g.edges[u][j];
path[j]=u;
}
}
}
//关键操作结束
}
//函数结束dist[]数组存放了v点到其余顶点的最短路径长度
//path[]中存放v点到其余各顶点的最短路径
```
>
2024-07-03 09:49:47 +00:00
### 2.费洛伊德算法
2021-02-27 15:01:45 +00:00
>
```
void Floyd(MGraph g,int Path[][maxSize])
{
int i,j,k;
int A[maxSize][maxSize];
//对A[][]和Path[][]进行初始化
for(i=0;i<g.n;++j)
{
for(j=0;j<g.n;++j)
{
A[i][j]=g.edges[i][j];
Path[i][j]=-1;
}
}
//完成以k为中间点对所有顶点对{ij}进行检测和修改
for(k=0;k<g.n;++k)
{
for(i=0;i<g.n;++i)
{
for(j=0;j<g.n;++j)
{
if(A[i][j]>A[i][k]+A[k][j])
{
A[i][j]=A[i][k]+A[k][j];
Path[i][j]=k;
}
}
}
}
}
```
2024-07-03 09:49:47 +00:00
## 五、拓扑排序
2021-02-27 15:01:45 +00:00
>
```
//拓扑排序
int TopSort(AGraph *G)
{
int i,j,n=0;
int stack[maxSize],top=-1;
ArcNode *p;
//将图中入度为0的顶点入栈
for(i=0;i<G->n;++i)
{
if(G->adjlist[i].count==0)
stack[++top]=i;
}
//关键操作
while(top!=-1)
{
i=stack[top--]; //顶点出栈
++n; //计数器加1统计当前顶点
cout<<i<<" "; //输出当前顶点
p=G->adjlist[i].firstarc;
//将所有由此结点引出的边所指的顶点的入度减小1将这个过程中入度为0的顶点入栈
while(p!=NULL)
{
j=p->adjvex; //该边所指向的结点位置
--(G->adjlist[j].count);
if(G->adjlist[j].count==0)
stack[++top]=j;
p=p->nextarc; //指向下一条边的顶点
}
}
//关键操作结束
if(n==G->n) return 1;
else return 0;
}
```
>
2024-07-03 09:49:47 +00:00
## 六、关键路径
2021-02-27 15:01:45 +00:00
>
2024-07-03 09:49:47 +00:00
## 七、例程
2021-02-27 15:01:45 +00:00
```
//1.判断以邻接表方式存储的有向图中是否存在由顶点vi到顶点vj的路径
//思路广度优先搜索遍历BFS起点为viBFS退出之前遇到vj则证明有路径
int BFS(AGraph *G,int vi,int vj)
{
ArcNode *p;
int que[maxSize],front=rear=0;
int visit[maxSize];
int i,j;
for(i=0;i<G->n;++i)visit[i]=0;
rear=(rear+1)%maxSize;
que[rear]=vi;
visit[vi]=1;
while(front!=rear)
{
front=(front+1)%maxSize;//出队
j=que[front];
if(j==vj)return 1;//找到了顶点vj
p=G->adjlist[j].firstarc; //指向出队顶点的第一条边
while(p!=NULL)
{
if(visit[p->adjvex]==0)
{
rear=(rear+1)%maxSize;
que[rear]=p->adjvex;
visit[p->adjvex]=1;
}
p=p->nextarc; //p指向下一条边
}
}
return 0;
}
```
```
//2.有向图G中如果r到G中的每个结点都有路径可达则称结点r为G的根结点
//判断有向图是否有根
//深度优先搜索遍历DFS以r为起点进行DFS遍历若在函数退出时已经访问所有顶点则r为根
int visit[maxSize],sum; //假设常量maxSize已经定义
void DFS(AGraph *G,int v)
{
ArcNode *p;
visit[v]=1;
++sum;//每访问一个顶点加1
p=G->adjlist[v].firstarc;
while(p!=NULL)
{
if(visit[p->adjvex]==0)
{
DFS(G,p->adjvex);
p=p->nextarc;
}
}
}
void print(AGraph *G)
{
int i,j;
for(i=0;i<G->n;++i)
{
sum=0; //每次选取一个新起点计数器清零
for(j=0;j<G->n;++j)visit[j]=0; //每次进行DFS时访问标记数组清零
DFS(G,i);
if(sum==G->n)cout<<i<<endl; //当图中所有顶点全部被访问时则判断为根,输出
}
}
```