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

548 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 原创
第七章 图
# 第七章 图
# 一、图的存储结构
>
## 1.邻接矩阵
>
## 2.邻接表
>
## 3.邻接多重表(有点看不懂…)
>
# 二、图的遍历
## 1.深度优先搜索遍历
>
```
//以邻接表存储结构为例
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)
}
}
```
>
## 2.广度优先搜索遍历
>
```
//以邻接表为例
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);
}
}
```
## 3.例程
```
//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;
}
```
# 三、最小(代价)生成树
>
## 1.普里姆算法
>
```
//形参写成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),普里姆算法适用于稠密图
```
## 2.克鲁斯卡尔算法
>
```
//假设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决定
//克鲁斯卡尔算法适用于稀疏矩阵
```
# 四、最短路径
## 1.迪杰斯特拉算法
>
```
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点到其余各顶点的最短路径
```
>
## 2.费洛伊德算法
>
```
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;
}
}
}
}
}
```
# 五、拓扑排序
>
```
//拓扑排序
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;
}
```
>
# 六、关键路径
>
# 七、例程
```
//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; //当图中所有顶点全部被访问时则判断为根,输出
}
}
```