4.图
4.1 图的遍历
1.求不带权的无向连通图G中距离顶点V最远的一个顶点
可利用广度优先遍历 返回最后一个顶点即可
int BFS(AGraph *G,int v)
{
ArcNode *p;
int que[maxsize],front,rear;
int visit[maxsize];
front = rear = 0;
int i,j;
for(i=0;i<G->n;i++) visit[i] = 0;
rear = (rear+1)%maxsize;
que[rear] = v;
visit[v] = 1;
while(rear!=front)
{
front = (front+1)%maxsize;
j = que[front];
p = G->adjlist[j].firstarc;
while(p)
{
if(visit[p->adjvex]==0)
{
visit[p->adjvex] = 1;
rear = (rear+1)%maxsize;
que[rear] = p->adjvex;
}
p = p->nextarc;
}
}
return j;
}
2.判断无向图G是否是一棵树
若边数有n-1 则是 否则 不是
//借助DFS实现
int visit[maxsize];
void DFS(AGraph *G,int v,int &vn,int &en)
{
ArcNode *p;
visit[v] = 1;
vn++; //顶点数加一
p = G->adjlist[v].firstarc;
while(p)
{
en++; //边数加一 这里边数的统计结果是实际边数的两倍
if(visit[p->adjvex]==0)
DFS(G,v,vn,en);
p = p->nextarc;
}
}
int isTree(AGraph *G)
{
int i,vn,en;
vn = en = 0;
for(i=0;i<G->n;i++) visit[i] = 0;
for(i=0;i<G->n;i++) //深度优先遍历来统计图的边数和个数
{
DFS(G,i,vn,en);
}
if(vn == G->n && en/2 == G->n-1)
return 1;
return 0;
}
4.2 图的存储
4.2.1 邻接表存储
1.用邻接表储存无向图算法
--有向图时也是一样 只不过边结点只插入一次即可
//用邻接表储存无向图算法--有向图时也是一样 只不过边结点只插入一次即可
void makeAGraph(AGraph *g)
{
int i,j,k,n,e;
cin>>n>>e; //输入n个顶点和n条边
int v1,v2; //两个顶点下标
//输入顶点信息 建立顶点向量
for(i=0;i<n;i++)
{
cin>>g->adjlist[i].vertex;
g->adjlist[i].firstarc = NULL;
}
//输入边信息 建立边向量
for(k=0;k<e;k++)
{
cin>>v1>>v2; //输入一条边的两个顶点
i = 0;
while(g->adjlist[i].vertex != v1) i++; //查找v1在顶点表中的下标
j = 0;
while(g->adjlist[j].vertex != v2) j++; //查找v2在顶点表中的下标
//申请边结点
ArcNode *p;
p = (ArcNode *)malloc(sizeof(ArcNode)); //将结点联入
p->adjvex = j; p->nextarc = g->adjlist[i].firstarc; g->adjlist[i].firstarc = p;
p = (ArcNode *)malloc(sizeof(ArcNode));
p->adjvex = i; p->nextarc = g->adjlist[j].firstarc; g->adjlist[j].firstarc = p;
}
}
2.有向图的反向邻接表的生成
void deverseGraph(AGprph *g,AGprph *rg)
{
int i,j;
for(i=0;i<g->n;i++) //顶点表的复制
{
rg->adjlist[i].vertex = g->adjlist[i].vertex;
rg->adjlist[i].firstarc = NULL;
}
ArcNode *p,*s;
for(i=0;i<g->e;i++)
{
p = g->adjlist[i].firstarc;
while(p)
{
s = (ArcNode*)malloc(sizeof(ArcNode));
s->adjvex = i;
j = p->adjvex;
s->nextarc = rg->adjlist[j].firstarc;
rg->adjlist[j].firstarc = s;
p = p->next;
}
}
}
4.2.2 邻接矩阵与邻接表转换
1.邻接矩阵转化成邻接表
void AdjMatrixToAdjList( AdjMatrix gm, AdjList gl )
//将图的邻接矩阵表示法转换为邻接表表示法。
{
for (i=1;i<=n;i++) //邻接表表头向量初始化。
{
scanf(&gl[i].vertex);
gl[i].firstarc=null;
}
for (i=1;i<=n;i++)
for (j=1;j<=n;j++)
if (gm[i][j]==1)
{
p=(ArcNode *)malloc(sizeof(ArcNode)) ;//申请结点空间。
p->adjvex=j;//顶点 I 的邻接点是 j
p->next=gl[i].firstarc;
gl[i].firstarc=p; //链入顶点 i 的邻接点链表中
}
}
2.邻接表转化成邻接矩阵
void AdjListToAdjMatrix(AdjList gl, AdjMatrix gm)
//将图的邻接表表示转换为邻接矩阵表示。
{
for (i=1;i<=n;i++) //设图有 n 个顶点,邻接矩阵初始化。
for (j=1;j<=n;j++)
gm[i][j]=0;
for (i=1;i<=n;i++)
{p=gl[i].firstarc; //取第一个邻接点。
while (p!=null)
{
gm[i][p->adjvex]=1;
p=p->next;
}//下一个邻接点
}
}
4.3 图的顶点和边
4.3.1 图的顶点(度)
1.已知有向图 求各顶点的入度数 (邻接表 邻接矩阵实现)
//求顶点i的入度
int inDegree(AGraph *g,int i)
{
int j,num;
ArcNode *p;
num = 0;
for(j=0;j<g->n;j++) //遍历整个邻接表
{
if(i!=j)
{
p = g->adjlist[j];
while(p)
{
if(p->adjvex==i) num++;
p = p->nextarc;
}
}
}
return num;
}
void AllDegree(AGraph *g)
{
int i,res;
for(i=0;i<g->n;i++)
{
res = inDegree(g,i);
cout<<i<<":"<<res<<endl;
}
}
2.无向图 求所有顶点的度
3.删除某个顶点(有向图)
//删除结点值为key的结点的算法
//除了删除该顶点外 还要删除相关的边
void deleteNode(AGraph *g,int key)
{
//查找key顶点下标
int i,j;
for(i=0;g->adjlist[i].vertex!=key;i++);
ArcNode *p,*q,*u;
p = g->adjlist[i].firstarc;
//删除所有的出度边
while(p)
{
u = p;
p = p->next;
free(u);
}
//查找所有的入度边并删除
for(j=0;j<g->n;j++)
{
if(j!=i)
{
p = q = g->adjlist[j].firstarc; //设置前驱q
if(p->adjvex == i) //第一个邻接点是被删除顶点
{
u = p;
g->adjlist[j].firstarc = p->next;
p = p->next;
free(u);
}
else //第一个邻接点不是被删除顶点
{
while(p)
{
if(p->adjvex == i)
{
u = p;
q->nextarc = p->nextarc;
free(u);
break;
}
if(p->adjvex > i) p->adjvex--; //i之后的顶点在向量中的下标前移
q->nextarc = p; q=p;p=p->nextarc;
}
}
for(j=i+1;j<n;j++) g[j-1] = g[j]; //删除下标为i的顶点 后面元素前移
}
}
}
4.3.2 图的边
1.删除无向图的边(邻接表表示)
void DeletEdge(AdjGraph g, int i,int j)
//在用邻接表方式存储的无向图g中,删除边(i,j)
{
p=g[i].firstarc;
pre=null; //删顶点 i 的边结点(i,j),pre 是前驱指针
while (p)
if (p->adjvex==j)
{
if(pre==null)
g[i].firstarc=p->next;
else
pre->next=p->next;
free(p);
}//释放结点空间。
else{
pre=p;
p=p->next;
} //沿链表继续查找
p=g[j].firstarc;
pre=null; //删顶点 j 的边结点(j,i)
while (p)
if (p->adjvex==i)
{
if(pre==null)
g[j].firstarc=p->next;
else
pre->next=p->next;
free(p);}//释放结点空间。
else{
pre=p;
p=p->next;
} //沿链表继续查找
}
2.删除有向图的边(邻接表表示)
void deleteEdge(AGraph *g,int i,int j) //删除<i,j>的边
{
ArcNode *p,pre;
p = g->adjlist[i].firstarc;
pre = p;
p = q = g->adjlist[j].firstarc; //设置前驱q
if(p->adjvex == j) //第一个邻接点是被删除顶点
{
u = p;
g->adjlist[j].firstarc = p->next;
p = p->next;
free(u);
}
else //第一个邻接点不是被删除顶点
{
while(p)
{
if(p->adjvex == i)
{
u = p;
q->nextarc = p->nextarc;
free(u);
break;
}
q=p;p=p->nextarc;
}
}
}
4.3 图的路径
4.3.1 普通路径问题(包括回路)
1.有向图判断vi到vj有无路径
// 1)深度优先遍历的解决方式
//邻接表方式实现
int visit[maxsize];
int havePath(AGraph *g,int vi,int vj) //从vi到vj判断有无路径
{
if(vi==vj) //存在路径
return 1;
else
{
visit[vi] = 1;
ArcNode *p;
p = g->adjlist[vi].firstarc;
while(p)
{
if(!visit[p->adjvex] && havePath(g,p->adjvex))
return 1;
p=p->nextarc;
}
return 0;
}
}
//邻接矩阵实现方式
int visit[maxsize];
int havePath(MGraph *g,int v1,int v2)
{
if(v1==v2) //存在路径
return 1;
else
{
visit[v1] = 1;
int i;
for(i=0;i<g->n;i++) //扫描v1的邻接顶点
{
if(g->edges[v1][i]&&havePath(g,i,v2))
return 1; //邻接点进行递归遍历
}
return 0;
}
}
//2)广度优先遍历实现方式
//邻接表方式实现
int havePath(AGraph *g,int v1,int v2)
{
int visit[maxsize];
int i;
for(i=0;i<g.n;i++) visit[i] = 0;
int que[maxsize],head,rear;
head = rear = 0;
rear = (rear+1)%maxsize;
que[rear] = v1;
visit[v1] = 1;
ArcNode *p;
while(rear!=head)
{
head = (head+1)%maxsize;
p = g->adlist[que[head]].firstarc;
while(p)
{
if(p->adjvex == v2)
return 1;
if(!visit[p->adjvex])
{
visit[p->adjvex] = 1;
rear = (rear+1)%maxsize;
que[rear] = p->adjvex;
}
p = p->nextarc;
}
}
return 0;
}
//邻接矩阵实现方式
int havePath(MGraph *g,int v1,int v2)
{
int que[maxsize],front,rear;
front = rear = 0;
int visit[maxsize];
int i,v;
for(i=0;i<g.n;i++) visit[i] = 0;
rear = (rear+1)%maxsize;
que[rear] = v1;
visit[v1] = 1;
while(front!=rear)
{
head = (head+1)%maxsize;
v = que[head];
for(i=0;i<g.n;i++)
{
if(g->edges[v][i]&&i==v2)
return 1;
if(g->edges[v][i]&&!visit[i])
{
rear = (rear+1)%maxsize;
que[rear] = i;
visit[i] = 1;
}
}
}
return 0;
}
2.判断一个有向图是否存在回路
//通过拓扑排序的方式来判断是否有回路
//邻接表实现方式(天勤的方式)
typedef struct VNode
{
int indegree; //用于标记节点的入度
ArcNode *firstarc;
}
int isCircle(AGraph *g)
{
int stack[maxsize],top=-1;
int i,n=0;
for(i=0;i<g->n;i++)
if(g->adjlist[i].indegree==0)
stack[++top] = i;
ArcNode *p;
while(top!=-1)
{
i = stack[top--];
n++;
p = g->adjlist[i].firstarc;
while(p)
{
g->adjlist[p->adjvex].indegree--;
if(g->adjlist[p->adjvex].indegree == 0)
stack[++top] = p->adjvex;
p = p->nextarc;
}
}
if(n==g->n)
return 0;
else
return 1;
}
//邻接矩阵的实现方式
//设置向量indegree 用来存放个顶点的入度值
int isCircle(MGraph *g)
{
int i,j,top=-1;
int m=0;
for(i=0;i<g->n;i++)
if(indegree[i]==0) //建立入度为0的栈 top为栈顶 -1
{
indegree[i] = top;
top = i;
}
while(top!=-1)
{
m++;j=top; //取栈顶元素
top = indegree[top]; //退栈
for(i=0;i<g->n;i++)
if(g->edges[j][i])
{
indegree[i]--;
if(indegree[i]==0)
{
indegree[i]=top;top=i;
}
}
}
if(m<g->n)
return 0;
return 1;
}
3.如果r到图G中的每个节点都可以到达,r则为G的根节点,求图G的根节点
void DFS(AGraph *g,int v,int visit[],int &sum)
{
visit[v] = 1;
sum++;
ArcNode *p;
p = g->adjlist[v].firstarc;
while(p)
{
if(visit[p->adjvex]==0)
DFS(g,p->adjvex,visit);
}
}
void PrintRoot(AGraph *g)
{
int i,sum;;
for(i=0;i<g.n;i++)
{
sum = 0;
int j;
for(j=0;j<g.n;j++) visit[j] = 0;
DFS(g,i,visit,sum);
if(sum==g->n)
cout<<i<<endl;
}
}
4.判断无向图中是否存在一条以V0为起点的包含所有的顶点的简单路径并输出
int visit[maxsize];
int num=0;
int path[maxsize];
void isIncludeALL(AGraph *g,int v0)
{
visit[v0] = 1;
path[++num] = v0;
ArcNode *p;
if(num==g->n) //此路径包含所有顶点 则输出
{
for(int i=1;i<=num;i++) cout<<path[i]<<;
exit(0); //找到后则退出整个程序
}
p = g->adjlist[v0].firstarc;
while(p)
{
if(visit[p->adjvex]==0)
isIncludeALL(g,p->adjvex);
p = p->nextarc;
}
visit[v0] = 0;
num--;
}
5.判断有向图中是否有从vi到vj的简单路径 若有 则全部打印之
//邻接表的DFS方式递归实现
int path[maxsize],num=-1; //路径数组
int visit[maxsize]; //标记数组
void PrintPath(AGraph *g,int vi,int vj)
{
path[++num] = vi;
visit[vi] = 0;
if(vi==vj)
{
for(int i=0;i<=num;i++)
cout<<path[i]<<","<<endl;
}
ArcNode *p;
p = g->adjlist[vi].firstarc;
while(p)
{
if(visit[p->adjvex]==0)
PrintPath(g,p->adjvex,vj);
p = p->nextarc;
}
visit[vi] = 0; //释放该节点 使得该节点可重新使用
num--;
}
//邻接表的DFS方式非递归实现
void PrintPath(AGraph *g,int vi,int vj)
{
int path[maxsize],num=-1;
int visit[maxsize];
int i;
for(i=0;i<g->n;i++) visit[i]=0;
path[++num] = vi;
visit[vi] = 1;
ArcNode *p;
while(num!=-1||p)
{
p = g->adjlist[path[num]].firstarc; //第一个临接定点
while(p!=NULL && visit[p->adjvex]==1) p=p->next; //下一个访问顶点
if(p==NULL)
{
i = path[num];
visit[i] = 0; //此句是我另加上去的 1800上没有 我感觉得加上 因为该节点或许以后可以 //重新使用 不知道对不对
num--;
} //退栈
else
{
i = p->adjvex;
if(i==vj)
{
for(int i=0;i<=num;i++)
cout<<path[i]<<","<<endl;
}
else
{
visit[i] = 1;
path[++num] = i;
}
}
}
}
6.根到叶子节点的最大距离被称为树的半径 给定一个无向连通图 写一个算法以找出半径最小的生成树 1800的最后一题 (参考书上的答案怎么感觉怪怪的啊?)
4.3.2 最短路径
1)直接用迪杰斯特拉算法解决 1800 P146 T32
2)直接用Floyd算法解决 1800 P146 T33 T34
//1800 P146 T34
void Floyd(MGraph *g,int A[][maxsize]) //因本题没有求路径 所以省略了path数组
{
int i,j,k;
for(i=0;k<g->n;k++) //初始化A数组
for(j=0;k<g->n;k++)
A[i][j] = g->adges[i][j];
for(k=0;k<g->n;k++)
for(i=0;k<g->n;k++)
for(j=0;k<g->n;k++)
if(A[i][j]>=A[i][k]+A[k][j])
A[i][j]=A[i][k]+A[k][j];
}
int ShortPath(MGraph *g) //返回医院应该建立位置的下标
{
int A[maxsize][maxsize];
Floyd(g,A);
int s,i,j,k,m;
m = INF; //等于世界上最大的值
for(i=0;i<g->n;i++)
{
s=0;
for(j=0;j<g->n;j++)
if(A[i][j]>s)
s = A[i][j];
if(m>s)
{
m=s;k=i; //K记下出发点 也就是医院该建立的位置
}
}
return k;
}
3)如果图的每个边的长度相等时 适合用广度优先遍历算法解决
1.求距离vo的最短路径长度为K的所有顶点 (以弧数为单位)
void findPathToK(AGraph *g,int v0,int k)
{
int que[maxsize],head=0,rear=0;
int visit[maxsize];
int t,level,flag=0;
que[rear++] = v0;
t=rear;
level = 1;
visit[v0] = 1;
ArcNode *p;
while(front!=rear&&level<=K+1)
{
v = Q[++front];
p = g->adjlist[v].firstarc;
while(p!=NULL) //证明邻接顶点存在
{
if(visit[p->adjvex]==0)
{
que[++rear] = p->adjvex;
visit[p->adjvex] = 1;
if(level==K+1)
{
//打印
cout<<p->adjvex<<endl;
flag = 1;
}
}
p = p->nextarc;
}//while
if(front==t)
{
level++;t=rear;
}
}//while
if(flag==0) //没有找到
return 0;
return 1;
}
2.求图的中心点的算法。设V是有向图G的一个顶点,我们把V的偏心度定义为:max{从w到v的最短距离|w是g中所有顶点},如果v是有向图G中具有最小偏心度的顶点,则称顶点v是G的中心点。
分析:利用 FLOYD 算法求出每对顶点间的最短路径矩阵 w,然后对矩阵 w,求出每列 j 的最大值,得到顶点 j 的偏心度。然后在所有偏心度中,取最小偏心度的顶点 v 就是有向图的中心点。
void FLOYD_PXD(AdjMatrix g)
//对以带权邻接矩阵表示的有向图 g,求其中心点。
{
AdjMatrix w=g ; //将带权邻接矩阵复制到 w。
for (k=1;k<=n;k++) //求每对顶点间的最短路径。
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(w[i][j]>w[i][k]+w[k][j])
w[i][j]=w[i][k]+w[k][j];
v=1;
dist=MAXINT; //中心点初值顶点 v 为1,偏心度初值为机器最大数。
for(j=1;j<=n;j++)
{s=0;
for(i=1;i<=n;i++)
if(w[i][j]>s)
s=w[i][j]; //求每列中的最大值为该顶点的偏心度。
if(s<dist)
{dist=s;
v=j;}//各偏心度中最小值为中心点。
}
printf( "有向图 g 的中心点是顶点%d,偏心度%d\n",v,dist);
}
3.图G有n个点,利用从某个源点到其余各点最短路径算法思想,设计一产生G的最小生成树的算法。
分析:按 Dijkstra 路径增序求出源点和各顶点间的最短路径。本题是求出最小生成树,即以源点为根,其路径权值之和最小的生成树。在确定顶点的最短路径时,总是知道其(弧出自的)前驱(双亲)顶点,我们可用向量 p[1..n]记录各顶点的双亲信息,源点为根,无双亲,向量中元素值用-1 表示。
void Shortest_PTree ( AdjMatrix G, vertype v0)
//利用从源点 v0 到其余各点的最短路径的思想,产生以邻接矩阵表示的图 G 的最小生成树
{
int d[] ,s[] ; //d 数组存放各顶点最短路径,s 数组存放顶点是否找到最短路径。
int p[] ; //p 数组存放顶点在生成树中双亲结点的信息。
for(i=1;i<=n;i++)
{d[i]=G[v0][i]; s[i]=0;
if(d[i]<MAXINT)
p[i]=v0; //MAXINT 是机器最大数,v0 是 i 的前驱(双亲)。
else p[i]=-1;
}// for //i 目前无前驱,p 数组各量初始化为-1。
s[v0]=1; d[v0]=0; p[v0]=-1; //从 v0 开始,求其最小生成树。
for(i=1;i<n;i++)
{mindis=MAXINT;
for(j=1;j<=n;j++)
if(s[j]==0 && d[j]<mindis)
{ u=j; mindis=d[j];}
s[u]=1; //顶点 u 已找到最短路径。
for (j=1;j<=n;j++) //修改 j 的最短路径及双亲。
if (s[j]==0 && d[j]>d[u]+G[u][j])
{d[j]=d[u]+G[u][j]; p[j]=u;}
}
for(i=1;i<=n;i++) //输出最短路径及其长度,路径是逆序输出。
if(i!=v0)
{pre=p[i];
printf( "\n 最短路径长度=%d,路径是:%d,",d[i],i);
while (pre!=-1)
{ printf( ",%d",pre); pre=p[pre];} //一直回溯到根结点。
}
}
4.3.3 最长路径
1)广度优先遍历解决
1.求有向无环图的最长路径 每条弧的长度均为一
int findLongPath(AGraph *g,int v0)
{
int que[maxsize],front=0,rear=0;
int t,level=1,v;
int visit[maxsize];
for(int i=0;i<g->n;i++) visit[i] = 0;
visit[v0] = 1;
que[++rear] = v0;
t = rear;
ArcNode *q;
while(front!=rear)
{
v = que[++front];
q = g->adjlist[v].firstarc;
while(q)
{
if(visit[q->adjvex]==0)
{
que[++rear] = q->adjvex;
visit[q->adjvex] = 1;
}
q = q->nextarc;
}
if(front == t)
{
level++;t=rear;
}
}
return level-1;
}
int longestPath(AGraph *g) 返回图的最长路径长度
{
int i,m,j;
m = 0;
for(i=0;i<g->n;i++)
{
j = findLongPath(g,i);
if(i>m) m = j;
}
return m;
}
2)拓扑排序解决
1.用拓扑排序求图的最长路径的算法
//此算法我自己写的 或许还不一定对。。。
typedef struct
{
ArcNode *firstarc;
int indegree; //每个顶点的入度数
}VNode; //图的顶点结构
typedef struct
{
int next;
int w; //与下一邻接点所构成的边的权值大小
}Node; //数组元素的结构 (采用树的储存方式来记录节点路径)
void topSort(AGraph *g)
{
Node set[maxsize];
//寻找入度为零的顶点
int i,sign;
int stack[maxsize],top=0; //栈储存顶点下标
for(i=0;i<g->n;i++)
{
if(g->adjlist[i].indegree==0)
{
stack[++top] = i;
set[i].next = -1;
}
}
sign = top;
ArcNode *p;
int m,maxadj;
while(top!=-1)
{
i = stack[top--];
p = g->adjlist[i].firstarc;
m=0;
while(p)
{
g->adjlist[i].indegree--;
if(g->adjlist[i].indegree==0)
{
if(m<p->weight)
{
m = p->weight;
maxw = p->weight;
}
stack[++top] = p->adjvex;
}
}
set[i].next = minadj;
set[i].w = m;
set[minadj] = -1;
}
//从所有路径中选择最长的
int j,flag,result=0;
for(i=1;i<=sign;i++)
{
j=i;m=0;
while(j!=-1)
{
m+=set[j].w;
j = set[j].next;
}
if(result<m)
{
result = m;
flag = i;
}
}
//输出相应节点
while(flag!=-1)
{
cout<<flag<<","<<endl;
flag = set[flag].next;
}
}
4.4 连通分量/强连通分量
1.已知无向图G 求图的连通分量的算法,同时输出每一个连通分量顶点
int linkNum()
{
int k=0;
for(int i=0;i<g->n;i++)
{
if(visit[i]==0)
{
k++;
printf("%d\n",i);//输出顶点
DFS(i); //或者BFS
}
}
return k;
}
4.5 拓扑排序
1.利用深度优先遍历方法,对该图中结点进行拓扑排序。
分析:在遍历过程中,每访问一个顶点,就将其邻接到的顶点的入度减一,并对其未访问的、入度为0的邻接到的顶点进行递归。