图论〔Graph Theory〕是数学的一个分支。它以图为研究对象。图论中的图是由若干给定的点及连接两点的线所构成的图形,这种图形通常用来描述某些事物之间的某种特定关系,用点代表事物,用连接两点的线表示相应两个事物间具有这种关系。
下面我主要讲一下图的创建以及对图进行操作的一些基础算法:
首先来讲图的创建,图主要有两种存储形式,邻接矩阵和邻接表。下面给出这两种存储方式的结构:
//图的邻接矩阵存储形式
typedef struct{
int no; //顶点标号(默认为1-n)
InfoType *info; //存储顶点信息(比如顶点的名字)
}VertexType;
typedef struct{
VertexType vex[MAX_VERTEX_NUM]; //顶点向量
int arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; //邻接矩阵数组,相邻就是1,不相邻就是0,也可以存储权值
int n,e; //图的当前顶点数和弧数
}MGraph;
//图的邻接表存储形式
typedef struct ArcNode{ //链表节点
int adjvex; //该弧所指向的顶点的位置
int weight; //网的权值
ArcNode *nextarc; //指向下一条弧的指针
}ArcNode;
typedef struct{
VertexType data; //顶点信息:标号和其他信息
ArcNode *fristarc; //第一个表节点的地址,指向第一条依附该顶点的弧的指针
}VNode;
typedef struct{
VNode adjlist[MAX_VERTEX_NUM]; //单链表的个数(顶点的个数)
int n,e; //邻接表存储的图的当前顶点数和弧数
}AlGraph;
看懂了这两种存储结构都,让我们来思考一下怎么创建这两种存储方式呢?
先来看一下邻接矩阵的创建:
Status CreateMat(MGraph &G) //创建一个邻接矩阵,只需输入点与点之间的关系即可
{
//char iofo1;
int v1,v2,w,bh;
cin>>G.n>>G.e; //先数一数顶点数和边数
for(int i=1;i<=G.n;i++)
{
//cin>>bh;
G.vex[i].no = i; //给G.n个顶点标号为1-n,也可以自定义标号bh
//cin>>info1; //存储一些顶点信息,比如"青岛"
//G.vex[i].info = info1;
for(int j=1;j<=G.n;j++) //给矩阵赋初值0
G.arcs[i][j] = 0;
}
for(int i=1;i<=G.e;i++)
{
cin>>v1>>v2>>w; //输入顶点向量和两个顶点向量之间的关系
G.arcs[v1][v2] = w; //存储弧度信息,权值或者存1表示两顶点向量相邻
G.arcs[v2][v1] = w;
}
}
再来看一下邻接表的创建:
Status CreateList(AlGraph &AGG) //创建一个邻接表
{
int t,sum_e=0;
ArcNode *p;
cin>>AGG.n; //输入邻接表的顶点数
for(int i=1;i<=AGG.n;i++)
{
AGG.adjlist[i].fristarc = NULL; //给邻接表中的所有头结点的指针域赋初值
AGG.adjlist[i].data.no = i;
cin>>t;
while(t != 0)
{
p = (ArcNode *)malloc(sizeof(ArcNode));
p->adjvex = t;
p->nextarc = AGG.adjlist[i].fristarc;
AGG.adjlist[i].fristarc = p;
sum_e++; //记录弧数的二倍,因为两点之间连接了两次
cin>>t;
}
}
AGG.e = sum_e/2;
}
学会了两种存储方式的结构的创建和各变量所存储的内容后,下面来看一下两种存储方式之间的转化。
①邻接矩阵转变为邻接表:
Status MatToList(MGraph G,AlGraph &AG) //将邻接矩阵转为邻接表
{
ArcNode *p;
AG.n = G.n; //将邻接矩阵的顶点数和弧数赋给邻接表
AG.e = G.e;
for(int i=1;i<=G.n;i++)
{
AG.adjlist[i].fristarc = NULL; //给邻接表中的所有头结点的指针域赋初值
AG.adjlist[i].data.no = G.vex[i].no;//将邻接矩阵的编号赋给邻接表的头节点
}
for(int i=1;i<=G.n;i++)
{
for(int j=G.n;j>=1;j--)
{
if(G.arcs[i][j]!=0) //如果两个点之间相连,就用头插法插入节点信息
{
p = (ArcNode *)malloc(sizeof(ArcNode));
p->adjvex = j; //顶点的位置(几号)
p->nextarc = AG.adjlist[i].fristarc;
AG.adjlist[i].fristarc = p;
}
}
}
}
②邻接表转变为邻接矩阵:
Status ListToMat(MGraph &GG,AlGraph AGG) //将邻接表转为邻接矩阵
{
ArcNode *p;
GG.n = AGG.n;
GG.e = AGG.e;
for(int i=1;i<=GG.n;i++) //初始化邻接矩阵
for(int j=1;j<=GG.n;j++)
GG.arcs[i][j] = 0;
for(int i=1;i<=AGG.n;i++) //将每一顶点相连的点创建为一个个的单链表
{
p = AGG.adjlist[i].fristarc;
while(p)
{
GG.arcs[i][p->adjvex] = 1;
p = p->nextarc;
}
}
}
创建完毕之后我们想要看一看我们存储的图是不是我们想要的,下面就要用到打印了。
邻接矩阵和邻接表的打印:
void DisMat(MGraph G) //打印邻接矩阵
{
cout<<"该邻接矩阵为:"<<endl;
for(int i=1;i<=G.n;i++)
{
for(int j=1;j<=G.n;j++)
cout<<G.arcs[i][j]<<" ";
cout<<endl;
}
}
void DisList(AlGraph AG) //打印邻接表
{
cout<<"该邻接表为:"<<endl;
ArcNode *p;
for(int i=1;i<=AG.n;i++)
{
p = AG.adjlist[i].fristarc;
cout<<AG.adjlist[i].data.no; //打印节点的标号
while(p)
{
cout<<"->"<<p->adjvex;
p = p->nextarc;
}
cout<<endl;
}
}
最后等你完成所以的算法之后,销毁这个线性表:
void Destory_List(AlGraph &AGG) //销毁图的邻接表
{
ArcNode *p,*q;
for(int i=1;i<=AGG.n;i++)
{
p = AGG.adjlist[i].fristarc;
while(p)
{
q = p->nextarc;
free(p);
p = q;
}
}
}
图的两种搜索方式:深度优先搜索DFS和广度优先搜索BFS。
void DFS(AlGraph AG,int v) //深度优先搜索遍历邻接表
{
cout<<AG.adjlist[v].data.no<<" "; //先输出每个顶点的标号
d[v] = 1; //已经遍历过的顶点标记为 1
ArcNode *p;
p = AG.adjlist[v].fristarc; //创建一个新节点怕,并指向编号为v的头结点
while(p != NULL)
{
if(!d[p->adjvex]) //如果该顶点没有被标记过,就该顶点的单链表
DFS(AG,p->adjvex);
p=p->nextarc;
}
}
void BFS(AlGraph AG,int v) //广度优先搜索遍历邻接表
{
cout<<AG.adjlist[v].data.no<<" "; //先输出每个顶点的标号
b[v] = 1; //已经遍历过的顶点标记为 1
q.push(v);
ArcNode *p;
p = AG.adjlist[v].fristarc; //创建一个新节点,并指向编号为v的头结点
while(p!=NULL) //将顶点标号为v的单链表中的元素全部入队列
{ //也就是将你想遍历的第一个点相邻的节点入队列
q.push(p->adjvex);
p=p->nextarc;
}
while(!q.empty()) //只要队列不空,也就是还没有遍历完整个邻接表就不会停止
{
p=AG.adjlist[q.front()].fristarc; //让p指向队列第一个元素指向的顶点
while(p) //将与顶点p相连的顶点全部入队列
{
if(!b[p->adjvex]) //如果该顶点没有遍历过
{
q.push(p->adjvex); //将该顶点入队列
cout<<AG.adjlist[p->adjvex].data.no<<" ";//输出顶点的值
b[p->adjvex]=1; //并标记为 1
}
p=p->nextarc;
}
q.pop(); //将与p相连的所有节点遍历完并入队列后,将该顶点出队列
}
cout<<endl;
}
有关有每个顶点的入度和出度问题以及出度为0的顶点的数量,都写到了一起,看代码:
void InDs(MGraph G) //以邻接矩阵的形式存储,找每个顶点的入度
{
for(int i=1;i<=G.n;i++)
{
int sum=0;
for(int j=1;j<=G.n;j++)
{
if(G.arcs[j][i] != 0)
sum++;
}
printf("顶点%d的入度为:%d\n",i,sum);
}
cout<<endl;
}
void OutDs(MGraph G) //以邻接矩阵的形式存储,找每个顶点的出度
{
for(int i=1;i<=G.n;i++)
{
int sum=0;
for(int j=1;j<=G.n;j++)
{
if(G.arcs[i][j] != 0)
sum++;
}
printf("顶点%d的出度为:%d\n",i,sum);
}
cout<<endl;
}
void ZeroOutDs(MGraph G) //以邻接矩阵的形式存储,出度为 0的点的个数
{
int temp=0;
for(int i=1;i<=G.n;i++)
{
int sum=0;
for(int j=1;j<=G.n;j++)
{
if(G.arcs[i][j] != 0)
sum++;
}
if(sum == 0)
temp++;
}
printf("出度为0的顶点数为:%d\n",temp);
}
void InDs2(AlGraph AG) //以邻接表的形式存储,找每个顶点的入度
{
ArcNode *p;
for(int i=1;i<=AG.n;i++)
{
int nn2=0;
for(int j=1;j<=AG.n;j++)
{
p=AG.adjlist[j].fristarc;
while(p != NULL)
{
if(p->adjvex == i)
nn2++;
p = p->nextarc;
}
}
printf("顶点%d的入度为:%d\n",i,nn2);
}
cout<<endl;
}
void OutDs2(AlGraph AG) //以邻接表的形式存储,找每个顶点的出度
{
ArcNode *p;
for(int i=1;i<=AG.n;i++)
{
int nn1=0;
p=AG.adjlist[i].fristarc;
while(p != NULL)
{
nn1++;
p = p->nextarc;
}
printf("顶点%d的出度为:%d\n",i,nn1);
}
cout<<endl;
}
void ZeroOutDs2(AlGraph AG) //以邻接表的形式存储,出度为 0的顶点个数
{
int temp=0;
for(int i=1;i<=AG.n;i++)
{
if(AG.adjlist[i].fristarc == NULL)
temp++;
}
printf("出度为0的顶点数为:%d\n",temp);
}
为了测试方便,我在贴上几个样例可供测验:
第一部分样例(无向图)
5 6
1 2 1
1 4 1
2 3 1
2 5 1
3 4 1
3 5 1
第一部分样例(有向图)
4 4
1 2 1
1 3 1
2 4 1
4 1 1
第二部分样例(无向图)
8
3 2 0
5 4 1 0
7 6 1 0
8 2 0
8 2 0
7 3 0
6 3 0
5 4 0
第二部分样例(有向图)
4
2 3 0
4 0
0
1 0
后序还有各种关于图的各种功能的算法我会慢慢补充,要回宿舍了溜了溜了。
算了,临走之前把全部的代码贴上吧...
#include<iostream>
#include<queue>
#include<cstdlib>
#define Status int
#define INFINITY 32767 //定义最大值
#define MAX_VERTEX_NUM 9 //定义最大节点为8+1,从1开始存储的
using namespace std;
int b[MAX_VERTEX_NUM];
int d[MAX_VERTEX_NUM];
typedef char InfoType; //其他信息,可自定义
//图的邻接矩阵存储形式
typedef struct{
int no; //顶点标号(默认为1-n)
InfoType *info; //存储顶点信息(比如顶点的名字)
}VertexType;
typedef struct{
VertexType vex[MAX_VERTEX_NUM]; //顶点向量
int arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; //邻接矩阵数组,相邻就是1,不相邻就是0,也可以存储权值
int n,e; //图的当前顶点数和弧数
}MGraph;
//图的邻接表存储形式
typedef struct ArcNode{ //链表节点
int adjvex; //该弧所指向的顶点的位置
int weight; //网的权值
ArcNode *nextarc; //指向下一条弧的指针
}ArcNode;
typedef struct{
VertexType data; //顶点信息:标号和其他信息
ArcNode *fristarc; //第一个表节点的地址,指向第一条依附该顶点的弧的指针
}VNode;
typedef struct{
VNode adjlist[MAX_VERTEX_NUM]; //单链表的个数(顶点的个数)
int n,e; //邻接表存储的图的当前顶点数和弧数
}AlGraph;
//正文
queue<int>q; //定义int型队列
Status CreateMat(MGraph &G) //创建一个邻接矩阵,只需输入点与点之间的关系即可
{
//char iofo1;
int v1,v2,w,bh;
cin>>G.n>>G.e; //先数一数顶点数和边数
for(int i=1;i<=G.n;i++)
{
//cin>>bh;
G.vex[i].no = i; //给G.n个顶点标号为1-n,也可以自定义标号bh
//cin>>info1; //存储一些顶点信息,比如"青岛"
//G.vex[i].info = info1;
for(int j=1;j<=G.n;j++) //给矩阵赋初值0
G.arcs[i][j] = 0;
}
for(int i=1;i<=G.e;i++)
{
cin>>v1>>v2>>w; //输入顶点向量和两个顶点向量之间的关系
G.arcs[v1][v2] = w; //存储弧度信息,权值或者存1表示两顶点向量相邻
G.arcs[v2][v1] = w;
}
}
Status CreateList(AlGraph &AGG) //创建一个邻接表
{
int t,sum_e=0;
ArcNode *p;
cin>>AGG.n; //输入邻接表的顶点数
for(int i=1;i<=AGG.n;i++)
{
AGG.adjlist[i].fristarc = NULL; //给邻接表中的所有头结点的指针域赋初值
AGG.adjlist[i].data.no = i;
cin>>t;
while(t != 0)
{
p = (ArcNode *)malloc(sizeof(ArcNode));
p->adjvex = t;
p->nextarc = AGG.adjlist[i].fristarc;
AGG.adjlist[i].fristarc = p;
sum_e++; //记录弧数的二倍,因为两点之间连接了两次
cin>>t;
}
}
AGG.e = sum_e/2;
}
Status MatToList(MGraph G,AlGraph &AG) //将邻接矩阵转为邻接表
{
ArcNode *p;
AG.n = G.n; //将邻接矩阵的顶点数和弧数赋给邻接表
AG.e = G.e;
for(int i=1;i<=G.n;i++)
{
AG.adjlist[i].fristarc = NULL; //给邻接表中的所有头结点的指针域赋初值
AG.adjlist[i].data.no = G.vex[i].no; //将邻接矩阵的编号赋给邻接表的头节点
}
for(int i=1;i<=G.n;i++)
{
for(int j=G.n;j>=1;j--)
{
if(G.arcs[i][j]!=0) //如果两个点之间相连,就用头插法插入节点信息
{
p = (ArcNode *)malloc(sizeof(ArcNode));
p->adjvex = j; //顶点的位置(几号)
p->nextarc = AG.adjlist[i].fristarc;
AG.adjlist[i].fristarc = p;
}
}
}
}
Status ListToMat(MGraph &GG,AlGraph AGG) //将邻接表转为邻接矩阵
{
ArcNode *p;
GG.n = AGG.n;
GG.e = AGG.e;
for(int i=1;i<=GG.n;i++) //初始化邻接矩阵
for(int j=1;j<=GG.n;j++)
GG.arcs[i][j] = 0;
for(int i=1;i<=AGG.n;i++) //将每一顶点相连的点创建为一个个的单链表
{
p = AGG.adjlist[i].fristarc;
while(p)
{
GG.arcs[i][p->adjvex] = 1;
p = p->nextarc;
}
}
}
void DisMat(MGraph G) //打印邻接矩阵
{
cout<<"该邻接矩阵为:"<<endl;
for(int i=1;i<=G.n;i++)
{
for(int j=1;j<=G.n;j++)
cout<<G.arcs[i][j]<<" ";
cout<<endl;
}
}
void DisList(AlGraph AG) //打印邻接表
{
cout<<"该邻接表为:"<<endl;
ArcNode *p;
for(int i=1;i<=AG.n;i++)
{
p = AG.adjlist[i].fristarc;
cout<<AG.adjlist[i].data.no; //打印节点的标号
while(p)
{
cout<<"->"<<p->adjvex;
p = p->nextarc;
}
cout<<endl;
}
}
void Destory_List(AlGraph &AGG) //销毁图的邻接表
{
ArcNode *p,*q;
for(int i=1;i<=AGG.n;i++)
{
p = AGG.adjlist[i].fristarc;
while(p)
{
q = p->nextarc;
free(p);
p = q;
}
}
}
//图的两种搜索方式:深度优先搜索DFS和广度优先搜索BFS
void DFS(AlGraph AG,int v) //深度优先搜索遍历邻接表
{
cout<<AG.adjlist[v].data.no<<" "; //先输出每个顶点的标号
d[v] = 1; //已经遍历过的顶点标记为 1
ArcNode *p;
p = AG.adjlist[v].fristarc; //创建一个新节点怕,并指向编号为v的头结点
while(p != NULL)
{
if(!d[p->adjvex]) //如果该顶点没有被标记过,就该顶点的单链表
DFS(AG,p->adjvex);
p=p->nextarc;
}
}
void BFS(AlGraph AG,int v) //广度优先搜索遍历邻接表
{
cout<<AG.adjlist[v].data.no<<" "; //先输出每个顶点的标号
b[v] = 1; //已经遍历过的顶点标记为 1
q.push(v);
ArcNode *p;
p = AG.adjlist[v].fristarc; //创建一个新节点,并指向编号为v的头结点
while(p!=NULL) //将顶点标号为v的单链表中的元素全部入队列
{ //也就是将你想遍历的第一个点相邻的节点入队列
q.push(p->adjvex);
p=p->nextarc;
}
while(!q.empty()) //只要队列不空,也就是还没有遍历完整个邻接表就不会停止
{
p=AG.adjlist[q.front()].fristarc; //让p指向队列第一个元素指向的顶点
while(p) //将与顶点p相连的顶点全部入队列
{
if(!b[p->adjvex]) //如果该顶点没有遍历过
{
q.push(p->adjvex); //将该顶点入队列
cout<<AG.adjlist[p->adjvex].data.no<<" "; //输出顶点的值
b[p->adjvex]=1; //并标记为 1
}
p=p->nextarc;
}
q.pop(); //将与p相连的所有节点遍历完并入队列后,将该顶点出队列
}
cout<<endl;
}
void InDs(MGraph G) //以邻接矩阵的形式存储,找每个顶点的入度
{
for(int i=1;i<=G.n;i++)
{
int sum=0;
for(int j=1;j<=G.n;j++)
{
if(G.arcs[j][i] != 0)
sum++;
}
printf("顶点%d的入度为:%d\n",i,sum);
}
cout<<endl;
}
void OutDs(MGraph G) //以邻接矩阵的形式存储,找每个顶点的出度
{
for(int i=1;i<=G.n;i++)
{
int sum=0;
for(int j=1;j<=G.n;j++)
{
if(G.arcs[i][j] != 0)
sum++;
}
printf("顶点%d的出度为:%d\n",i,sum);
}
cout<<endl;
}
void ZeroOutDs(MGraph G) //以邻接矩阵的形式存储,出度为 0的点的个数
{
int temp=0;
for(int i=1;i<=G.n;i++)
{
int sum=0;
for(int j=1;j<=G.n;j++)
{
if(G.arcs[i][j] != 0)
sum++;
}
if(sum == 0)
temp++;
}
printf("出度为0的顶点数为:%d\n",temp);
}
void InDs2(AlGraph AG) //以邻接表的形式存储,找每个顶点的入度
{
ArcNode *p;
for(int i=1;i<=AG.n;i++)
{
int nn2=0;
for(int j=1;j<=AG.n;j++)
{
p=AG.adjlist[j].fristarc;
while(p != NULL)
{
if(p->adjvex == i)
nn2++;
p = p->nextarc;
}
}
printf("顶点%d的入度为:%d\n",i,nn2);
}
cout<<endl;
}
void OutDs2(AlGraph AG) //以邻接表的形式存储,找每个顶点的出度
{
ArcNode *p;
for(int i=1;i<=AG.n;i++)
{
int nn1=0;
p=AG.adjlist[i].fristarc;
while(p != NULL)
{
nn1++;
p = p->nextarc;
}
printf("顶点%d的出度为:%d\n",i,nn1);
}
cout<<endl;
}
void ZeroOutDs2(AlGraph AG) //以邻接表的形式存储,出度为 0的顶点个数
{
int temp=0;
for(int i=1;i<=AG.n;i++)
{
if(AG.adjlist[i].fristarc == NULL)
temp++;
}
printf("出度为0的顶点数为:%d\n",temp);
}
int main()
{
//第一部分是邻接矩阵转为邻接表,输入的是每两个顶点之间的关系(相邻与否)或者权值
MGraph G;
AlGraph AG;
CreateMat(G);
DisMat(G);
MatToList(G,AG);
DisList(AG);
InDs(G);
OutDs(G);
ZeroOutDs(G);
cout<<endl;
InDs2(AG);
OutDs2(AG);
ZeroOutDs2(AG);
Destory_List(AG);
//第二部分是邻接表转为邻接矩阵,输入的是和每个顶点相连的顶点,每个顶点的输入以结束
/*MGraph GG;
AlGraph AGG;
CreateList(AGG);
DisList(AGG);
ListToMat(GG,AGG);
DisMat(GG);
DFS(AGG,5);
cout<<endl;
BFS(AGG,5);
Destory_List(AGG);*/
return 0;
}