目录
图的概念与相关定义
(不再赘述)
图的存储结构
typedef struct arc
{
int index;//邻接点的编号
int weight;//边的权值
struct arc *next;//指向下一个邻接点
}AR;
typedef struct mygragh
{
int arcnum;//图中边的数量
int vexnum;//图中顶点数量
int type;//图的类型(比如,1表示有向图(网),0表示无向图(网))
AR *N;//为邻接表的表头定义动态数组
char **vexname;//存储顶点名的动态数组
int **A;//邻接矩阵动态数组
}GH;
常见算法
DFS(深度优先搜索)
描述
DFSTraverse(G, v, Visit()); 从顶点v起深度优先遍历图G,并对每个顶点调用函数Visit一次且仅一次。与树的先根遍历很相似。主要侧重于遍历的深度,想法简单,没有考虑距离因素。(废话少说,放码过来)
代码实现
void DFSvisit(GH *G)
{
int i,*visit;
visit=(int *)malloc(G->vexnum*sizeof(int));//
memset(visit,0,G->vexnum*sizeof(int));//
int st=clock();
for(i=0;i<G->vexnum;i++)//
{
if(!visit[i])//只要顶点i还未被访问
DFS(G,visit,i);//以i为起始点对图做DFS遍历
}
printf("\n");//
free(visit);//
printf("DFS:%dms",clock()-st);
}
void DFS(GH *G,int *visit,int index)
{
//邻接表/
AR *p;//
printf("%s ",G->vexname[index]);//访问当前的起始点index
visit[index]=1;//修改其访问标记
p=G->N[index].next;//令p指向index的第一个邻接点
while(p)//只有存在有邻接点
{
if(!visit[p->index])//且当前邻接点还未被访问
DFS(G,visit,p->index);//以当前邻接点为起始点对图继续做DFS的遍历
p=p->next;//接着遍历下一个邻接点
}
邻接矩阵//
/*int i,j;
printf("%s ",G->vexname[index]);
visit[index]=1;
for(i=0;i<G->vexnum;i++)
{
if(G->A[index][i]&&visit[i]==0)
DFS(G,visit,i);
}*/
}
BFS(广度优先搜索)
描述
BFSTraverse(G, v, Visit()); 从顶点v起广度优先遍历图G,并对每个顶点调用函数Visit一次且仅一次。和树的层次遍历很像,考虑了距离因素,同一层次的点到顶点v的距离都是相等的
代码实现
void BFSvisit(GH *G)
{
int i,*visit;//visit是用来做标记的数组,0表示未访问过,1表示访问过,防止重复遍历
visit=(int *)malloc(G->vexnum*sizeof(int));//
memset(visit,0,G->vexnum*sizeof(int));//初始,图中的每个定均未被访问,所以初始值全部为0
int st=clock();
for(i=0;i<G->vexnum;i++)//对图中每个顶点
{
if(!visit[i])//只要该顶点还未被访问
BFS(G,visit,i);//则以该点为起始点,对图做B广度优先遍历
}
printf("\n");
free(visit);//释放 visit数组
printf("BFS:%dms",clock()-st);
}
void BFS(GH *G,int *visit,int index)
{
///邻接表
int *q,front=0,rear=0;//定义队列,以及队列参数
int i;//
AR *p;//
q=(int *)malloc(G->vexnum*sizeof(int));//为队列分配空间
q[rear]=index;//其实元素入队
rear++;//
visit[index]=1;//入队的元素设置为已访问状态,防止重复入队
while(front!=rear)//只要队列非空
{
i=q[front];//读出队头元素
printf("%s ",G->vexname[i]);//输出队头元素对应的顶点名
front++;//
p=G->N[i].next;//令指针p指向当前顶点对应领接表的第一个节点
while(p)//只要当前顶点存在有邻接点
{
if(!visit[p->index])//只要该邻接点还未被访问
{
q[rear]=p->index;//将其入队
rear++;//
visit[p->index]=1;//修改访问标记
}
p=p->next;//再继续遍历下一个邻接点
}
}
free(q);//释放队列数组空间
邻接矩阵
/*int *q,front=0,rear=0;
int i,j;
q=(int *)malloc(G->vexnum*sizeof(int));
q[rear]=index;
rear++;
visit[index]=1;
while(front!=rear)
{
i=q[front];
front++;
printf("%s ",G->vexname[i]);
for(j=0;j<G->vexnum;j++)
{
if(G->A[i][j]&&visit[j]==0)
{
q[rear]=j;
rear++;
visit[j]=1;
}
}
}
free(q);*/
}
TIPS
BFS和DFS运行结束后记得释放无用的空间:例如visit和q
DFS与BFS的应用
寻找两点之间的所有路径(DFS)
算法描述这与DFS十分相似,就是将起点的visitv保持为1,在递归时先令visit为1,再恢复为0,以便其余路线可以经过该可行点
void findpath(GH *G,char *start,char *end)
{
int i,j;
int *path;//存路径结点的序号
int *visit;//标志状态
i=findvex(start,G);
j=findvex(end,G);
path=(int *)malloc(G->vexnum*sizeof(int));
path[0]=i;//第一个结点加入路径
visit=(int *)malloc(G->vexnum*sizeof(int));
memset(visit,0,G->vexnum*sizeof(int));
visit[i]=1;//将起点标位不可行
allpath(G,i,j,path,1,visit,0);
}
void allpath(GH *G,int i,int j,int *path,int n,int *visit,int sum)
{
int k;
AR *p;
if(i==j)
{
for(k=0;k<n;k++)
{
printf("%s ",G->vexname[path[k]]);
}
printf("路径长度为:%d",sum);
printf("\n");
}
else
{
p=G->N[i].next;//找到下一个可行点
while(p)//对所有邻接点尝试访问
{
if(visit[p->index]==0)//如果该点未访问过
{
path[n]=p->index;//将该点的序号加入路径
visit[p->index]=1;//加入路径以后该点被访问过
allpath(G,p->index,j,path,n+1,visit,sum+p->weight);
visit[p->index]=0;
}
p=p->next;//尝试下一个邻接点
}
}
}
寻找所有回路(DFS)
这又与上面的寻找所有路径相似,只是将起点(也就是终点)的visit设为0,以便它能够作为终点被再次选中。
void findcycle(GH *G,char *start)
{ //找回路,深度优先搜索,起点的visit始终设为0,每次递归完成后都会把1的visit还原为0以找到所有通路
int top=-1,sum=0;
int *path=(int *)malloc(sizeof(int)*G->vexnum);
int stid=findvex(start,G);
int *visit=(int *)malloc(sizeof(int)*G->vexnum);
path[++top]=stid;
memset(visit,0,sizeof(int)*G->vexnum);
circle(G,stid,path,top,visit,&sum);
if(!sum)
printf("没有回路\n");
}
void circle(GH *G,int start,int *path,int top,int *visit,int *sum)
{
int i;
if(start==path[top]&&top>2)
{ //注意这里要判断该回路的长度要至少为3,2有可能只是起点与任意相邻点之间的一去一回
(*sum)++;
for(i=0;i<=top;i++)
printf("%s ",G->vexname[path[i]]);
printf("\n");
}
else
{
AR *q=G->N[path[top]].next;
while(q)
{
if(visit[q->index]==0)
{
path[top+1]=q->index;
visit[q->index]=1;
circle(G,start,path,top+1,visit,sum);
visit[q->index]=0;
}
q=q->next;
}
}
判断图是否联通(DFS)
调用DFS的过程中在主调函数里面加一个记录联通分支数的变量,每进行一次DFS,联通分支数就加一,最后判断联通分支数是否为1即可。
判断无向图中是否有回路(DFS)
同样地记下联通分支数,如果联通分支数+点数>边数,则有回路,否则无回路
无向图中的最短路径(BFS)
从起点开始调用BFS,直到终点入队
typedef struct node
{
int index,pa;//该点的序号和其前驱的序号
}QU;
void findmin(GH *G,char *start,char *end)
{
QU *q=(QU *)malloc(sizeof(QU)*G->vexnum);
int *path=(int *)malloc(sizeof(int)*G->vexnum);
int *visit=(int *)malloc(sizeof(int)*G->vexnum);
memset(visit,0,sizeof(int)*G->vexnum);
AR *p;
int rear=-1,front=-1;
q[++rear].index=findvex(G,start);
q[rear].pa=front;
while(front!=rear)
{
p=G->N[q[front+1].index].next;
while(p)
{
if(visit[p->index]==0)
{
q[++rear].index=p->index;
q[rear].pa=front+1;
visit[p->index]=1;
if(strcmp(G->vexname[p->index],end)==0)
break;
}
p=p->next;
}
if(!p) front++;
else break;
}
front=-1;
int k=q[rear].pa;
path[++front]=q[rear].index;
while(k!=-1)
{
path[++front]=q[k].index;
k=q[k].pa;
}
for(k=front;k>=0;k--)
{
printf("%s ",G->vexname[path[k]]);
}
printf("\n");
}
无向图中距离起点最远的那个点(BFS)
从起点开始进行BFS,最后入队的那个点
int findfar(GH *G,char *start)
{
int *t=(int *)malloc(sizeof(int)*G->vexnum);
int *visit=(int*)malloc(sizeof(int)*G->vexnum);
memset(visit,0,sizeof(int)*G->vexnum);
int front=-1,rear=-1;
t[++rear]=findvex(G,start);
visit[rear]=1;
AR p,*q;
while(front!=rear)
{
p=G->N[t[front+1]];
q=p.next;
while(q)
{
if(!visit[q->index])
{
t[++rear]=q->index;
visit[q->index]=1;
}
q=q->next;
}
front++;
}
return t[rear];
}
tips
所有与距离有关的都可以用BFS来实现,可将BFS想象成一颗树在渐渐生长,这样最长最短路径也就可以比较容易地被解释了。
最小生成树
Prim
算法描述
取图中任意一个顶点 v 作为生成树的根,之后往生成树上添加新的顶点 w。在添加的顶点 w 和已经在生成树上的顶点v 之间必定存在一条边,并且该边的权值在所有连通顶点 v 和 w 之间的边中取值最小。之后继续往生成树上添加顶点,直至生成树上含有 n-1 个顶点为止。
时间复杂度为O(n^2),适用于稠密图
手动实现
Kruskal
算法描述
考虑问题的出发点: 为使生成树上边的权值之和达到最小,则应使生成树中每一条边的权值尽可能地小。
具体做法: 先构造一个只含 n 个顶点的子图 SG,然后从权值最小的边开始,若它的添加不使SG 中产生回路,则在 SG 上加上这条边,如此重复,直至加上 n-1 条边为止
时间复杂度为O(eloge),适用于稀疏图
手动实现
####最小生成树程序实现
课程设计
拓扑排序
算法描述
从有向图中选取一个没有前驱的顶点,并输出之;从有向图中删去此顶点以及所有以它为尾的弧;重复上述两步,直至图空,或者图不空但找不到无前驱的顶点为止
代码实现
(考试要求手写)
int TopologicalOrder(GH *G,int *t)//拓扑排序
{
int tops=-1;
int topt=-1;
int k;
AR *p;
int *du=(int *)malloc(sizeof(int)*G->vexnum);
int *st=(int *)malloc(sizeof(int)*G->vexnum);
memset(du,0,sizeof(int)*G->vexnum);
memset(st,0,sizeof(int)*G->vexnum);
//初始化度
for(int i=0;i<G->vexnum;i++)
{
p=G->N[i].next;
while(p)
{
du[p->index]++;
p=p->next;
}
}
//源点入zhai
for(int i=0;i<G->vexnum;i++)
{
if(du[i]==0)
{
st[++tops]=i;
break;
}
}
//拓扑排序
while(tops>=0)
{
k=st[tops];
tops--;
t[++topt]=k;
p=G->N[k].next;
while(p)
{
du[p->index]--;
if(du[p->index]==0)
{
st[++tops]=p->index;
}
p=p->next;
}
}
if(topt+1==G->vexnum) return 1;
else return 0;
}
AOE网
关键路径课设
最短路径
DIJKSTAR
算法描述
路径长度最短的最短路径的特点:在这条路径上,必定只含一条弧,并且这条弧的权值最小。
下一条路径长度次短的最短路径的特点:它只可能有两种情况:或者是直接从源点到该点(只含一条弧); 或者是从源点经过顶点v1,再到达该顶点(由两条弧组成)。
再下一条路径长度次短的最短路径的特点它可能有三种情况:或者是直接从源点到该点(只含一条弧); 或者是从源点经过顶点v1,再到达该顶点(由两条弧组成);或者是从源点经过顶点v2,再到达该顶点。
其余最短路径的特点:它或者是直接从源点到该点(只含一条弧); 或者是从源点经过已求得最短路径的顶点,再到达该顶点。
求最短路径的迪杰斯特拉算法:
设置辅助数组Dist,其中每个分量Dist[k] 表示 当前所求得的从源点到其余各顶点 k 的最短路径。
一般情况下,Dist[k] = <源点到顶点 k 的弧上的权值>或者 = <源点到其它顶点的路径长度>+ <其它顶点到顶点 k 的弧上的权值>
(1)在所有从源点出发的弧中选取一条权值最小的弧,即为第一条最短路径
(2)修改其它各顶点的Dist[k]值。假设求得最短路径的顶点为u,若 Dist[u]+G.arcs[u][k]<Dist[k]则将 Dist[k] 改为 Dist[u]+G.arcs[u][k]。
具体的实现方法实际上和我们最开始的分析是一致的,只不过每次比较起点到终点的连线距离时,已经提前将该数存入Dist
代码实现
typedef struct arc
{
int index,weight;
struct arc *next;
}AR;
typedef struct MyGraph
{
int type;//0,表示无向网,1表示有向网
int arcnum,vexnum;
char **vexname;
AR *N;
int **A;
}GH;
void dijkstra(GH *G,int start)
{
int k;
int *dis=(int *)malloc(sizeof(int)*G->vexnum);
int *path=(int *)malloc(sizeof(int)*G->vexnum);
int *flag=(int *)malloc(sizeof(int)*G->vexnum);
memset(flag,0,sizeof(int)*G->vexnum);
//初始化
for(int i=0;i<G->vexnum;i++)
{
dis[i]=G->A[start][i];
if(dis[i]<MAX) path[i]=start;
else path[i]=-1;
}
flag[start]=1;
for(int i=0;i<G->vexnum-1;i++)
{
k=findmin(G->vexnum,flag,dis);
if(k>0)
{
flag[k]=1;
for(int h=0;h<G->vexnum;h++)
{
if(flag[h]==0 && dis[h]>dis[k]+G->A[k][h])
{
dis[h]=dis[k]+G->A[k][h];
path[h]=k;
}
}
}
}
showpath1(G->vexnum,flag,path,dis,start);
free(dis);
free(path);
free(flag);
}
void showpath1(int n,int *flag,int *path,int *dis,int start)
{
for(int i=0;i<n;i++)
{
if(i!=start)
{
int k=i;
if(path[k]==-1)
printf("v[%d]->v[%d]:no path\n",start,k);
else
{ int *route=(int *)malloc(sizeof(int)*n);
int ind=-1;
while(k!=start)
{
route[++ind]=k;
k=path[k];
}
printf("v[%d]",start);
for(int h=ind;h>=0;h--) printf("->v[%d]",route[h]);
printf("\tlen:%d\n",dis[i]);
}
}
}
}
FLOYD
算法描述
从 vi 到 vj 的所有可能存在的路径中,选出一条长度最短的路径
若<vi,vj>存在,则存在路径{vi,vj} // 路径中不含其它顶点
若<vi,v1>,<v1,vj>存在,则存在路径{vi,v1,vj}// 路径中所含顶点序号不大于1
若{vi,…,v2}, {v2,…,vj}存在,则存在一条路径{vi, …, v2, …vj}// 路径中所含顶点序号不大于2
依次类推,则 vi 至 vj 的最短路径应是上述这些路径中,路径长度最小者。
代码实现
void floyd(GH *G)
{
int **P=(int **)malloc(sizeof(int *)*G->vexnum);
int **D=(int **)malloc(sizeof(int *)*G->vexnum);
for(int i=0;i<G->vexnum;i++)
{
P[i]=(int *)malloc(sizeof(int)*G->vexnum);
D[i]=(int *)malloc(sizeof(int)*G->vexnum);
for(int j=0;j<G->vexnum;j++)
{
D[i][j]=G->A[i][j];
if(D[i][j]<MAX) P[i][j]=i;
else P[i][j]=-1;
}
}
for(int k=0;k<G->vexnum;k++)
{
for(int i=0;i<G->vexnum;i++)
{
for(int j=0;j<G->vexnum;j++)
{
if(D[i][j]>D[i][k]+D[k][j])
{
D[i][j]=D[i][k]+D[k][j];
P[i][j]=k;
}
}
}
}
printf("WAY 2:\n");
for(int i=0;i<G->vexnum;i++)
{
for(int j=0;j<G->vexnum;j++)
{
if(P[i][j]!=-1&&i!=j)
{
printf("len:%d :",D[i][j]);
printf("v[%d] ",i);
showpath3(G->vexnum,D,P,i,j);
printf("\n");
}
else if(P[i][j]==-1 && i!=j)
{
printf("no path:v[%d]->v[%d]\n",i,j);
}
}
}
free(P);
free(D);
}
void showpath3(int n,int **D,int **P,int st,int ed)
{
if(st==P[st][ed])
printf("->v[%d]",ed);
else
{
showpath3(n,D,P,st,P[st][ed]);
showpath3(n,D,P,P[st][ed],ed);
}
}
Floyd算法应用
(这字实在难看,我自己都嫌弃)