《大话数据结构》读书笔记(三)

第7章 图(Graph)


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


无向边(Edge),用无序偶对(Vi,Vj)来表示。

有向边,也称为弧(Arc),用有序偶<Vi,Vj>来表示,Vi称为弧尾(Tail),Vj称为弧头(Head),图形显示为Vi---->Vj。


图的存储结构

1、邻接矩阵(Adjacency Matrix)

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

typedef char VertexType;           /* 顶点类型应由用户定义 */

typedef int EdgeType;              /* 边上的权值类型应由用户定义 */

#define MAXVEX 100                 /* 最大顶点数,应由用户定义 */

#define INFINITY 65535             /* 用65535来代表无穷大 */

typedef struct
{
	VertextType vexs[MAXVEX];      /* 顶点表 */

	EdgeType arc[MAXVEX][MAXVEX];  /* 邻接矩阵,可看做边表 */

	int numVertexes, numEdges;     /* 图中当前的顶点数和边数 */

}MGraph;


2、邻接表(adjacency List)

临界表顶点采用一个一维数组存储,另外对于顶点数组中,每个数据元素还需要存储指向第一个邻接点的指针,以便于查找该顶点的边信息。

顶点的所有邻接点构成一个线性表,用单链表存储,无向图为顶点的边表,有向图为顶点弧尾的出边表。

typedef char VertexType;           /* 顶点类型应由用户定义 */

typedef int EdgeType;              /* 边上的权值类型应由用户定义 */

typedef struct EdgeNode            /* 边表结点 */
{
	int adjvex;                    /* 邻接点域,存储该顶点对应的下标 */

	EdgeType weight;               /* 用于存储权值,对于非网图可以不需要 */

	struct EdgeNode *next;         /* 链域,指向下一个邻接点 */

}EdgeNode;

typedef struct VertexNode          /* 顶点表结点 */
{
	VertexType data;               /* 顶点域,存储顶点信息 */

	EdgeNode* firstedge;           /* 边表头指针 */

}VertexNode, AdjList[MAXVEX];

typedef struct
{
	AdjList adjList;

	int numVertexes,numEdges;      /* 图中当前顶点数和边数 */

}GraphAdjList;

3、十字链表(Orthogonal List)

对于有向图来说,邻接表只关心出度问题,入度必须要遍历整个图才能知道。有向图中采用了十字链表的存储方法。

结构定义书中没有给出,整理如下

typedef char VertexType;           /* 顶点类型应由用户定义 */

typedef int EdgeType;              /* 边上的权值类型应由用户定义 */

typedef struct ArcNode            /* 弧表结点 */
{
	int tailvex;                  /* 弧起点在顶点表的下标 */
	
	int headvex;                  /* 弧终点在顶点表的下标 */

	struct ArcNode *headlink;     /* 入边表指针域,指向终点相同的下一条边 */

	struct ArcNode *taillink;     /* 边表指针域,指向起点相同的下一条边 */

	EdgeType weight;               /* 用于存储权值,对于非网图可以不需要 */

}ArcNode;

typedef struct VertexNode          /* 顶点表结点 */
{
	VertexType data;               /* 顶点域,存储顶点信息 */

	ArcNode* firstin;              /* 表示入边头指针,指向该顶点的入边表中的第一个结点 */

	ArcNode* firstout;             /* 表示出边头指针,指向该顶点的出边表中的第一个结点 */

}VertexNode, OrtList[MAXVEX];

typedef struct
{
	OrtList ortList;

	int numVertexes,numEdges;      /* 图中当前顶点数和边数 */

}GraphOrtList;

4、邻接多重表(Adjacency MulList)

邻接多重表示对于无向图的邻接表的优化。

typedef char VertexType;         /* 顶点类型应由用户定义 */

typedef int EdgeType;            /* 边上的权值类型应由用户定义 */

typedef emnu{ unvisited,visited} VisitIf;

typedef struct EBox
{
	VisitIf mark:                  /*访问标记*/

	int ivex,jvex;                 /*该边依附的两个顶点的位置*/

	struct EBox ilink, jlink;      /*分别指向依附这两个顶点的下一条边*/

	InfoType info;                 /*该边信息指针*/

}EBox;

typedef struct VexBox
{
	VertexType data;

	EBox fistedge;                 /*指向第一条依附该顶点的边*/

}VexBox, Adjmulist[MAXVEX];

typedef struct
{
	Adjmulist adjmulist;

	int numVertexes,numEdges;      /* 无向图中当前顶点数和边数 */

}AMLGraph;

5、边集数组

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


图的遍历

深度优先遍历DFS(Depth_First_Search)

邻接矩阵的深度优先遍历算法,对于n个顶点e条边的图,要查找每个顶点的邻接点需要访问矩阵中的所有元素,因此需要O(n^2)的时间。

typedef int Boolean;      /* Boolean是布尔类型, 其值是true或false */

Boolen visited[MAX];     /* 访问标志的数组 */

/* 邻接矩阵的深度优先递归算法 */

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(G, i);
}

邻接表的深度优先遍历算法,对于那个顶点e条边的图,找邻接点所需的时间取决于顶点和边的数量,O(n+e)。

/* 邻接表的深度优先递归算法 */

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(GL, i);
}

广度优先遍历BFS(Breadth_First_Search)

邻接矩阵的广度遍历

/* 邻接矩阵的广度遍历算法 */

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);
				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 BFSTraverse(GraphAdjList GL)
{
	int i;
	
	EdgeNode *p;
	
	Queue Q;
	
	for(i=0; i<GL->numVertexes; i++)
		visited[i] = false;
	
	InitQueue(&Q);
	
	for(i=0; i<GL->numVertexes; i++)
	{
		if (! visited[i])
		{
			visited[i] = true;
			
			printf("%c ",GL->adjList[i].data);
			
			EnQueue(&Q,i);
			
			while(! QueueEmpty(Q))
			{
				DeQueue(&Q,i);
				
				p = GL->adjList[i].firstedge;
				
				while(p)
				{
					if (! visited[p->adjvex])
					{
						visited[p->adjvex] = true;
						
						printf("%c ",GL->adjList[p->adjvex].data);
						
						EnQueue(&Q, p->adjvex);
					}
					
					p = p->next;
				}
			}
		}
	}
}

最小生成树(Minimum Cost Spanning Tree)


最小生成树:构造连通图的最小代价生成树


普利姆(Prim)算法

普利姆算法是以顶点为起点,逐步找各顶点上最小权值的边来构建最小生成树的。

/* Prim算法生成最小生成树 */

void MiniSpanTree_Prim( MGraph G )
{
	int min, i, j, k;
	
	int adjvex[MAXVEX];     /* 保存相关顶点下标 */
	
	int lowcost[MAXVEX];    /* 保存相关顶点间边的权值 */
	
	lowcost[0] = 0;         /* 初始化第一个权值为0, 即V0加入生成树 */
	
	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;             /* 初始化最小权值为无穷大 */
		
		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)",adjvex[k],k);
		
		lowcost[k] = 0;             /* 将当前顶点的权值设置为0, 表示此顶点已经完成任务 */
		
		for(j=1;j<G.numVertexes; j++) /* 循环所有顶点 */
		{
			/* 若下标为k顶点各边权值小雨此前这些顶点未被加入生成树权值 */
			if (lowcost[j] != 0 && G.arc[k][j] < lowcost[j])
			{
				lowcost[j] = G.arc[k][j];  /* 将较小权值存入lowcost */
				
				adjvex[j] = k;             /* 将下标为k的顶点存入adjvex */
			}
		}
	}	
}

克鲁斯卡尔(Kruskal)算法

克鲁斯卡尔以边为目标去构建最小生成树,采用图的存储结构中的边集数组结构。

/* 对边集数组Edge结构的定义 */
typedef struct
{
	int begin;
	
	int end;
	
	int weight;
	
}Edge;

/* Kruskal算法生成最小生成树 */

void MiniSpanTree_Kruskal (MGraph G) /* 生成最小生成树 */
{
	int i, n, m;
	
	Edge edges[MAXEDGE];    /* 定义边集数组 */
	
	int parent[MAXVEX];     /* 定义一数组用来判断边与边是否形成环路 */
	
	/* 此处省略将邻接矩阵G转化为边集数组edges,并按权由小到大排列的代码 */
	
	for (i=0; i<G.numVertexes; i++)	
		parent[i] = 0;        /* 初始化数组为0 */
		
	for(i=0; i<G.numEdges; i++)  /* 循环每一条边 */
	{
		n = Find(parent, edges[i].begin);
		
		m = Find(parent, edges[i].end);
		
		if (n != m)      /* 假如n与m不等,说明此边没有与现有生成树形成环路 */
		{
			parent[n] = m; /* 将此边的结尾顶点放入下标为起点的parent中,表示此顶点已经在生成树集合中 */
			
			printf("(%d,%d) %d ",edges[i].begin,edges[i].end,edges[i].weight);
		}
		
	}
}

int Find(int* parent, int f)  /* 查找连线顶点的尾部下标 */
{
	while(parent[f] > 0)
		f = parent[f];
	
	return f;
}


对比两个算法,克鲁斯卡尔算法主要是针对边来展开,边数少时效率会非常高,所有对稀疏图有很大优势;而普利姆算法对于稠密图,即边数非常多的情况会更好一些。


最短路径


迪杰斯特拉(Dijkstra)算法

#define MAXVEX 9

#define INFINITY 65535

typedef int Pathmatirx[MAXVEX];      /* 用于存储最短路径下标的数组 */

typedef int ShortPathTable[MAXVEX];  /* 用于存储到各点最短路径的权值 */

/* Dijkstra算法,求有向图G的V0顶点到其余顶点v最短路径P[v]及带权长度D[v] */
void ShortestPath_Dijkstra(MGraph G, int v0, Pathmatirx* P, ShortPathTable* D)
{
	int v,w,k,min;
	
	int final[MAXVEX];   /* final[w]=1表示求得顶点V0至Vw的最短路径 */
	
	for(v=0,v<G.numVertexes;v++)  /* 初始化数据 */
	{
		final[v] = 0;      /* 全部顶点初始化为未知最短路径状态 */
		
		(*D)[v] = G.matirx[v0][v]; /* 将与V0点有连线的顶点加上权值 */
		
		(*P)[v] = 0;		   /* 初始化路径数组P为0 */
	}
	
	(*D)[v0] = 0;        /* V0至V0路径为0 */
	
	final[v0] = 1;       /* V0至V0不需要求路径 */
	
	/* 开始主循环,每次求得V0到某个V顶点的最短路径 */
	for(v=1;v<G.numVertexes; v++) 
	{
		min = INFINITY;
		
		for(w=0;w<G.numVertexes;w++)   /* 寻找离V0最近的顶点 */
		{
			if (! final[w] && (*D)[w]<min)
			{
				k = w;
				min = (*D)[w];             /* w顶点离V0顶点更近 */
			}
		}
		
		final[k] = 1;   /* 将目前找到的最近的顶点置为1 */
		
		for(w=0;w<G.numVertexes;w++)  /* 修正当前最短路径及距离 */
		{
			/* 如果经过v顶点的路径比现在这条路径的长度短的话 */
			if (! final[w] && (min+G.matirx[k][w]<(*D)[w]))
			{
				/* 说明找到了更短的路径,修改D[w]和P[w] */
				(*D)[w] = min + G.matirx[k][w]; /* 修改当前路径的长度 */
				
				(*P)[w] = k;
			}
		}
	}
}


弗洛伊德(Floyd)算法

typedef int Pathmatirx[MAXVEX][MAXVEX];

typedef int ShortPathTable[MAXVEX][MAXVEX];

/* Floyd 算法,求网图G中各顶点v到其余顶点w最短路径P[v][w]及带权长度D[v][w] */
void ShortestPath_Floyd(MGraph G, Pathmatirx *P, ShortPathTable *D)
{
	int v,w,k;
	
	/*初始化D与P */
	for(v=0;v<G.numVertexes;v++)
	{
		for(w=0;w<G.numVertexes;w++)
		{
			(*D)[v][w] = G.matirx[v][w];   /* D[v][w]值即为对应点间的权值 */
			
			(*P)[v][w] = w;                /* 初始化 P */
		}
	}
	
	for(k=0;k<G.numVertexes;k++)
	{
		for(v=0;v<G.numVertexes;v++)
		{
			for(w=0;w<G.numVertexes;w++)
			{
				/* 如果经过下标为k顶点路径比原两点间路径更短 */
				/* 将当前两点间权值设为更小的一个 */
				if ((*D)[v][w] > (*D)[v][k]+(*D)[k][w])
				{
					(*D)[v][w] = (*D)[v][k]+(*D)[k][w];
					
					(*P)[v][w] = (*P)[v][k];   /* 路径设置经过下标为k的顶点 */
				}
			}
		}
	}
}

拓扑排序

AOV网:在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系。

拓扑排序:对一个有向图构造拓扑序列的过程。

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无回路, 则输出拓扑排序序列并返回OK, 若有回路返回ERROR */
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 (GL->adjList[i].in = 0)
			stack[++top] = i; /* 将入度为0的顶点入栈 */
	
	while(top != 0)
	{
		gettop = stack[top--];  /* 出栈 */
		
		printf("%d -> ",GL->adjList[gettop].data);
		
		count++;  /* 统计输出顶点数 */
		
		/*对此顶点弧表遍历 */
		for(e=GL->adjList[gettop].firstedge; e; e=e->next)
		{
			k = e->adjvex;   
			
			if (!(--GL->adjList[k].in))  /* 将k号顶点邻接点的入度减1 */
				
				stack[++top] = k;          /* 若为0则入栈,以便于下次循环输出 */
		}
	}
	
	if (count < GL->numVertexes)		/* 如果count小于顶点数,说明存在环 */
		return ERROR;
	else
		return OK;	
}

关键路径

int *etv, *ltv;     /* 事件最早发生时间和最迟发生时间数组 */

int *stack2;        /* 用于存储拓扑序列的栈 */

int top2;           /* 用于stack2的指针 */

/* 拓扑排序,用于关键路径计算 */
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 (GL->adjList[i].in = 0)
			stack[++top] = i; /* 将入度为0的顶点入栈 */
	
	top2 = 0 ;  /* 初始化为0 */
	
	etv = (int *)malloc(GL->numVertexes*sizeof(int));
	
	for(i=0;i<GL->numVertexes;i++)
		etv[i] = 0;  /* 初始化为0 */
	
	stack2 = (int *)malloc(GL->numVertexes * sizeof(int));  /* 初始化 */
	
	while(top != 0)
	{
		gettop = stack[top--];  /* 出栈 */
		
		printf("%d -> ",GL->adjList[gettop].data);
		
		count++;  /* 统计输出顶点数 */
		
		stack2[++top2] = gettop;  /* 将弹出的顶点序号压入拓扑序列的栈 */
		
		/*对此顶点弧表遍历 */
		for(e=GL->adjList[gettop].firstedge; e; e=e->next)
		{
			k = e->adjvex;   
			
			if (!(--GL->adjList[k].in))  /* 将k号顶点邻接点的入度减1 */
				
				stack[++top] = k;          /* 若为0则入栈,以便于下次循环输出 */
			
			if ((etv[gettop]+e->weight)>etv[k])  /* 求各顶点事件最早发生时间值 */
				etv[k] = etv[gettop]+e->weight;
		}
	}
	
	if (count < GL->numVertexes)		/* 如果count小于顶点数,说明存在环 */
		return ERROR;
	else
		return OK;	
}

/* 求关键路径,GL为有向图,输出GL的各项关键活动 */
void CriticalPath(GraphAdjList GL)
{
	EdgeNode *e;
	
	int i,gettop,k,j;
	
	int ete,lte;    /* 声明活动最早发生时间和最迟发生时间变量 */
	
	TopologicalSort(GL);  /* 求拓扑序列,计算数组etv和stack2的值 */
	
	ltv=(int *)malloc(GL->numVertexes*sizeof(int));  /* 事件最晚发生时间 */
	
	for(i=0;i<GL->numVertexes;i++)
		ltv[i] = etv[GL->nmVertexes-1];      /* 初始化ltv */
	
	while(top2 != 0)                       /* 计算ltv */
	{
		gettop = stack2[top--];              /* 将拓扑序列出栈,后进先出 */
		
		for(e = GL->adjList[gettop].firstedge; e; e->next)
		{
			/* 求各顶点事件的最迟发生时间ltv值 */
			k = e->adjvex;
			
			/* 求各顶点事件最晚发生时间ltv */
			if (ltv[k]-e->weight<ltv[gettop])
				ltv[gettop] =  ltv[k]-e->weight;
			
		}
	}
	
	/* 求ete,lte和关键活动 */
	for(j=0;j<GL->numVertexes; j++)
	{
		for(e=GL->adjList[j].firstedge; e; e->next)
		{
			k = e->adjvex;
			
			ete = etv[j];              /* 活动最早发生时间 */
			
			lte = ltv[k] - e->weight;  /* 活动最迟发生时间 */
			
			if(ete == lte)             /* 两者相等即在关键路径上 */
			{
				printf("<v%d,v%d> length: %d, ",
				GL->adjList[j].data,GL-.adjList[k].data,e->weight);
			}
		}
	}
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值