图
图是由顶点的有穷集合V(G)和边的有穷集合E(G)组成的,用G=(V,E)表示图,是多对多关系,其中分为有向图和无向图,带权图又被称为网。
存储表示
图常用的存储表示----------邻接矩阵法和邻接表法。
邻接矩阵
图有N个顶点,那么这个图的邻接矩阵是一个N*N的二维数组。代码中设置两点没边,则这两点对应的二维数组值为0,其他有边的两点看是否为带权图,是则二维数组值就为两点的权值,不是则用一个特定的数字来代表这两点有边,当然还要判断这个图是有向图还是无向图了,具体实现看代码注释。
#include <iostream>
using namespace std;
typedef char VertexType; //顶点的类型
typedef int EdgeType; //边的权值类型
#define MaxVex 100 //定义最大定点数
#define Infinity (~(1<<(sizeof(int)*8-1)))
/*
表示1左移4*8-1位 然后取反,
int(int为四字节时,int在有的电脑上不是四字节)的最大值,
当两点之间不邻接时,将两点之间的权值设为int的最大值,
在输出时,用“∞”表示。
*/
//邻接矩阵定义
typedef struct
{
VertexType Vexs[MaxVex]; //顶点数组
EdgeType Edge[MaxVex][MaxVex]; //边的权值信息数组
int numV; //当前的顶点数
int numE; //当前的边数
}MGraph;
//创建图的邻接矩阵
void CreateMGraph(MGraph* G)
{
char vi, vj;//vi,vj用来存储顶点
int e;
printf("请输入图的顶点数和边数(顶点数 边数):");
cin>>G->numV>>G->numE;
for (int i = 0; i < G->numV; i++)//矩阵的初始化
{
for (int j = 0; j < G->numV; j++)
{
if (i == j)
{
G->Edge[i][j] = 0;//将顶点与自身的权值初始化为0
}
else
{
G->Edge[i][j] = Infinity;//将顶点与其他顶点的权值初始化为Infinity,表示目前两点无边
}
}
}
for (int i = 0; i < G->numV; i++)//将顶点信息按照输入顺序存入到顶点数组中
{
printf("请输入第%d个顶点的值:", i + 1);
cin>>G->Vexs[i];
}
cout<<endl;
for (int k = 0; k < G->numE; ++k)
{
int i, j;
cout<<"请输入第"<<k+1<<"边的信息(顶点 顶点 权值):";
cin>>vi>>vj>>e;
/*
若为不带权值的图,输入时e输入1,
若为带权值的图,则e输入对应的权值
区分带权图和不带权图
*/
for (i = 0; i < G->numV; i++)
{
if (G->Vexs[i] == vi)
{
break;//找出顶点在数组中的位置
}
}
for (j = 0; j < G->numV; j++)
{
if (G->Vexs[j] == vj)
{
break;
}
}
//无向图的邻接矩阵具有对称性,也就是两点边的权值是一样的
//有向图不具备此性质,只需要执行第一步,第二步不需要执行
G->Edge[i][j] = e;
G->Edge[j][i] = e;
}
//输出图的邻接矩阵
void ShowpGraph(MGraph G)
{
printf("\n邻接矩阵为:\n");
printf("\t");//规划格式
for (int i = 0; i < G.numV; ++i)
{
printf("%6c", G.Vexs[i]);//显示每个顶点的值,并规划格式
}
printf("\n");
for (int i = 0; i < G.numV; ++i)
{
printf("\n%8c", G.Vexs[i]);
for (int j = 0; j < G.numV; ++j)
{
if (G.Edge[i][j] == Infinity)
{
printf("%6s", "∞");//两点之间无连接时输出“∞”
}
else
{
printf("%6d", G.Edge[i][j]); //两点之间有连接时,输出边的权值
}
}
printf("\n");
}
}
int main()
{
MGraph G;
CreateMGraph(&G);//创建图的邻接矩阵
ShowpGraph(G);//输出图的邻接矩阵
return 0;
}
从以上代码的注释中就可以看出以上代码可以创建多种类的图,上面只是创建无向带权图,可以尝试修改代码创建其它种类的图。
使用的图为:
运行结果如下:
邻接表
邻接表结合顺序存储和链式存储结构,链表代替邻接矩阵的每行,每个链表中存储顶点和该顶点相邻的所有点。对于带权图,在结点结构中增加权值域。本文实现无向图的邻接表创建,有向图在代码注释中会提示怎么创建,至于带权图根据上面所说增加权值域的方法来扩展吧。
#include <iostream>
using namespace std;
typedef struct EdgeNode
{
int adjvex;//结点编号
struct EdgeNode *next;//指向下一结点的指针
}EdgeNode; //边结点
typedef struct VextexNode
{
char vextexinfo; //头结点信息(如A,B,C,D)
EdgeNode *firstedge; //指向该结点的临边
}AdjList; //头结点(即邻接表)
typedef struct
{
int vexNum; //顶点数
int edgeNum; //边数
AdjList adjlist[20]; //头结点数组
}ALGraph;
void InitAdjList(ALGraph &g)//初始化邻接表
{
int a,b;
cout<<"输入图的顶点数和边数:";
cin>>a>>b;
g.vexNum=a;
g.edgeNum=b;
//初始化头结点数组(即表头信息)
for(int i =1; i<=a ; i++)
{
cout<<"请输入第"<<i<<"个结点的信息:";
cin>>g.adjlist[i].vextexinfo;
g.adjlist[i].firstedge=NULL; //初始化指针指向空
}
}
void Locata(ALGraph &g,char &vex1,char &vex2, int &m, int &n)//确定邻接表数组下标的位置
{
for(int i =1;i<=g.vexNum;i++)
{
if(vex1 == g.adjlist[i].vextexinfo)
m=i;
if(vex2 == g.adjlist[i].vextexinfo)
n=i;
}
}//通过两个结点来确认边,并得到这两个结点的在数组的下标位置
void SetAdjList(ALGraph &g)//*建立邻接表
{
char vex1,vex2;
int m,n;
for(int i=1;i<=g.edgeNum;i++) //有几条边就循环几次
{
cout<<"请输入第"<<i<<"条边(形如A B,表示A到B的一条边):";
cin>>vex1>>vex2; //输入边的信息(即通过输入两顶点来确认边)
Locata(g,vex1,vex2,m,n);//得到vex1和vex2在邻接表的数组下标位置m和n
EdgeNode *s1 = new EdgeNode;
s1->adjvex=n; //无向图现在m是头,n是尾
s1->next = g.adjlist[m].firstedge;
g.adjlist[m].firstedge=s1;
//要是将其改为有向图的邻接表,则下面这段代码全部删除
EdgeNode *s2 = new EdgeNode;
s2->adjvex=m; //无向图现在n是头,m是尾
s2->next = g.adjlist[n].firstedge;
g.adjlist[n].firstedge=s2;
}
}
void ShowAdjList(ALGraph &g)//显示邻接表
{
cout<<"该图的邻接表如下:"<<endl;
for(int i=1;i<=g.vexNum ;i++)
{
EdgeNode *p;
cout<<"结点"<<i<<":";
for(p = g.adjlist[i].firstedge; p!=NULL ; p=p->next)//循环到没有点与头结点相连
cout<<p->adjvex<<" ";
cout<<endl;
}
}
int main()
{
ALGraph g;
InitAdjList(g);
SetAdjList(g);
ShowAdjList(g);
return 0;
}
使用的图是上面的图,只是边上无权值。
运行结果如下:
遍历DFS
以上代码虽能实现存储结构的显示,但我们依旧要学会图的遍历算法。深度优先搜索(DFS)首先访问图中某个顶点V,然后选择一个与V邻接的未被访问的顶点W,从W开始进行DFS,重复这个过程,直到一个顶点的所有邻接点全被访问过,按照刚才的访问顺序回溯到顶点的某个相邻结点U未被访问过为止,然后再从U出发进行DFS遍历。重复以上过程,直到所有的点都被访问过为止。具体实现看下面的代码。
邻接矩阵DFS算法
void dfs(MGraph* G, int* v, int s)
{
int j;
v[s] = 1;//表示结点被访问过
cout << G->Vexs[s] << " ";
for (j = 0; j < G->numV; j++)
{
if (G->Edge[s][j] != Infinity && !v[j])
dfs(G, v, j);
}
}
void DFS(MGraph* G)
{
int i;
int* v = (int*)malloc(sizeof(int) * G->numV);//用于记录图中的哪些结点被访问过了
cout << endl << "图的邻接矩阵DFS遍历:" << endl;
for (i = 0; i < G->numV; i++)//初始化结点都未被访问过
v[i] = 0;
for (i = 0; i < G->numV; i++)
{
if (!v[i])//对未被访问过的结点调用dfs函数,若是连通图,只会执行一次
dfs(G, v, i);
}
}
//只需在主函数调用DFS函数就可以实现对上面图的遍历了
int main()
{
MGraph G;
CreateMGraph(&G);//创建图的邻接矩阵
ShowpGraph(G);//输出图的邻接矩阵
DFS(&G);//图的邻接矩阵DFS算法输出
cout << endl;
return 0;
}
使用的图是上面的图。
运行结果如下:
邻接表DFS算法
void dfs(ALGraph &g,int* v,int s)
{
EdgeNode *t=NULL;
v[s]=1;
cout<<s<<" ";
t=g.adjlist[s].firstedge->next;
while(t!=NULL)
{
if(!v[t->adjvex])
{
dfs(g,v,t->adjvex);
}
t=t->next;
}
}
void DFS(ALGraph &g)
{
int i;
int* v=(int*)malloc(sizeof(int)*g.vexNum);//用于记录图中的哪些结点被访问过了
cout<<endl<<"图的邻接表的DFS遍历:"<<endl;
for(i=1;i<=g.vexNum;i++)//初始化结点都未被访问过
v[i]=0;
for(i=1;i<=g.vexNum;i++)
{
if(!v[i])//对未被访问过的结点调用dfs函数,若是连通图,只会执行一次
dfs(g,v,i);
}
}
//只需在主函数调用DFS函数就可以实现对上面图的遍历了
int main()
{
ALGraph g;
InitAdjList(g);
SetAdjList(g);
ShowAdjList(g);
DFS(g);//图的邻接表DFS算法输出
cout<<endl;
return 0;
}
使用的图是上面的图,只是边上无权值。
运行结果如下: