本文将介绍图论相关算法,难度较大,如果有写的不对的地方欢迎评论区探讨
本文只讲图论算法,基础知识略过不讲
由于图论算法书上的代码可读性以及可执行性不高,故本文会将这些算法一一实现,长度会比较长,这是最伟大的一集(^ _ ^)这些算法仅使用一个测试点进行测试,如果大家写的时候遇到了错误,请反馈给我
一、图的存储与创建(书上的有点啰嗦,不简洁,不适宜考试中写)
1.邻接矩阵
typedef struct
{
int mat[50][50];//矩阵
int vexnum,arcnum;//顶点数,边数
}graph;//图的定义,这个存储方式比书上简化多了,考试大多是int类型,所以此定义是对于考试来说非常方便的
2.邻接表
typedef struct arcnode
{
int num;
struct arcnode *next;
}arcnode;//中间点
typedef struct vernode
{
int data;
arcnode *firstarc;
}vernode;//顶点
typedef struct
{
vernode ver[50];
int vexnum,arcnum;//顶点数,边数
}graph;
3.邻接矩阵的创建,课本217页算法7.1
有两种方法,一种是直接输入一个矩阵,此种方法非常简单,按照输入一般的矩阵就可以输入进去了
第二种是输入两个顶点及边的权值,如果这种方法,也不是很难
void creategraph(graph &g)//此处是c++写法,传的引用
{
int i,j;
scanf("%d %d",&g.vexnum,&g.arcnum);
for(i=1;i<=g.vexnum;i++)
{
g.ver[i]=i;
for(j=1;j<=g.vexnum;j++)
{
g.mat[i][j]=(i==j)?0:32767;//初始化,同一点则赋0,不是一个点则赋32767(一个特别大的数,表示不可达,32767是int类型上限,书上是INFINTY)
}
}
int num1,num2,weigh;
for(i=1;i<=g.arcnum;i++)
{
scanf("%d %d %d",&num1,&num2,&weigh);
g.graph[num1][num2]=weigh;
g.graph[num2][num1]=weigh;//有向网就没有这句了
}
}
4.邻接表的创建(此算法课本好像没有?)
//需要先输入n(顶点的个数)
for(i=1;i<=n;i++)
{
scanf("%d",&num);
g.ver[i].data=num;
visited[i]=0;//全局变量,方便后面做遍历
g.ver[i].firstarc=NULL;//初始化,千万要做
}
while(1)
{
scanf("%d %d",&a,&b);
if(a==-1&&b==-1)break;//结束条件
arcnode *p=(arcnode*)malloc(sizeof(arcnode));
p->num=b;
p->next=g.ver[a].firstarc;//头插法建立
g.ver[a].firstarc=p;
}
十字链表存储方法考试不考,此处不讲
二、图的遍历(225页的算法不讲,因为它只是思路,实现不了)
1.深度优先(递归,课本226页7.5-7.6)
1.1邻接矩阵实现的
void dfs(graph g,int v0)
{
printf("%d ",v0);
visited[v0]=1;//全局数组
int i;
for(i=0;i<g.vexnum;i++)
{
if(!visited[i] && g.mat[v0][i]==1)
dfs(g,i);
}
}
1.2邻接表实现的
void DFS(graph *g,int n)
{
arcnode *p;
int i;
visited[n]=1;
printf("%d ",g->ver[n].data);
p=g->ver[n].firstarc;
while(p)
{
i=p->num;
if(!visited[i])
DFS(g,i);//递归
p=p->next;//按深度遍历就是一条路走到黑~
}
}
1.3非递归形式的DFS,226页2.7
void push(stack *s,int n)//栈的操作
{
s->a[++(s->top)]=n;
}
int pop(stack *s)
{
int n=s->a[(s->top)--];
return n;
}
int visited[50];
void DFS(graph g,int v0)
{
stack s;
s.top=-1;//栈的初始化,对应InitStack函数
push(&s,v0);
int v,w;
arcnode *temp;//
while(s.top!=-1)
{
v=pop(&s);
if(!visited[v])
{
cout<<v<<" ";
visited[v]=1;
temp=g.ver[v].firstarc;
if(temp)//判断邻接顶点是否为空,是则求出
w=g.ver[v].firstarc->num;
else
w=-1;//否则赋值-1
while(w!=-1)
{
if(!visited[w])
push(&s,w);
if(temp->next)
w=temp->next->num;//同上
else
w=-1;
temp=temp->next;
}
}
}
}
2.广度优先遍历(228页7.8)
typedef struct
{
int arr[50];
int front;
int rear;
}seqquene;
void init(seqquene *a)
{
a->front=a->rear=0;
}
void enter(seqquene *q,int x)
{
q->arr[q->rear]=x;
q->rear=(q->rear+1)%50;
}
int del(seqquene *q)
{
int x=q->arr[q->front];
q->front=(q->front+1)%50;
return x;
}
int visited[50];
void BFS(graph g,int v0)
{
cout<<v0<<" ";
visited[v0]=1;
seqquene q;
init(&q);//队列的初始化,对应InitStack函数
enter(&q,v0);
int v,w;
arcnode *temp;//
while(q.rear!=q.front)
{
v=del(&q);
temp=g.ver[v].firstarc;
if(temp)//判断邻接顶点是否为空,是则求出
w=g.ver[v].firstarc->num;
else
w=-1;//否则赋值-1
while(w!=-1)
{
if(!visited[w])
{
cout<<w<<" ";
visited[w]=1;
enter(&q,w);
}
if(temp->next)
w=temp->next->num;//同上
else
w=-1;
temp=temp->next;
}
}
}
三、图的应用
1.简单路径,231页7.9
int pre[50]={-1};
void printpath(int n)
{
int path[50];//存储路径的数组
int pathIndex = 0;//路径数组的索引
//从目标顶点开始,逆向追踪到起始顶点
for(int current=n;current!=-1;current=pre[current])
{
path[pathIndex++]=current;//将顶点添加到路径数组中
}
//打印路径,路径是逆序的,所以需要反转打印
for(int i=pathIndex-1;i>=0;i--)
{
cout<<path[i]<<" ";
}
cout<<endl;
}
void DFS(graph *g,int u,int v)
{
int w;
if(u==v)
{
printpath(v);
return;
}
arcnode *p=g->ver[u].firstarc;
if(p)
w=g->ver[v].firstarc->num;
else
w=-1;
while(w!=-1)
{
if(pre[w]==-1)
{
pre[w]=u;
DFS(g,w,v);
}
if(p->next)
w=p->next->num;
else
w=-1;
p=p->next;
}
}
注:没有写主函数的邻接表实现的操作,主函数均为以下的
int main()
{
graph g;
int i,a,b,num,n;
cin>>n;
for(i=1;i<=n;i++)
{
scanf("%d",&num);
g.ver[i].data=num;
pre[i]=-1;//全局变量,方便后面做遍历
g.ver[i].firstarc=NULL;//初始化,千万要做
}
while(1)
{
scanf("%d %d",&a,&b);
if(a==0&&b==0)break;//结束条件
arcnode *p=(arcnode*)malloc(sizeof(arcnode));
p->num=b;
p->next=g.ver[a].firstarc;//头插法建立
g.ver[a].firstarc=p;
}
DFS(&g,1,4);
return 0;
}
2.最小生成树,prim算法,233页算法7.10
#include <iostream>
using namespace std;
typedef struct
{
int ver[100];
int graph[100][100];
int vexnum,arcnum;
}Graph;//
typedef struct
{
int adjvex;
int low;
}closedge;
int all=0;//最小生成树的权值之和
void creategraph(Graph &g)//生成图的算法,前面讲过了
{
int i,j;
scanf("%d %d",&g.vexnum,&g.arcnum);
for(i=1;i<=g.vexnum;i++)
{
g.ver[i]=i;
for(j=1;j<=g.vexnum;j++)
{
g.graph[i][j]=(i==j)?0:32767;
}
}
int num1,num2,weigh;
for(i=1;i<=g.arcnum;i++)
{
scanf("%d %d %d",&num1,&num2,&weigh);
g.graph[num1][num2]=weigh;
g.graph[num2][num1]=weigh;
}
}
int mini(Graph &g,closedge *s)
{
int i,min,loc=-1;
min=32767;
for(i=1;i<=g.vexnum;i++)
{
if(min>s[i].low && s[i].low!=0)//选最小边
{
min=s[i].low;
loc=i;
}
}
return loc;
}
void prim(Graph &g,int n)
{
int i,j,k;
closedge s[100];
s[1].low=0;
for(i=2;i<=g.vexnum;i++)//初始化
{
s[i].adjvex=n;
s[i].low=g.graph[n][i];
}
cout<<"1";
for(k=1;k<g.vexnum;k++)
{
j=mini(g,s);//选取当前最小边
all+=s[j].low;//最小边权值加入到最小生成树的总权值中
printf(" %d",j);//输出边的信息
s[j].low=0;//
for(i=1;i<=g.vexnum;i++)
{
if(g.graph[j][i]<s[i].low)//更新边的信息
{
s[i].low=g.graph[j][i];
s[i].adjvex=j;
}
}
}
}
int main()
{
Graph g;
creategraph(g);
prim(g,1);
cout<<endl<<all;
return 0;
}
3.拓扑排序,239页7.11 7.12
#include <iostream>
#include <stack>
using namespace std;
int a[50];
int n=0;
typedef struct arcnode
{
int adjvex;
struct arcnode *next;
}arcnode;
typedef struct vernode
{
int data;
arcnode *first;
}vernode;
typedef struct
{
vernode ver[50];
int vexnum,arcnum;
}graph;
void findid(graph &g,int indegree[])//求入度
{
int i;
arcnode *p;
for(i=0;i<g.vexnum;i++)//初始化入度数组
indegree[i]=0;
for(i=0;i<g.vexnum;i++)
{
p=g.ver[i].first;//求每个顶点的入度
while(p)
{
indegree[p->adjvex]++;//p指向的点入度+1,不停循环
p=p->next;
}
}
}
void topsort(graph &g)
{
stack<int> s;//此处偷懒了,调用stl,如果考试的时候不让用请使用前面栈操作来替换
int indegree[50];
int i,cnt,k;
arcnode *p;
findid(g,indegree);//求入度
for(i=0;i<g.vexnum;i++)
{
if(!indegree[i])//入度为0则进栈
s.push(i);
}
cnt=0;
while(!s.empty())
{
i=s.top();
a[n++]=i;//先存到数组中,一会一起输出
s.pop();//出栈
cnt++;
p=g.ver[i].first;
while(p)
{
k=p->adjvex;
indegree[k]--;//删掉节点对应的边
if(!indegree[k])//入度为0就进栈
s.push(k);
p=p->next;
}
}
if(cnt < g.vexnum)
cout<<"ERROR";
else
{
for(i=0;i<n;i++)
cout<<a[i]<<" ";
}
}
int main()
{
graph g;
int i,j,n;
arcnode *p,*q;
cin>>g.vexnum;
for(i=0;i<g.vexnum;i++)//初始化
{
g.ver[i].first=NULL;
g.ver[i].data=i;
}
//此处是输入的邻接矩阵,但存储方式使用邻接表
for(i=0;i<g.vexnum;i++)//尾插法创建图
{
for(j=0;j<g.vexnum;j++)
{
cin>>n;
if(n && g.ver[i].first==NULL)
{
p=new arcnode;
p->adjvex=j;
g.ver[i].first=p;
p->next=NULL;
}
else if(n)
{
q=g.ver[i].first;
while(q->next)//尾插法,找到最后
q=q->next;
p=new arcnode;
q->next=p;
p->next=NULL;
p->adjvex=j;
}
}
}
topsort(g);
return 0;
}
关键路径不考此处略去
4.迪杰斯特拉最短路径,248页7.15,全书最想吐槽的算法,此算法书上的可读性极差(个人感觉),addtail算法根本不给,path的定义也不明确。。。
#include <iostream>
using namespace std;
typedef struct
{
int mat[50][50];//矩阵
int vexnum,arcnum;//顶点数,边数
}graph;
int dist[50],path[50][50];//dist是最短路径的长度,path是最短路径的路径,其实这两个就是个整型数组,不要被书上的定义吓到了
int member(int n,int s[])
{
int i;
for(i=0;i<50;i++)
{
if(s[i]==n)
return 1;
}
return 0;
}
void addtail(int path[][50],int v0,int v)
{
int i=0;
for(i=0;path[v0][i]!=-1;i++);//注意这里的分号,别抄错了
path[v0][i]=v;
}
void dij(graph g,int v0)//其余参数以全局变量的形式带入
{
int s[50];//最短路径点的集合,其实也是个整型数组
int i,j,k,min;
int scount=0;//控制s集合的计数器
for(i=0;i<g.vexnum;i++)
{
//path数组在主函数已经初始化过了
dist[i]=g.mat[v0][i];
if(dist[i]<32767)
{
path[i][0]=v0;
addtail(path,i,i);
}
}
for(i=0;i<50;i++)//
{
s[i]=-1;
}
s[scount++]=v0;//纳入s集合,表示已经访问过了,类似visited数组
for(i=0;i<g.vexnum;i++)
{
min=32767;
for(j=0;j<g.vexnum;j++)
{
if(!member(j,s) && dist[j]<min)
{
k=j;
min=dist[j];
}
}
if(min==32767)return;
s[scount++]=k;
for(j=0;j<g.vexnum;j++)
{
if(!member(j,s) && g.mat[k][j]!=32767 && (dist[k]+g.mat[k][j]<dist[j]))//权值小就改变,类似prim算法
{
dist[j]=dist[k]+g.mat[k][j];
for(int m=0;path[k][m]!=-1;m++)
{
addtail(path,j,path[k][m]);//把对应的节点路径上都添加此节点
}
addtail(path,j,k);
}
}
}
}
int main()
{
graph g;
int i,j,v0;//v0是源点
cin>>g.vexnum;
cin>>v0;
for(i=0;i<g.vexnum;i++)
{
for(j=0;j<g.vexnum;j++)
{
cin>>g.mat[i][j];
if(g.mat[i][j]==0)
g.mat[i][j]=32767;//初始化为不可达
}
}
for(i=0;i<50;i++)
{
for(j=0;j<50;j++)
path[i][j]=-1;//初始化
}
dij(g,v0);
for(i=0;i<g.vexnum;i++)
{
if(dist[i]==32767)
{
cout<<"Vertex "<<i<<" is unreachable from "<<v0<<endl;
}
else
{
cout<<"Shortest distance from "<<v0<<" to "<<i<<" is "<<dist[i]<<endl;
cout<<"Path: "<<v0;
for(int step=0;path[i][step]!=-1;step++)
{
cout<<" -> "<<path[i][step];//输出路径
}
cout<<endl;
}
}
return 0;
}
5.弗洛伊德最短路径,251页7.16
下面的代码是基于这道题实现的
#include <iostream>
using namespace std;
typedef struct
{
int vexnum,edgenum;
int matrix[50][50];
}graph;
int dist[50][50];
int path[50][50];
void flyd(graph g)
{
int i,j,k;
for(i=0; i<g.vexnum; i++)
{
for(j=0; j<g.vexnum; j++)
{
dist[i][j]=g.matrix[i][j];//初始化
if(i != j && path[i][j] != 9999)
path[i][j]=j;
}
path[i][i]=0;
dist[i][i]=0;
}
for(k=0; k<g.vexnum; k++)
{
for(i=0; i<g.vexnum; i++)
{
for(j=0; j<g.vexnum; j++)
{
if(dist[i][k] == 9999 || dist[k][j] == 9999)//这句话是为了减少后面判断的
continue;
if(dist[i][j] > (dist[i][k]+dist[k][j]))
{
dist[i][j]=dist[i][k]+dist[k][j];
path[i][j]=path[i][k];//更新路径
}
}
}
}
for(i=0;i<g.vexnum;i++)
{
for(j=0;j<g.vexnum;j++)
{
cout<<"from "<<i<<" to "<<j<<": dist = "<<dist[i][j];
k=i;
cout<<" path:";
while(k!=j)
{
cout<<k<<" ";
k=path[k][j];
}
cout<<j<<" "<<endl;
}
}
}
int main()
{
int i,j;
graph g;
cin>>g.vexnum;
for(i=0;i<g.vexnum;i++)
{
for(j=0;j<g.vexnum;j++)
{
cin>>g.matrix[i][j];
}
}
flyd(g);
return 0;
}
本系列文章到此结束,由于查找和排序书上的代码可抄性非常高,我就不再翻译了,大家对算法有什么不理解的地方欢迎评论,实在不理解的就请先记下来,因为我写的这些代码真的能跑。
图论这一篇文章是最难写的,因为好多操作书上没有给,不管怎么说,我还是总结了一遍,希望对大家的期末复习有所帮助,感谢大家的阅读。