数据结构——图

数据结构—图

图的定义与基本术语

  • :G=(V, E)

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

    E: 边的有穷集合

  • 有向图和无向图:前者边有方向,后者边无方向

数据结构——图_第1张图片 数据结构——图_第2张图片

  • 完全图:任意两个点都有一条边相连
    • 无向完全图:一张图中每条边都是无方向的;在无向完全图中;在无向完全图中:n个顶点,有n(n-1)/2条边
    • 有向完全图:图中各边都有方向,且每两个顶点之间都有两条方向相反的边连接的图;在有向完全图中:n个顶点,有**n(n-1)**条边

数据结构——图_第3张图片

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

  • 稠密图:有较多边或弧

  • :边/弧带权的图

  • 邻接:有边/弧相连的两个顶点之间的关系 ()无向图,无先后关系;<>有向图,有先后关系

    • 存在(Vi,Vj),则称Vi和Vj互为邻接点
    • 存在i,Vj>,则称Vi邻接到Vj,则称Vj邻接于Vi
  • 关联(依附):边/弧与顶点之间的关系。

    • 存在(Vi,Vj)/i,Vj>,则称该边/弧关联于Vi和Vj
  • 顶点的度:与该顶点相关联的边的数目,记为TD(v)

    • 在有向图中,顶点的度等于该顶点的入度与出度之和
      • 入度:以当前顶点为终点的有向边的条数,记作ID(v)
      • 出度:以当前顶点为始点的有向边的条数,记作OD(v)

数据结构——图_第4张图片

  • 当有向图中仅有一个顶点的入度为0,其余顶点的入度均为1(出度任意),则该图为树形,称为有向树

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

数据结构——图_第5张图片

  • 路径长度:路径上边或弧的数目/权值之和 (有权值按权值算)

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

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

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

    数据结构——图_第6张图片

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

数据结构——图_第7张图片

数据结构——图_第8张图片

  • 权与网

    • ​ 权:图中边或弧具有的相关树称为权,表明从一个顶点到另一个顶点的距离或耗费
    • ​ 网:带权的图称为网
  • 子图

    设有两个图G = (V, {E}), G1 = (V1, {E1}), 若V1∈V,E1∈E,则称G1是G的子图 (即顶点和边都是图的子集)

    数据结构——图_第9张图片

  • 连通分量 (强连通分量)

    • 连通分量:无向图G的极大连通子图称为G的连通分量
      • 极大连通子图:该子图是G的连通子图D,将G中任何一个不在D中的顶点加入,子图不再连通的,则该子图称为极大连通子图
    • 强连通分量:有向图G的极大连通子图称为G的强连通分量
      • 极大连通子图:该子图是G的连通子图D,将G中任何一个不在D中的顶点加入,子图不再连通的,则该子图称为极大连通子图

数据结构——图_第10张图片

单顶点就是强连通分量

  • 极小连通子图:该子图是G的连通子图,在该子图中删除任何一条边,该子图不再连通
  • 生成树:包含无向图G所有顶点的极小连通子图
  • 生成森林:对非连通图,由各个连通分量的生成树的集合

小世界理论

你和任何一个陌生人之间所间隔的人不会超过五个,也就是说,最多通过五个人你就能够认识任何一个陌生人。”根据这个理论,你和世界上的任何一个人之间只隔着五个人,不管对方在哪个国家,属哪类人种,是哪种肤色。

将小世界理论的人际关系网络抽象成一个无向图G,用图G中的一个顶点表示一个人,两个人认识与否用代表这两个人的顶点之间是否有一条边来表示。从任一顶点出发用广度优先对图进行遍历,统计所有长度不超过7的顶点

图的存储结构

图的逻辑结构:多对多

邻接矩阵:借助二维数组来表示图的元素间的关系

链式存储结构:由于图中顶点多对多的特性,很难确定需要多少个指针域,因此有如下几个常用的链式存储方式:邻接表邻接多重表十字链表

1.邻接矩阵

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

    • 设图 A = (V, E)有 n 个顶点,则顶点表表示为:image-20200308183102678

    • 图的邻接矩阵表是一个二维数组 A.arcs[n][n], 定义为image-20200308183250459

      如果存在一条边 属于A中的边,则存在该边,记为1 (自身与自身无边)

      eg:数据结构——图_第11张图片

      PS: “邻接”,相邻且相接

1.特点:无向图的邻接矩阵是对称矩阵,且对角线上元素为0,因为当有一个顶点到另一个顶点存在边时,另一个顶点到该顶点也一定存在一条边

2.统计某一个顶点的度是多少只需遍历该顶点对应的数组,计算1的次数即可,即顶点 i 的度 = 第i行(列)中 1 的个数

3.特别的:无向完全图的邻接矩阵中,对角元素为0,其余均为1

1.2有向图的邻接矩阵

有向图存在方向,因此只有当存在当前顶点发出的弧时,才记为1 (和无向图的邻接矩阵一样,自身与自身无弧)

数据结构——图_第12张图片

在有向图的邻接矩阵中:

  • 第 i 行:表示以结点Vi为尾的弧(即出度边)
  • 第 i 列:表示以结点Vi为头的弧(即入度边)

1.有向图的邻接矩阵的特点:对角线元素为0,但不一定是对称矩阵

2.对有向图的邻接矩阵而言:

  • 顶点 i 的出度 = 第 i 行的元素之和
  • 顶点 i 的入度 = 第 i 列的元素之和
  • 顶点 i 的度 = 第 i 行元素之和 + 第 i 列元素之和

3.特别的:有向完全图的对角线元素全0,取余元素全1

1.3网的邻接矩阵

网即有权图

网的邻接矩阵定义为:image-20200309103742151

如果存在边 / 弧属于VR图,则邻接矩阵记录该条边的权值Wij;否则记录INF

1.3.1有向网的邻接矩阵
数据结构——图_第13张图片
1.4 无向网的存储结构

两个数组 :顶点表和邻接矩阵

//最大顶点数
#define MAX_VEX_NUM 100
//假的无穷大,填补邻接矩阵中没有邻接关系的点
#define MAX_INT 2147483647

typedef struct Graph
{
   
     //顶点表
	顶点数据类型 vexs[MAX_VEX_NUM];
     //邻接矩阵表
     边的类型    arcs[MAX_VEX_NUM][MAX_VEN_NUM];
     //顶点数和边数
     int vexNum, arcNum;
}AMGraph;
1.4.1采用邻接矩阵创建无向网
  1. 输入总顶点数和总边数
  2. 依次输入顶点的信息存入顶点表
  3. 初始化邻接矩阵,因为是在网当中,所以使每个权值初始化为最大值
  4. 构造邻接矩阵
1.4.1.1实现

构造

void creatGraph(AMGraph* graph)
{
	printf("请输入顶点数和边数:");
	//1.顶点数和边数
	scanf("%d  %d", &(graph->vexNum), &(graph->arcNum));
	setbuf(stdin, NULL);
	putchar('\n');

	//顶点数据
	for (int i = 0; i < graph->vexNum; i++)
	{
		printf("请输入第%d个顶点数据:", i + 1);
		scanf("%c", &(graph->vexs[i]));
		setbuf(stdin, NULL);
		putchar('\n');
	}
	//3.初始化边, n个顶点就有
	for (int i = 0; i < graph->vexNum; i++)
	{
		for (int j = 0; j < graph->vexNum; j++)
		{
			if(i!=j)
				graph->arcs[i][j] = MAX_INT;
			else
				graph->arcs[i][j] = 0;
		}
	}
	//4.构造邻接矩阵
	for (int i = 0; i < graph->arcNum; i++)
	{
		char v1, v2;
		int weight;
		printf("请输入第%d条边依附的顶点及权值:", i+1);
		scanf("%c %c %d", &v1, &v2, &weight);
		setbuf(stdin, NULL);
		putchar('\n');
		//找到边依附的两个顶点,记录下标,就是在邻接矩阵中的位置
		int i = findVex(graph, v1);
		int j = findVex(graph, v2);
		//如果没查找到顶点或该顶点已经有依附的边
		if (i == -1 || j == -1 || graph->arcs[i][j] != MAX_INT)
		{
			printf("不存在该顶点或该顶点已有依附的边\n");
			exit(-1);
		}
		//对称赋值
		graph->arcs[i][j] = weight;
		graph->arcs[j][i] = weight;
	}
}

查找顶点是否存在

int findVex(AMGraph* graph, char vex)
{
   
	for (int i = 0; i < graph->vexNum; i++)
	{
   
		if (vex == graph->vexs[i])
		{
   
			return i;
		}
	}
	return -1;
}
1.4.2邻接矩阵创建无向图和有向网

无向图:和无向网区别就是无向图的边不存在权值,因此初始化邻接矩阵时边均为0,构造邻接矩阵时,存在的边为1

有向网:有向网的邻接矩阵不一定时对称矩阵,因此仅需为graph->arcs[i][j]赋值,而不需要在对称元素上赋值

邻接矩阵不便于增加和删除顶点;当边数较少时比较浪费空间

2.邻接表

2.1无向图的邻接表

数据结构——图_第14张图片

  • 顶点:按编号顺序将顶点存放在一维数组中,一维数组中每个元素都有指针域和数据域image-20200309184223677

  • 边:用链表存储关联了同一顶点的所有边;每个结点有两个数据image-20200309184515809adjvex为邻接顶点,nextarc为下一条边;eg:image-20200309184647422代表v1有两条边,分别邻接的是在顶点数组中下标为3的顶点和下标为1的顶点

    当为无向网时,可以给表结点增加数据域以存放权值

无向图邻接表的特点:

  • 邻接表不唯一,关联了同一顶点的边在链表中的位置可以任意
  • 每一条边会在一个邻接表中出现2次,因此在无向图的邻接表当中,若有 n 个顶点,e 条边, 则其邻接表需要 n 个头结点和 2e 个表结点
  • 用邻接表表示的无向图中顶点vi的度为第 i 个单链表中的结点数
2.1.1实现

实现步骤:

  1. 输入顶点数和边数
  2. 建立顶点表,依次输入顶点数据,并初始化顶点表中每个顶点的指针域为NULL
  3. 创建临邻接表
    1. 依次输入每条边依附的两个顶点( 起点,终点 )
    2. 确定两个顶点的下标 i 和 j ,建立两个边结点,一个结点的head存放起点的下标,另一个存放终点的下标
    3. 存放起点下标的边结点链接到对应的终点顶点上,存放终点下标的边结点链接到对应的起点顶点上
  • 结构
//边结构
typedef struct GraphNode
{
   
	int head;
	struct GraphNode* nextArc;
}gNode;
//顶点表
typedef struct Head
{
   
	char data;
	gNode* firstArc;
}head;

//图的基本结构
typedef struct ALGraph
{
   
	head gHead[MAX_VEX];
	int vexNum, arcNum;  //顶点数和边数
}ALgraph;
  • 构造
void creatGraph(ALgraph* graph)
{
   
	printf("请输入顶点数和边数: ");
	scanf("%d %d", &(graph->vexNum), &(graph->arcNum));
	setbuf(stdin, NULL);
	putchar('\n');

	//顶点赋值
	for (int i = 0; i < graph->vexNum; i++)
	{
   
		char c;
		printf("请输入第%d个顶点的数据:", i+1);
		scanf("%c", &c);

		graph->gHead[i].data = c;
		graph->gHead[i].firstArc = NULL;

		setbuf(stdin, NULL);
		putchar('\n');
	}
	
	//边结点链接
	for (int i = 0; i < graph->arcNum; i++)
	{
   
		
		char v1, v2;

		printf("请输入第%d条边依附的结点:", i + 1);
		scanf("%c %c", &v1, &v2);
		setbuf(stdin, NULL);

		int i = indexOfVex(graph, v1);
		int j = indexOfVex(graph, v2);

		//生成两个结点,因为一条边会关联两个顶点,两个顶点都会链接上这条边
		//只是不同顶点对应的边结点的head值不一样
		gNode* g = (gNode*)malloc(sizeof(gNode));
		g->head = j;
		g->nextArc = NULL;

		gNode* g2 = (gNode*)malloc(sizeof(gNode));
		g2->head = i;
		g2->nextArc = NULL;
	
		
		/*头插,在对应的关联边链表中进行插入
		如果用尾插法,需要记录上一次的尾结点,但上一次的尾指针所指向的边结点
		不一定就在当次循环中找到的顶点的边链表当中
		例如:1. a b  a-->1  b-->0   pre-->1
		     2. b c  b-->0   b由于在上次时已经有长子了
		而pre此时还是指向a的长子1,pre-->next = g的话,就会变成 a-->1-->2 b-->0,这显然不是我们想要的*/
		//链接到对应顶点,存终点下标的链接到起点顶点,存起点下标的链接到终点顶点
		g->nextArc = graph->gHead[i].firstArc;
		graph->gHead[i].firstArc = g;
		
		g2->nextArc = graph->gHead[j].firstArc;
		graph->gHead[j].firstArc = g2;
			
	}
}
2.2有向图的邻接表

数据结构——图_第15张图片

有向图的邻接表创建与无向图的邻接表创建区别仅在于:在构造有向图时,由于弧是有方向的,因此只需构造一条出度边依附在出度点上即可,该边的邻接点域值head存储入度点的下标;而在无向图中每条边需要保存两次;因此邻接表表示的无向图空间复杂度记为O(n+2e),邻接表表示的有向图空间复杂度为O(n+e)

2.2.1实现
void creatGraph(ALgraph* graph)
{
   
	printf("请输入顶点数和边数: ");
	scanf("%d %d", &(graph->vexNum), &(graph->arcNum));
	setbuf(stdin, NULL);
	putchar('\n');

	//顶点赋值
	for (int i = 0; i < graph->vexNum; i++)
	{
   
		char c;
		printf("请输入第%d个顶点的数据:", i+1);
		scanf("%c", &c);

		graph->gHead[i].data = c;
		graph->gHead[i].firstArc = NULL;

		setbuf(stdin, NULL);
		putchar

你可能感兴趣的:(C数据结构,数据结构)