《大话数据结构》第七章--学习笔记

图的定义

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

注意:

1.线性表中我们把数据元素叫元素,树中将数据元素叫结点,在图中数据元素,我们则称之为顶点。

2.图中,任意两个顶点之间都可能有关系,顶点之间的逻辑关系用边来表示,边集可以是空的。

各种图的定义

无向边:若顶点vi到vj之间的边没有方向,则称这条边为无向边,用无序偶对(vi,vj)来表示。

有向边:若从顶点vi到vj之间的边有方向,则称这条边为有向边,也称为弧。

如果任意两个顶点之间的边都是有向边,则称该图为有向图。

无向边用小括号()表示,而有向边则是用尖括号<>表示。

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

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

有很少条边或弧的图称为稀疏图,反之称为稠密图。

与图的边或弧相关的树叫做权。这种带权的称为网。

图的顶点与边间的关系

路径的长度是路径上的边或弧的数目。

第一个顶点和最后一个顶点相同的路径称为回路或环。序列中顶点不重复出现的路径称为简单路径。除了第一个顶点和最后一个顶点之外,其余顶点不重复出现的回路,称为简单回路或简单环。

连通图的相关术语

在无向图中,若一个顶点到另一个顶点有路径,则称两个顶点连通。如果图中任意两个点都是连通的,则该图为连通图。

无向图中的极大连通子图称为连通分量

1.要是子图

2.子图要是连通的

3.连通子图含有极大顶点数

4.具有极大顶点数的连通子图包含依附于这些顶点的所有边

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

一个连通图的生成树是一个极小的连通子图,它含有图中全部的n个顶点,但只有足以构成一棵树的n-1条边。

如果一个有向图恰有一个顶点入度为0,其余顶点的入度均为1,则是一个有向数。

一个有向图的生成森林由若干棵有向数组成,含有图中的全部顶点,但只有足以构成若干棵不相交的有向树的弧。

图的存储结构

邻接矩阵

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

arc[i][j]=    1,若(vi,vj)属于E或<vi,vj>属于E

                  0,其他

     v0  v1  v2  v3      

v0  0    1    1    1

v1  1    0    1    0

v2  1    1    0    1

v3  1    0    1    0

怎么用邻接矩阵存图呢?输入u,v,w,让arc[u][v]=w;

arc[i][j]      =Wij

                 =0,当i==j时

                 =无穷

void shuru()
{
   int u,v,w;
   int n;
   //初始化.
   for(int i=1;i<=n;i++)
      for(int j=1;j<=n;j++)
        {
           if(i==j)
              arc[i][j]=0;
           else  src[i][j]=999999999;
        }
   for(int i=1;i<=n;i++)
     {
         scanf("%d %d %d",&u,&v,&w)
         arc[u][v]=w;//赋值
     }
}

邻接表

用邻接矩阵在边数相对顶点来说较少的时候。这种结构会对空间造成极大的浪费。

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

逆邻接表:即对每个顶点vi都建立一个链接为vj为弧头的表。

邻接表的处理方法如下:

1.图中顶点用一个一维数组存储,当然,顶点也可以用单链表来存储,不过数组可以较容易地读取顶点信息,更加方便。另外,对于顶点数组中,每个数据元素还需要存储指向一个邻接表的指针,以便于查找该顶点的边信息。

2.图中每个顶点vi的所有邻接点构成一个线性表,由于邻接表的个数不定,所以用的单链表存储,无向图称为vi的边表,有向图则称为顶点vi的边表,有向图则称顶点vi作为狐表的出边表。 

/* 建立图的邻接表结构 */
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 */               
	}
}

十字链表

十字链表:邻接表和逆邻接表结合起来。

邻接多重表

ivex和jvex是与某条边衣服的两个顶点在顶点表中的下标;link指向依附顶点ivex的下一条边;jlink指向依附顶点jvex的下一条边。

边集数组

边集数组是由两个一维数组构成的。一个是存储顶点的信息;另一个是存储边的信息,这个边数组每个数据元素由一条边的起点下标、终点下标和权组成。

图的遍历

我之前在看《啊哈!算法》的时候写了图的遍历的学习笔记

图的遍历--学习笔记_MogulNemenis的博客-CSDN博客

最小生成树

这个也写过学习笔记了

最小生成树--学习笔记_MogulNemenis的博客-CSDN博客

最短路径

这个也...

最短路径--学习笔记_MogulNemenis的博客-CSDN博客

拓扑排序

我们这里以一个例子来讲,例子来自哔哩哔哩某博主

算法步骤

 1.如上图,首先我们要把上表,转化成AOV网

2.找到无入度的顶点(无入度,即没有箭头指向该点)

3.任选一点写入排序队列,并删除这点,以及该点射出的箭头

4.重复上面的1,2点,直到输出全部顶点或者AOV网中不存在入度为0的顶点。

在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,这样的有向图为顶点表示活动的网,我们称为AOV网。(AOV网中不存在回路)

算法实现

由于拓扑排序需要删除顶点,用邻接表会更方便。

构造时会出现两个结果,如果网的全部顶点都被输出,则说明它不存在环的AOV网;如果输出的顶点数少了,说明该网有环,表示AOV网。


/* 邻接矩阵结构 */
typedef struct
{
	int vexs[MAXVEX];
	int arc[MAXVEX][MAXVEX];
	int numVertexes, numEdges;
}MGraph;

/* 邻接表结构****************** */
typedef struct EdgeNode /* 边表结点  */
{
	int adjvex;    /* 邻接点域,存储该顶点对应的下标 */
	int weight;		/* 用于存储权值,对于非网图可以不需要 */
	struct EdgeNode *next; /* 链域,指向下一个邻接点 */
}EdgeNode;

typedef struct VertexNode /* 顶点表结点 */
{
	int in;	/* 顶点入度 */
	int data; /* 顶点域,存储顶点信息 */
	EdgeNode *firstedge;/* 边表头指针 */
}VertexNode, AdjList[MAXVEX];

typedef struct
{
	AdjList adjList; 
	int numVertexes,numEdges; /* 图中当前顶点数和边数 */
}graphAdjList,*GraphAdjList;
/* **************************** */



/* 拓扑排序,若GL无回路,则输出拓扑排序序列并返回1,若有回路返回0。 */
Status TopologicalSort(GraphAdjList GL)
{    
	EdgeNode *e;    
	int i,k,gettop;   
	int top=0;  /* 用于栈指针下标  */
	int count=0;/* 用于统计输出顶点的个数  */    
	int *stack;	/* 建栈将入度为0的顶点入栈  */   
	stack=(int *)malloc(GL->numVertexes * sizeof(int) );    

	for(i = 0; i<GL->numVertexes; i++)                
		if(0 == GL->adjList[i].in) /* 将入度为0的顶点入栈 */         
			stack[++top]=i;    
	while(top!=0)    
	{        
		gettop=stack[top--];        
		printf("%d -> ",GL->adjList[gettop].data);        
		count++;        /* 输出i号顶点,并计数 */        
		for(e = GL->adjList[gettop].firstedge; e; e = e->next)        
		{            
			k=e->adjvex;            
			if( !(--GL->adjList[k].in) )  /* 将i号顶点的邻接点的入度减1,如果减1后为0,则入栈 */                
				stack[++top]=k;        
		}
	}   
	printf("\n");   
	if(count < GL->numVertexes)        
		return ERROR;    
	else       
		return OK;
}

关键路径

在前面讲的AOV网前提下,我们来Joe少一个新的概念。在一个表示工程的带权有向图中,用顶点表示事件,用有向边表示活动,用边上的权值表示活动的持续时间,这种有向图的边表示活动的网,我们称之为AOE网。

路径上各个活动所持续的时间之和称为路径长度,从源点到汇点具有最大长度的路径叫关键路径,在关键路径上的活动叫做关键活动。

 关键路径算法的原理

比如你有四个小时,其中有两个小时要用来写作业。但是你妈妈买了课外作业给你做,需要两个小时才能做完,那么现在你的四个小时都要写作业,不能休息。

即活动开始的最晚时间和活动开始的最早时间相等,此时活动为关键活动,活动间的路径为关键路径,吐过不等,就不是。

算法实现

nt *etv,*ltv; /* 事件最早发生时间和最迟发生时间数组 */
int *stack2;   /* 用于存储拓扑序列的栈 */
int top2;	   /* 用于stack2的指针 */
/* 拓扑排序 */
Status TopologicalSort(GraphAdjList GL)
{    /* 若GL无回路,则输出拓扑排序序列并返回1,若有回路返回0。 */    
	EdgeNode *e;    
	int i,k,gettop;   
	int top=0;  /* 用于栈指针下标  */
	int count=0;/* 用于统计输出顶点的个数 */   
	int *stack;	/* 建栈将入度为0的顶点入栈  */   
	stack=(int *)malloc(GL->numVertexes * sizeof(int) );    
	for(i = 0; i<GL->numVertexes; i++)                
		if(0 == GL->adjList[i].in) /* 将入度为0的顶点入栈 */           
			stack[++top]=i;    

	top2=0;    
	etv=(int *)malloc(GL->numVertexes * sizeof(int) ); /* 事件最早发生时间数组 */    
	for(i=0; i<GL->numVertexes; i++)        
		etv[i]=0;    /* 初始化 */
	stack2=(int *)malloc(GL->numVertexes * sizeof(int) );/* 初始化拓扑序列栈 */

	printf("TopologicalSort:\t");
	while(top!=0)    
	{        
		gettop=stack[top--];        
		printf("%d -> ",GL->adjList[gettop].data);        
		count++;        /* 输出i号顶点,并计数 */ 

		stack2[++top2]=gettop;        /* 将弹出的顶点序号压入拓扑序列的栈 */

		for(e = GL->adjList[gettop].firstedge; e; e = e->next)        
		{            
			k=e->adjvex;            
			if( !(--GL->adjList[k].in) )        /* 将i号顶点的邻接点的入度减1,如果减1后为0,则入栈 */                
				stack[++top]=k; 

			if((etv[gettop] + e->weight)>etv[k])    /* 求各顶点事件的最早发生时间etv值 */                
				etv[k] = etv[gettop] + e->weight;
		}    
	}    
	printf("\n");   
	if(count < GL->numVertexes)        
		return ERROR;    
	else       
		return OK;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值