目录
图的定义和术语
图的存储结构
顺序存储结构—邻接矩阵
链式存储结构
邻接表
邻接多重表
十字链表
图的遍历
图的连通性问题
有向无环图及其应用
最短路径
30.生成森林:在非连通图中,由每一个连通分量所得到的生成树所构成的森林。
31.有向图的生成森林:有向图的生成森林是指这样一些子图所构成的森林,由若干棵有向树所组成,这些有向树包含了该有向图的全部的顶点。所谓有向树是指,只有一个顶点入度为0,而其它的顶点入度都为1的有向图。如:
32.网络:连通的带权图(包括弱连通图)叫做网络
创建如图所示的几个图,分别为无向图,无向网,有向图,有向网
将图的顶点信息用一维数组表示,而图的边或者弧的信息用二维数组来表示即图的邻接矩阵来表示,我们将图的类型定义为枚举类型,将图定义为结构体类型,在结构体中存储图的顶点信息,邻接矩阵,图的类型,图中顶点的数目以及边或者弧的数目,如下定义了图的结构:
#define MVnum 20 //定义最大的顶点数量
//定义图的类型,无向无权图简记为无向图UDG,无向带权图即无向网UDN,有向无权图简记为有向图DG,有向带权图即有向网DN,D是direction的首字母大写
typedef enum Gkind{UDG=1,UDN,DG,DN}Gkind;
//在C语言中枚举类型被当作整数类型或者无符号整型来处理,所以枚举类型相当于是为一组离散的整数取一个别名,取别名的目的是为了见名知意
//定义图,将其定义成结构体类型,在结构体中显示处图的种类,图中含有边或者弧的条数,以及顶点信息用一维数组来表示,边或者弧用一个二维数组来表示,即用邻接矩阵来表示
typedef struct graphic
{
Gkind kind; //图的类型,无向图,无向网,有向图,有向网
char VInfo[MVnum]; //存储图的顶点信息
int Adjacency_matrix[MVnum][MVnum]; //邻接矩阵存储图的边或者弧的信息
int vnum; //图的顶点的数目
int EaNum; //图的边或者弧的数目
}G,*Graphic;
定义好图结构之后就需要定义一个函数来创建图,如下函数用于创建图:
void Create_Graphic(Graphic g)
{
//输入你所需要创建的图的种类
Gkind k;
printf("请输入你所需要创建的图的种类,1代表无向图,2代表无向网,3代表有向图,4代表有向网\n");
scanf("%d", &k);
g->kind = k;
//依次输入图的顶点的数目和图的数目
printf("请输入图中所含有的顶点的数目以及边或者弧的数目\n");
scanf("%d%d", &(g->vnum), &(g->EaNum));
getchar();
//输入图的顶点信息
printf("请输入图中的顶点元素的值:\n");
for (int i = 0; i < g->vnum; i++)
{
scanf("%c", &(g->VInfo[i]));
getchar();
}
//输入边或者弧的信息,即某两个顶点之间是否存在边或者存在弧;
switch (k)
{
case UDG:printf("判断图中两个顶点之间是否存在边,若存在则输入1,若不存在则输入0\n"); break;
case UDN:printf("判断图中两个顶点之间是否存在边,若存在则输入边的权值大小,若不存在则输入-1\n"); break;
case DG:printf("判断图中两个顶点之间是否存在弧,若存在则输入1,若不存在则输入0\n"); break;
case DN:printf("判断图中两个顶点之间是否存在弧,若存在则输入弧的权值大小若不存在则输入-1\n");
default:break;
}
for (int i = 0; i < g->vnum; i++)
{
for (int j = 0; j < g->vnum; j++)
{
printf("%c->%c:", g->VInfo[i], g->VInfo[j]);
scanf("%d", &g->Adjacency_matrix[i][j]);
}
}
printf("图创建成功\n");
}
完整的程序源代码,以及运行结果截图
//图的顺序存储结构之邻接矩阵
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#define MVnum 20 //定义最大的顶点数量
//定义图的类型,无向无权图简记为无向图UDG,无向带权图即无向网UDN,有向无权图简记为有向图DG,有向带权图即有向网DN,D是direction的首字母大写
typedef enum Gkind{UDG=1,UDN,DG,DN}Gkind;
//在C语言中枚举类型被当作整数类型或者无符号整型来处理,所以枚举类型相当于是为一组离散的整数取一个别名,取别名的目的是为了见名知意
//定义图,将其定义成结构体类型,在结构体中显示处图的种类,图中含有边或者弧的条数,以及顶点信息用一维数组来表示,边或者弧用一个二维数组来表示,即用邻接矩阵来表示
typedef struct graphic
{
Gkind kind; //图的类型,无向图,无向网,有向图,有向网
char VInfo[MVnum]; //存储图的顶点信息
int Adjacency_matrix[MVnum][MVnum]; //邻接矩阵存储图的边或者弧的信息
int vnum; //图的顶点的数目
int EaNum; //图的边或者弧的数目
}G,*Graphic;
//以下函数用于创建图
void Create_Graphic(Graphic g);
int main()
{
G Mygraphic;
Graphic mg = &Mygraphic;
Create_Graphic(mg);
printf("所创建的图的顶点信息:\n");
for (int i = 0; i < mg->vnum; i++)
{
printf("%c ", mg->VInfo[i]);
}
printf("\n所创建的图的边的信息即图的邻接矩阵\n");
for (int i = 0; i < mg->vnum; i++)
{
for (int j = 0; j < mg->vnum; j++)
{
printf("%d ", mg->Adjacency_matrix[i][j]);
}
printf("\n");
}
return 0;
}
void Create_Graphic(Graphic g)
{
//输入你所需要创建的图的种类
Gkind k;
printf("请输入你所需要创建的图的种类,1代表无向图,2代表无向网,3代表有向图,4代表有向网\n");
scanf("%d", &k);
g->kind = k;
//依次输入图的顶点的数目和图的数目
printf("请输入图中所含有的顶点的数目以及边或者弧的数目\n");
scanf("%d%d", &(g->vnum), &(g->EaNum));
getchar();
//输入图的顶点信息
printf("请输入图中的顶点元素的值:\n");
for (int i = 0; i < g->vnum; i++)
{
scanf("%c", &(g->VInfo[i]));
getchar();
}
//输入边或者弧的信息,即某两个顶点之间是否存在边或者存在弧;
switch (k)
{
case UDG:printf("判断图中两个顶点之间是否存在边,若存在则输入1,若不存在则输入0\n"); break;
case UDN:printf("判断图中两个顶点之间是否存在边,若存在则输入边的权值大小,若不存在则输入-1\n"); break;
case DG:printf("判断图中两个顶点之间是否存在弧,若存在则输入1,若不存在则输入0\n"); break;
case DN:printf("判断图中两个顶点之间是否存在弧,若存在则输入弧的权值大小若不存在则输入-1\n");
default:break;
}
for (int i = 0; i < g->vnum; i++)
{
for (int j = 0; j < g->vnum; j++)
{
printf("%c->%c:", g->VInfo[i], g->VInfo[j]);
scanf("%d", &g->Adjacency_matrix[i][j]);
}
}
printf("图创建成功\n");
}
运行结果截图,测试用例中给出了创建四种图,即无向图,无向网,有向图,有向网的测试结果:
若我们直接去看书那么书上对邻接表的定义是这样的:邻接表是图的一种链式存储结构,在邻接表中,对图中的每个顶点建立一个单链表,第i个单链表中的结点表示依附于顶点vi的边(对于有向图而言是以顶点vi为尾的弧),每个结点由三个域组成,其中邻接点域(adjvex)指示与顶点vi邻接的点在图中的位置,链域(nextarc)指示下一条边或者弧的结点;数据域存储的是与边或者弧有关的信息如权值信息等;每个链表附设一个表头节点,在表头结点中除了设有链域(firstarc)指向单链表的第一个节点以外还有存储顶点vi有关的信息的数据域;
直接去看书上对于邻接表的定义可能会觉得有点难以理解,其实是这样的,我们为无向图中的每一个顶点建立一个单链表,该单链表具有表头结点,表头结点有两个域,一个是数据域用于存放该顶点的值信息,另一个域是指针域,这个指针域指向我们为该顶点建立的单链表的第一个结点,也就是第一个非表头结点,然后将我们为所有顶点建立的单链表的头节点存储在一维数组中,按照顺序存储,既然是为每一个顶点建立一个单链表那么此单链表除了表头结点之外还有其他节点,图中某个顶点vi的单链表除了表头结点之外的其他节点是用于存放无向图边的信息的,顶点vi的单链表除了表头结点之外的其它结点的节点结构和顶点vi的单链表表头结点的结点结构不一样,其它结点的结点结构有两个数据域,一个数据域adjvex是用于存放与该顶点邻接的第一个顶点在数组中的位置也就是下标,而另一个数据域info是用于存放与边相关的信息的,比如权值信息,若没有权值这个数据域可以不要,有一个指针域nextarc用于指向下一个结点,下一个节点的数据域adjvex存放的是与顶点vi邻接的第二个顶点在数组中的下标,info域存放与边有关的信息,如权值信息,若不带取值这个域可以不要,指针域指向单链表中的下一个节点,如此往复直到将与顶点vi邻接的顶点信息都加入到单链表中为止,如图所示结合例子来理解将会更容易理解:
对于有向图而言我们需要建立两个邻接表,一个是正邻接表用于存储以顶点vi为弧尾即以vi作为始点的弧的信息,而另一个邻接表称为逆邻接表,用于存放以顶点vi作为终点的弧的信息
由于所需要创建的图是有向图所以需要两个邻接表来存储此图,分别是正邻接表和逆邻接表,正邻接表用于存储以某个顶点为起点的弧的信息,而你邻接表用于存储以某个顶点为终点的弧的信息
第一步构造邻接表的非头节点结构以及头节点结构以即定义图的结构体结构:
//定义顶点的单链表非头节点节点结构,创建的图是有向无权图,所以非表头结点的节点结构的数据域只有一个,用于存放与该顶点邻接的顶点在数组中的位置即数组下标,没有了数据域info,info是用于存放弧的权值信息的
typedef struct ArcNode
{
int adj_position; //用于存放以该顶点为始点的邻接点在数组中的下标
struct ArcNode* nextarc; //存放下一个结点的信息
}ArcNode, * AN;
//定义图的顶点的头节点结构
typedef struct HeadNode
{
char data; //头节点的数据域用于存放图的顶点信息
AN Pfirstarc; //指向该顶点的正邻接表的第一个结点
AN IVfirstarc; //指向该顶点的逆邻接表的第一个结点
}HeadNode, * HN;
//定义图的结构体类型
typedef struct DG
{
HeadNode hv[N]; //将图的顶点单链表的头节点用一维数组顺序存放
int vnum; //用于存放图的定点数量
int arcnum; //用于存放图的弧的数量
}DG,*dg;
构造好图的结构之后,我们需要一个函数用于为图的每一个顶点创建单链表,由于是有向图,所以实际上需要创建两个单链表,一个单链表存放的是以该顶点为起点的弧的信息,另一个单链表存放的是以该顶点为终点的弧的信息
void Create_LinkList(HN h,int o) //o示我们要为数组中的哪一个顶点创建单链表
{
//建立正向邻接表
AN p = (AN)malloc(sizeof(ArcNode)), q;
q = p;
//为节点的数据域输入值,直到输入-1表示单链表创建完毕
printf("请输入以顶点%c作为始点的邻接点在数组中的下标:\n", h->data);
scanf("%d", &p->adj_position);
while (p->adj_position != -1)
{
if (h->Pfirstarc == NULL)
{
h->Pfirstarc = p;
}
else
{
q->nextarc = p;
q = q->nextarc;
}
p = (AN)malloc(sizeof(ArcNode));
printf("请输入以顶点%c为始点的下一个邻接点在数组中的下标:\n", h->data);
scanf("%d", &p->adj_position);
}
//建立逆向邻接表
q = p;
//为节点的数据域输入值,直到输入-1表示单链表创建完毕
printf("请输入以顶点%c作为终点的邻接点在数组中的下标:\n", h->data);
scanf("%d", &p->adj_position);
while (p->adj_position != -1)
{
if (h->IVfirstarc == NULL)
{
h->IVfirstarc = p;
}
else
{
q->nextarc = p;
q = q->nextarc;
}
p = (AN)malloc(sizeof(ArcNode));
printf("请输入以顶点%c为终点的下一个邻接点在数组中的下标:\n", h->data);
scanf("%d", &p->adj_position);
}
printf("第顶点%c的正单链表和逆单链表均创建成功\n", h->data);
return;
}
之后就是创建图了
void Create_DG(dg mydg, int n)
{
mydg->vnum = n;
printf("请输入该有向无权图的弧的数目:\n");
scanf("%d", &(mydg->arcnum));
getchar();
//注意先将头节点的指针域设置为空指针
for (int i = 0; i < n; i++)
{
printf("请输入第%d个顶点的值:\n", i + 1);
scanf("%c", &(mydg->hv[i]).data);
(mydg->hv[i]).Pfirstarc = NULL;
(mydg->hv[i]).IVfirstarc = NULL;
getchar();
}
//为每一个顶点创建单链表
for (int i = 0; i < n; i++)
{
Create_LinkList(&(mydg->hv[i]), i);
}
printf("图创建成功\n");
return;
}
完整程序源代码
//图的链式存储结构实现,邻接表,
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#define N 6
//定义顶点的单链表非头节点节点结构,创建的图是有向无权图,所以非表头结点的节点结构的数据域只有一个,用于存放与该顶点邻接的顶点在数组中的位置即数组下标,没有了数据域info,info是用于存放弧的权值信息的
typedef struct ArcNode
{
int adj_position; //用于存放以该顶点为始点的邻接点在数组中的下标
struct ArcNode* nextarc; //存放下一个结点的信息
}ArcNode, * AN;
//定义图的顶点的头节点结构
typedef struct HeadNode
{
char data; //头节点的数据域用于存放图的顶点信息
AN Pfirstarc; //指向该顶点的正邻接表的第一个结点
AN IVfirstarc; //指向该顶点的逆邻接表的第一个结点
}HeadNode, * HN;
//定义图的结构体类型
typedef struct DG
{
HeadNode hv[N]; //将图的顶点单链表的头节点用一维数组顺序存放
int vnum; //用于存放图的定点数量
int arcnum; //用于存放图的弧的数量
}DG,*dg;
///以下函数用于创建一个单链表
void Create_LinkList(HN a,int o);
//以下函数用于创建图
void Create_DG(dg mydg,int n);
int main()
{
int n;
printf("请输入图中的顶点个数\n");
scanf("%d", &n);
DG g;
dg my_dg = &g;
Create_DG(my_dg, n);
return 0;
}
void Create_LinkList(HN h,int o) //o示我们要为数组中的哪一个顶点创建单链表
{
//建立正向邻接表
AN p = (AN)malloc(sizeof(ArcNode)), q;
q = p;
//为节点的数据域输入值,直到输入-1表示单链表创建完毕
printf("请输入以顶点%c作为始点的邻接点在数组中的下标:\n", h->data);
scanf("%d", &p->adj_position);
while (p->adj_position != -1)
{
if (h->Pfirstarc == NULL)
{
h->Pfirstarc = p;
}
else
{
q->nextarc = p;
q = q->nextarc;
}
p = (AN)malloc(sizeof(ArcNode));
printf("请输入以顶点%c为始点的下一个邻接点在数组中的下标:\n", h->data);
scanf("%d", &p->adj_position);
}
//建立逆向邻接表
q = p;
//为节点的数据域输入值,直到输入-1表示单链表创建完毕
printf("请输入以顶点%c作为终点的邻接点在数组中的下标:\n", h->data);
scanf("%d", &p->adj_position);
while (p->adj_position != -1)
{
if (h->IVfirstarc == NULL)
{
h->IVfirstarc = p;
}
else
{
q->nextarc = p;
q = q->nextarc;
}
p = (AN)malloc(sizeof(ArcNode));
printf("请输入以顶点%c为终点的下一个邻接点在数组中的下标:\n", h->data);
scanf("%d", &p->adj_position);
}
printf("第顶点%c的正单链表和逆单链表均创建成功\n", h->data);
return;
}
void Create_DG(dg mydg, int n)
{
mydg->vnum = n;
printf("请输入该有向无权图的弧的数目:\n");
scanf("%d", &(mydg->arcnum));
getchar();
//注意先将头节点的指针域设置为空指针
for (int i = 0; i < n; i++)
{
printf("请输入第%d个顶点的值:\n", i + 1);
scanf("%c", &(mydg->hv[i]).data);
(mydg->hv[i]).Pfirstarc = NULL;
(mydg->hv[i]).IVfirstarc = NULL;
getchar();
}
//为每一个顶点创建单链表
for (int i = 0; i < n; i++)
{
Create_LinkList(&(mydg->hv[i]), i);
}
printf("图创建成功\n");
return;
}
程序运行结果截图