06 图

06 图

在这里插入图片描述

定义

图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。

1. 在图中数据元素,我们称之为顶点(Vertex)
2. 在图中,任意两个顶点之间都可能有关系,顶点之间的逻辑关系用边来表示,边集可以是空的。
  1. 各种图

    1. 无向边:

    若顶点vI到vj之间的边没有方向,则称这条边为无向边(Edge),用无序偶对(vI,vj)来表示。如果图中任意两个顶点之间的边都是无向边,则称该图为无向图(Undirected graphs)。

    1. 有向边:

    若从顶点vI到vj的边有方向,则称这条边为有向边,也称为弧(Arc)。如果图中任意两个顶点之间的边都是有向边,则称该图为有向图(Directed graphs)

    1. 无向完全图:

    在无向图中,如果任意两个顶点之间都存在边,则称该图为无向完全图

    1. 有向完全图

    在有向图中,如果任意两个顶点之间都存在方向互为相反的两条弧,则称该图为有向完全图。

    有些图的边或弧具有与它相关的数字,这种与图的边或弧相关的数叫做权(Weight)。这些权可以表示从一个顶点到另一个顶点的距离或耗费。这种带权的图通常称为网(Network)

  2. 图的顶点与边间关系

    1. 路径的长度:

    是路径上的边或弧的数目

  3. 连通图相关术语

    1. 连通图:

    在无向图G中,如果从顶点v到顶点v’有路径,则称v和v’是连通图。如果对于图中任意两个顶点vi、vj属于V,vi和vj都是连通的,则称G是连通图(Connected Graph )

    1. 强连通图:

    在有向图G中,如果对于每一对vi、vj属于V、vi不等于vj,从vi到vj和从vj到vi都存在路径,则称G是强连通图。有向图中的极大强连通子图称做有向图的强连通分量。

  4. 图的定义与术语总结

图的存储结构

ADT图(Graph)

Data
	顶点的有穷非空集合和边的集合
Operation

	CeateGraph(*G, V, VR):按照顶点集V和边弧集VR的定义构造图G
	DestroyGraph(*G):图G存在则销毁 
	LocateVex(G, u):若图G中存在顶点u,则返回图中的位置
	PutVex(G, v, value):将图G中的顶点v赋值value
	FirstAdjVex(G, *v):返回顶点v的一个邻接顶点,若顶点在G中无邻接顶点返回空
	NextAdjVex(G, v, *w):返回顶点v相对于顶点w的下一个邻接顶点,若w是v的最后一个邻接点则返回“空”
	InsertVex(*G, v):在图中增添新顶点v
	DeleteVex(*G, v):删除图G中顶点v及其相关的弧
	InsertArc(*G, v, w):在图G中增添弧<v,w>,若G是无向图,还需要增添对称弧<w,v>。
	DeleteArc(*G, v, w):在图G中删除弧<v,w>,若G是无向图,则还删除对称弧<w,v>
	DFSTraverse(G):对图G中进行深度优先遍历,在遍历过程对每个顶点调用
	HFSTraverse(G):在图G中进行广度优先遍历,在遍历过程对每个顶点调用。

endADT
  1. 邻接矩阵(Adjacency Matrix)

    图的邻接矩阵存储方式使用两个数组来表示图。一个以为数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。

     #define MAXVEX 100 /* 最大顶点数,应由用户定义 */
     #define INFINITY 65535
     
     typedef int Status;	/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
     typedef char VertexType; /* 顶点类型应由用户定义  */
     typedef int EdgeType; /* 边上的权值类型应由用户定义 */
     typedef struct
     {
     	VertexType vexs[MAXVEX]; /* 顶点表 */
     	EdgeType arc[MAXVEX][MAXVEX];/* 邻接矩阵,可看作边表 */
     	int numNodes, numEdges; /* 图中当前的顶点数和边数  */
     }MGraph;
    

    构造邻接矩阵:时间复杂度:O(n2

     /* 建立无向网图的邻接矩阵表示 */
     void CreateMGraph(MGraph *G)
     {
     	int i,j,k,w;
     	printf("输入顶点数和边数:\n");
     	scanf("%d,%d",&G->numNodes,&G->numEdges); /* 输入顶点数和边数 */
     	for(i = 0;i <G->numNodes;i++) /* 读入顶点信息,建立顶点表 */
     		scanf(&G->vexs[i]);
     	for(i = 0;i <G->numNodes;i++)
     		for(j = 0;j <G->numNodes;j++)
     			G->arc[i][j]=INFINITY;	/* 邻接矩阵初始化 */
     	for(k = 0;k <G->numEdges;k++) /* 读入numEdges条边,建立邻接矩阵 */
     	{
     		printf("输入边(vi,vj)上的下标i,下标j和权w:\n");
     		scanf("%d,%d,%d",&i,&j,&w); /* 输入边(vi,vj)上的权w */
     		G->arc[i][j]=w; 
     		G->arc[j][i]= G->arc[i][j]; /* 因为是无向图,矩阵对称 */
     	}
     }
    
  2. 邻接表

    我们把数组与链表相结合的存储方式称为邻接表(Adjacency List)

    邻接表的结点定义

     typedef char VertexType; /* 顶点类型应由用户定义 */
     typedef int EdgeType; /* 边上的权值类型应由用户定义 */
     
     typedef struct EdgeNode /* 边表结点  */
     {
     	int adjvex;    /* 邻接点域,存储该顶点对应的下标 */
     	EdgeType info;		/* 用于存储权值,对于非网图可以不需要 */
     	struct EdgeNode *next; /* 链域,指向下一个邻接点 */
     }EdgeNode;
     
     typedef struct VertexNode /* 顶点表结点 */
     {
     	VertexType data; /* 顶点域,存储顶点信息 */
     	EdgeNode *firstedge;/* 边表头指针 */
     }VertexNode, AdjList[MAXVEX];
     
     typedef struct
     {
     	AdjList adjList; 
     	int numNodes,numEdges; /* 图中当前顶点数和边数 */
     }GraphAdjList;
    

    邻接表的创建:对于n个顶点和e条边来说,本算法的时间复杂度为O(n+e)

     /* 建立图的邻接表结构 */
     void  CreateALGraph(GraphAdjList *G)
     {
     	int i,j,k;
     	EdgeNode *e;
     	printf("输入顶点数和边数:\n");
     	scanf("%d,%d",&G->numNodes,&G->numEdges); /* 输入顶点数和边数 */
     	for(i = 0;i < G->numNodes;i++) /* 读入顶点信息,建立顶点表 */
     	{
     		scanf(&G->adjList[i].data); 	/* 输入顶点信息 */
     		G->adjList[i].firstedge=NULL; 	/* 将边表置为空表 */
     	}
     	
     	
     	for(k = 0;k < G->numEdges;k++)/* 建立边表 */
     	{
     		printf("输入边(vi,vj)上的顶点序号:\n");
     		scanf("%d,%d",&i,&j); /* 输入边(vi,vj)上的顶点序号 */
     		e=(EdgeNode *)malloc(sizeof(EdgeNode)); /* 向内存申请空间,生成边表结点 */
     		e->adjvex=j;					/* 邻接序号为j */                         
     		e->next=G->adjList[i].firstedge;	/* 将e的指针指向当前顶点上指向的结点 */
     		G->adjList[i].firstedge=e;		/* 将当前顶点的指针指向e */               
     		
     		e=(EdgeNode *)malloc(sizeof(EdgeNode)); /* 向内存申请空间,生成边表结点 */
     		e->adjvex=i;					/* 邻接序号为i */                         
     		e->next=G->adjList[j].firstedge;	/* 将e的指针指向当前顶点上指向的结点 */
     		G->adjList[j].firstedge=e;		/* 将当前顶点的指针指向e */               
     	}
     }
    
  3. 十字链表(Orthogonal List)

    把邻接表和逆邻接表结合起来,解决展示有向图的问题,就有了十字链表

  4. 邻接多重表

  5. 边集数组

图的遍历

从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次,这一过程就叫做图的遍历(Traversing Graph)

  1. 深度优先遍历

    深度优先遍历(Depth_First_Search),也有称为深度优先搜索,简称为DFS。深度优先遍历其实就是一个递归的过程,其实也是一个树的前序遍历过程。

    邻接矩阵的深度优先遍历

     /* 邻接矩阵的深度优先递归算法 */
     void DFS(MGraph G, int i)
     {
     	int j;
      	visited[i] = TRUE;
      	printf("%c ", G.vexs[i]);/* 打印顶点,也可以其它操作 */
     	for(j = 0; j < G.numVertexes; j++)
     		if(G.arc[i][j] == 1 && !visited[j])
      			DFS(G, j);/* 对为访问的邻接顶点递归调用 */
     }
     
     /* 邻接矩阵的深度遍历操作 */
     void DFSTraverse(MGraph G)
     {
     	int i;
      	for(i = 0; i < G.numVertexes; i++)
      		visited[i] = FALSE; /* 初始所有顶点状态都是未访问过状态 */
     	for(i = 0; i < G.numVertexes; i++)
      		if(!visited[i]) /* 对未访问过的顶点调用DFS,若是连通图,只会执行一次 */ 
     			DFS(G, i);
     }
    

    邻接表的深度优先遍历

     /* 邻接表的深度优先递归算法 */
     void DFS(GraphAdjList GL, int i)
     {
     	EdgeNode *p;
      	visited[i] = TRUE;
      	printf("%c ",GL->adjList[i].data);/* 打印顶点,也可以其它操作 */
     	p = GL->adjList[i].firstedge;
     	while(p)
     	{
      		if(!visited[p->adjvex])
      			DFS(GL, p->adjvex);/* 对为访问的邻接顶点递归调用 */
     		p = p->next;
      	}
     }
     
     /* 邻接表的深度遍历操作 */
     void DFSTraverse(GraphAdjList GL)
     {
     	int i;
      	for(i = 0; i < GL->numVertexes; i++)
      		visited[i] = FALSE; /* 初始所有顶点状态都是未访问过状态 */
     	for(i = 0; i < GL->numVertexes; i++)
      		if(!visited[i]) /* 对未访问过的顶点调用DFS,若是连通图,只会执行一次 */ 
     			DFS(GL, i);
     }
    

    总结:对于两个不同存储结构的深度优先遍历算法,对于n个顶点e条边的图来说,邻接矩阵由于是二维数组,要查找每个顶点的邻接点需要访问矩阵中的所有元素,因此都需要O(n2)的时间。而邻接表做存储结构时,找邻接点所需要的时间取决于顶点和边的数量,所以是O(n+e)。显然对于点多边少的稀疏图来说,邻接表的结构使得算法在时间效率上大大提高。

  2. 广度优先遍历

    广度优先遍历(Breadth_First_Search),又称为广度优先搜索,简称BFS。

    邻接矩阵的广度优先遍历

     /* 邻接矩阵的广度遍历算法 */
     void BFSTraverse(MGraph G)
     {
     	int i, j;
     	Queue Q;
     	for(i = 0; i < G.numVertexes; i++)
            	visited[i] = FALSE;
         InitQueue(&Q);		/* 初始化一辅助用的队列 */
         for(i = 0; i < G.numVertexes; i++)  /* 对每一个顶点做循环 */
         {
     		if (!visited[i])	/* 若是未访问过就处理 */
     		{
     			visited[i]=TRUE;		/* 设置当前顶点访问过 */
     			printf("%c ", G.vexs[i]);/* 打印顶点,也可以其它操作 */
     			EnQueue(&Q,i);		/* 将此顶点入队列 */
     			while(!QueueEmpty(Q))	/* 若当前队列不为空 */
     			{
     				DeQueue(&Q,&i);	/* 将队对元素出队列,赋值给i */
     				for(j=0;j<G.numVertexes;j++) 
     				{ 
     					/* 判断其它顶点若与当前顶点存在边且未访问过  */
     					if(G.arc[i][j] == 1 && !visited[j]) 
     					{ 
      						visited[j]=TRUE;			/* 将找到的此顶点标记为已访问 */
     						printf("%c ", G.vexs[j]);	/* 打印顶点 */
     						EnQueue(&Q,j);				/* 将找到的此顶点入队列  */
     					} 
     				} 
     			}
     		}
     	}
     }
    

    邻接表的广度优先遍历

     /* 邻接表的深度优先递归算法 */
     void DFS(GraphAdjList GL, int i)
     {
     	EdgeNode *p;
      	visited[i] = TRUE;
      	printf("%c ",GL->adjList[i].data);/* 打印顶点,也可以其它操作 */
     	p = GL->adjList[i].firstedge;
     	while(p)
     	{
      		if(!visited[p->adjvex])
      			DFS(GL, p->adjvex);/* 对为访问的邻接顶点递归调用 */
     		p = p->next;
      	}
     }
     
     /* 邻接表的深度遍历操作 */
     void DFSTraverse(GraphAdjList GL)
     {
     	int i;
      	for(i = 0; i < GL->numVertexes; i++)
      		visited[i] = FALSE; /* 初始所有顶点状态都是未访问过状态 */
     	for(i = 0; i < GL->numVertexes; i++)
      		if(!visited[i]) /* 对未访问过的顶点调用DFS,若是连通图,只会执行一次 */ 
     			DFS(GL, i);
     }
    

最小生成树

我们把构造连通网的最小代价生成树称为最小生成树(Minimum Cost Spanning Tree)

  1. 普利姆(Prim)算法

    普利姆算法生成最小生成树

     /* Prim算法生成最小生成树  */
     void MiniSpanTree_Prim(MGraph G)
     {
     	int min, i, j, k;
     	int adjvex[MAXVEX];		/* 保存相关顶点下标 */
     	int lowcost[MAXVEX];	/* 保存相关顶点间边的权值 */
     	lowcost[0] = 0;/* 初始化第一个权值为0,即v0加入生成树 */
     			/* lowcost的值为0,在这里就是此下标的顶点已经加入生成树 */
     	adjvex[0] = 0;			/* 初始化第一个顶点下标为0 */
     	for(i = 1; i < G.numVertexes; i++)	/* 循环除下标为0外的全部顶点 */
     	{
     		lowcost[i] = G.arc[0][i];	/* 将v0顶点与之有边的权值存入数组 */
     		adjvex[i] = 0;					/* 初始化都为v0的下标 */
     	}
     	for(i = 1; i < G.numVertexes; i++)
     	{
     		min = INFINITY;	/* 初始化最小权值为∞, */
     						/* 通常设置为不可能的大数字如32767、65535等 */
     		j = 1;k = 0;
     		while(j < G.numVertexes)	/* 循环全部顶点 */
     		{
     			if(lowcost[j]!=0 && lowcost[j] < min)/* 如果权值不为0且权值小于min */
     			{	
     				min = lowcost[j];	/* 则让当前权值成为最小值 */
     				k = j;			/* 将当前最小值的下标存入k */
     			}
     			j++;
     		}
     		printf("(%d, %d)\n", adjvex[k], k);/* 打印当前顶点边中权值最小的边 */
     		lowcost[k] = 0;/* 将当前顶点的权值设置为0,表示此顶点已经完成任务 */
     		for(j = 1; j < G.numVertexes; j++)	/* 循环所有顶点 */
     		{
     			if(lowcost[j]!=0 && G.arc[k][j] < lowcost[j]) 
     			{/* 如果下标为k顶点各边权值小于此前这些顶点未被加入生成树权值 */
     				lowcost[j] = G.arc[k][j];/* 将较小的权值存入lowcost相应位置 */
     				adjvex[j] = k;				/* 将下标为k的顶点存入adjvex */
     			}
     		}
     	}
     }
    
  2. 克鲁斯卡尔(Kruskal)算法

最短路径

  1. 迪斯杰特拉(Dijkstra)算法
  2. 佛洛依德(Floyd)算法

拓扑排序

  1. 介绍
  2. 算法

关键路径

  1. 原理
  2. 算法
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值