数据结构与算法--图的定义及存储结构

一、图的定义和术语

      1.图的定义

          图: G=(V,E)

               V: 顶点(数据元素)的有穷非空集合

               E: 边的有穷集合

           1) 无向图: 每条边都是无方向的

           2) 有向图: 每条边都是有方向的

           3) 完全图: 任意两个点都有一条边相连

           4) 稀疏图: 有很少边或弧的图(e<nlogn)

           5) 稠密图: 有较多边或弧的图

           6) 网: 边/弧带权的图

           7) 邻接: 有边/弧相连的两个顶点之间的关系(圆括号是无向图,尖括号是有向图)

                         存在(Vi,Vj),则称Vi和Vj互为邻接点

                         存在<Vi,Vj>,则称Vi邻接到Vi,Vj邻接于Vi

           8) 关联(依附): 边/弧与顶点之间的关系

                          存在(Vi,Vj)/<Vi,Vj>,则称该边/弧关联于Vi和Vj

           9) 顶点的度: 与该顶点相关联的边的数目,记为TD(v)

             在有向图中,顶点的度等于该顶点的入度出度之和

                     顶点v的入度是以v为终点的有向边的条数,记作ID(v)

                     顶点v的出度是以v为始点的有向边的条数,记作OD(v)

           10) 当有向图中仅1个顶点的入度为0,其余顶点的入度均为1,此时是何形状?

                   答: 是树!而且是一棵有向树!

      2.图的相关概念

             1) 路径: 接续的边构成的顶点序列

             2) 路径长度: 路径上边或弧的数目/权值之和

             3) 回路(环): 第一个顶点和最后一个顶点相同的路径

             4) 简单路径: 除路径起点和终点可以相同外,其余顶点均不相同的路径

             5) 简单回路(简单环): 除路径起点和终点相同外,其余顶点均不相同的路径

             6) 连通图(强连通图): 在无(有)向图G={V,{E}}中,若对任何两个顶点v、u都存在从v、u的路径,则称G是连通图(强连通图)

             7) 权与网: 图中边或弧所具有的相关数称为权,表明从一个顶点到另一个顶点的距离或耗费

                               带权的图称为

             8) 子图: 设有两个图G=(V,{E})、G1=(V1,{E1}),若V_{1}\subseteq V,E_{1}\subseteq E,则称G1是G的子图

      3.连通分量

              1) 无向图G的极大连通子图称为G的连通分量

              2) 极大连通子图:该子图是G的连通子图,将G的任何不在该子图中的顶点加入,子图不再连通

              3) 有向图G的极大强连通子图称为G的连强通分量

             4) 极大强连通子图:该子图是G的强连通子图,将G的任何不在该子图中的顶点加入,子图不再强连通

              4) 极小连通子图:该子图是G的连通子图,在该子图中删除任何一条边,子图不再连通

              5) 生成树: 包含无向图G所有顶点的极小连通子图

              6) 生成森林: 对非连通图,由各个连通分量的生成树的集合

二、图的存储结构

      1.数组(邻接矩阵)表示法

          建立一个顶点表(记录各个顶点信息)和一个邻接矩阵(表示各个顶点之间关系)

              * 设图A=(V,E)有n个顶点,则     

顶点表Vexs[n]
i012...n-1
Vexs[i]V1V2V3...Vn

              * 图的邻接矩阵是一个二维数组A.arc[n][n],定义为:

                         如果<i,j>属于E或者(i,j)属于E,则A.arcs[i][j]=1

                         否则A.arcs[i][j]=0

             1) 无向图的邻接矩阵表示法:

             2) 有向图的邻接矩阵表示法:

             3) 网(即有权图)的邻接矩阵表示法:

     2.邻接矩阵的存储表示:两个数组分别存储顶点表邻接矩阵

#define MVNum 100                                //最大顶点数     
#define MaxInt 32767                            //表示极大值,即∞

typedef char VerTexType;                         //设顶点的数据类型为字符型
typedef int ArcType;					//假设边的权值类型为整型
typedef struct
{
	VerTexType vexs[MVNum];                 //顶点表
	ArcType arcs[MVNum][MVNum];       //邻接矩阵
	int vexnum, arcnum;                        //图的当前顶点数和边数
}AMGraph;

      3.采用邻接矩阵表示法创建无向网

      1) 输入总顶点数和总边数

      2) 依次输入点的信息存入顶点表中

      3) 初始化邻接矩阵,使每个权值初始化为极大值

      4) 构造邻接矩阵

int LocateVex(AMGraph *G, VerTexType u)       //在图G中查找顶点u
{
	for (int i = 0; i < G->vexnum; i++)
	{
		if (u == G->vexs[i])
			return i;                    //在则返回顶点表中的下标
	}
	return -1;                            //否则返回-1
}

void CreateUDN(AMGraph *G)                        //采用邻接矩阵表示法创建无向网
{
	printf("请输入顶点数: ");                                //输入顶点数
	scanf("%d", &G->vexnum);
	printf("请输入边数: ");                                  //输入总边数
	scanf("%d", &G->arcnum);
	for (int i = 0; i < G->vexnum; i++)
	{
		scanf("%c", &G->vexs[i]);                      //依次输入点的信息
	}
	for (int i = 0; i < G->vexnum; i++)
	{
		for (int j = 0; j < G->vexnum; j++)
		{
			G->arcs[i][j] = MaxInt;                      //边的权值均置为极大值
		}
	}
	for (int k = 0; k < G->arcnum; k++)         //构造邻接矩阵
	{
		VerTexType v1, v2;              
		ArcType w;
		printf("请输入一条边所依附的两个顶点: ");
		scanf("%c %c", &v1, &v2);                               //输入一条边所依附的顶点
		printf("\n请输入边的权值: ");
		scanf("%d", &w);                                      //输入边的权值
		putchar('\n');
		int i = LocateVex(G, v1);
		int j = LocateVex(G, v2);                              //确定v1和v2在G中的位置
		G->arcs[i][j] = w;                                         //边的权值置为w
		G->arcs[j][i] = G->arcs[i][j];                         //对应对称权值也设为w
	}
}

      4.邻接矩阵表示法的优缺点

       优点:

            1) 直观、简单、好理解

            2) 方便检查任意一对顶点间是否存在边

            3) 方便找任一顶点的所有"邻接点"(有边直接相连的顶点)

            4) 方便计算任一顶点的"度"(从该点发出的边数为"出度",指向该点的边数为"入度")

                    //无向图: 对应行(或列)非0元素的个数

                    //有向图: 对应行非0元素的个数是"出度";对应列非0元素的个数是"入度"

       点:

            1) 不便于增加和删除顶点

            2) 浪费空间----存稀疏图(点很多而边很少)有大量无效元素

            3) 浪费时间----统计稀疏图中一共有多少条边

      5.邻接表表示法(链式)

             1) 无向图的邻接表表示法:

             2) 有向图的邻接表表示法:

      6.图的邻接表存储表示

#define MVNum 100                      //最大顶点数

typedef char VerTexType;            
typedef struct ArcNode                //边结点
{ 
	int avjvex;                                   //该边所指向的顶点位置
	int info;                                      //和边相关的信息
	struct ArcNode *nextarc;           //指向下一条边的指针
}ArcNode;
typedef struct VNode                      //顶点
{
	VerTexType data;                               //顶点信息
	ArcNode *firstarc;                              //指向第一条依附该顶点的边的指针
}VNode,AdjList[MVNum];                    //AdjList表示邻接表类型
typedef struct                              //图的结构定义
{
	AdjList vertices;                          //vertices--vertex的复数
	int vexnum, arcnum;                 //图的当前顶点数和边数
}ALGraph;

      7.采用邻接表表示法创建无向网

         算法思想:

             1) 输入总顶点数和边数

             2) 建立顶点表

                 *依次输入点的信息存入顶点表中

                 *使每个表头结点的指针域初始化为NULL

             3) 创建邻接表

                 *依次输入每条边依附的两个顶点

                 *确定两个顶点的序号i和j,建立边结点

                 *将此边结点分别插入到Vi和Vj对应的两个边链表的头部

int LocateVex(ALGraph *G, VerTexType u)
{
	for (int z = 0; z < G->vexnum; z++)
	{
		if (G->vertices[z].data == u)
			return z;
	}
	return -1;
}

void CreateUDG(ALGraph *G)                              //采用邻接表表示法,创建无向图G
{
	VerTexType v1, v2;
	int i, j,w;
	printf("请输入总顶点数: ");                                 //输入总顶点数
	scanf("%d", &G->vexnum);
	printf("请输入总边数: ");                                      //输入总边数
	scanf("%d", &G->arcnum);
	for (int i = 0; i < G->vexnum; i++)                       //输入各点,构造表头结点表
	{ 
		scanf("%c", &G->vertices[i].data);                   //输入顶点值
		G->vertices[i].firstarc = NULL;                        //初始化表头结点的指针域
	}
	for (int k = 0; k <(2*G->arcnum); k++)               //输入各边,构造邻接表
	{
		printf("请输入第一个顶点: ");                    //输入一条边依附的第一个顶点
		scanf("%c", &v1);
		printf("请输入第二个顶点: ");                    //输入一条边依附的第二个顶点
		scanf("%c", &v2);
		i = LocateVex(G, v1);
		j = LocateVex(G, v2);
		printf("请输入边的权值: ");
		scanf("%d", &w);
		ArcNode *p1 = (ArcNode*)malloc(sizeof(ArcNode));      //生成一个新的边结点p1
		p1->avjvex = j;                                          //邻接点序号为j
		p1->info = w;                                          //赋权值
		p1->nextarc=G->vertices[i].firstarc;
		G->vertices[i].firstarc = p1;                 //将新结点*p1插入顶点Vi的边表头部
		ArcNode *p2 = (ArcNode*)malloc(sizeof(ArcNode));      //生成一个新的边结点p2
		p2->avjvex = i;                                          //邻接点序号为i
		p2->info = w;                                             //赋权值
		p2->nextarc = G->vertices[j].firstarc;
		G->vertices[j].firstarc = p2;                //将新结点*p2插入顶点Vj的边边表头部		
	}
}

      8.邻接表表示法的优缺点

         1) 方便找任一顶点的所有邻接点

         2) 节约稀疏图的空间--需要N个头指针+2E个边结点(每个边结点至少两个域)

         3) 方便计算无向图中任一顶点的度

         4) 对有向图来说:只能计算"出度";需要构造"逆邻接表"(存指向自己的边)来方便计算"入度"

         5) 不方便检查任意一对顶点间是否存在边

      9.邻接矩阵与邻接表表示法的关系

              1)联系: 邻接表中每个链表对应邻接矩阵中的一行,链表中结点个数等于一行中非零元素的个数

              2)区别: 

                  (1).对于任一确定的无向图,邻接矩阵是唯一的(行列号与顶点编号一致),但邻接表不唯一(链接次序与顶点编号无关)

                  (2).邻接矩阵的空间复杂度为O(n*n),而邻接表的空间复杂度为O(n+e)

              3)用途: 邻接矩阵多用于稠密图;而邻接表多用于稀疏图

      10.十字链表(有向图)

               1) 十字链表有向图的另一种链式存储结构(可以看成将有向图的邻接表和逆邻接表结合起来形成的一种链表)

               2) 有向图中的每一条弧对应十字链表中的一个弧结点

               3) 有向图中的每个顶点在十字链表中对应有一个结点,叫作顶点结点

      11.邻接多重表(无向图)

 


版权声明:本文为SONG_YIH原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。