大话数据结构---图

1、图的定义

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

  • 无向边:若顶点V1和V2之间的边没有方向,则称这条边为无向边,用无序偶对(V1,V2)来表示。如果图中任意两个顶点之间的边都是无向边,则称该图为无向图。
  • 有向边:若从顶点V1到V2的边有方向,则称这条边为有向边,也称为弧(Arc)。如果图中任意两个顶点之间的边都是有向边,则称该图为有向图。

2、图的存储结构

- 邻接矩阵

  • 图的邻接矩阵存储方式是用两个数组来表示图,一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。
typedef char VertexType;   /*顶点类型应由用户定义*/
typedef int EdgeType;      /*边上的权值类型应由用户定义*/
#define MAXVEX 100         /*最大顶点数,应由用户定义*/
#define INFINITY 65535     /*用65535来表示无穷*/

typedef struct
{
	VertexType vexs[MAXVEX];   /*顶点表*/
	EdgeType arc[MAXVEX][MAXVEX];   /*邻接矩阵,可看作边表*/
	int numVertexes, numEdges;      /*图中当前的顶点数和边数*/
}MGraph;
  • 无向网图的创建代码:
void CreateMGraph(MGraph *G)
{
	int i, j, k, w;
	printf("输入顶点数和边数:\n");
	scanf("%d, %d", &G->numVertexes, &G->numEdges);   /*输入顶点数和边数*/
	for(i = 0; i < G->numVertexes; i++)   /*读入顶点信息,建立顶点表*/
		scanf(&G->vexs[i]);
	for(i = 0; i < G->nummVertexes; i++)
		for(j = 0; j < G->numVertexes; j++)
			G->arc[i][j] = INFNITY;   /*邻接矩阵初始化*/
	for(k = 0; k < G->numVertexes; k++)
	{
		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];   /*因为是无向图,矩阵对称*/
	}
}

-邻接表

重点关注顶点的操作:

  • 结点定义的代码:
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;
  • 无向图邻接表创建的代码:
/*建立图的邻接表结构*/
void CreateALGraph(GraphAdjList *G)
{
	int i, j, k;
	EdgeNode *e;
	printf("输入顶点数和边数:\n");
	scanf("%d, %d", &G->numVertexes, &G->numEdges);   /*输入顶点数和边数*/
	for(i = 0; i < G->numVertexes; i++)
	{
		scanf(&G->adjList[i].data);   /*输入顶点信息*/
		G->adjList[i].firstedge = NULL;   /*将边表置为空表*/
	}
	for(k = 0; k < G->numVertexes; 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*/
	}
}

- 十字链表

  • 十字链表的好处就是因为把邻接表和逆邻接表整合在了一起,这样既容易找到以v1为尾的弧,也容易找到以v1为头的弧,因而容易求得顶点的出度和入度。

- 邻接多重表

重点关注边的操作:

  • 邻接多重表与邻接表的差别,仅仅是在于同一条边在邻接表中用两个结点表示,而在邻接多重表中只有一个结点。

- 边集数组

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

3、图的遍历

- 深度优先遍历(Depth_First_Search,DFS)

/*邻接表的深度优先递归算法*/
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);
}

-广度优先遍历(Breadth_First_Search,BFS)

/*邻接表的广度遍历算法*/
void BFSTraverse(GraphAdjList GL)
{
	int i;
	EdgeNode *p;
	Queue Q;
	for(int i = 0; i < GL->numVertexes; i++)
		visited[i] = FALSE;
	InitQueue(&Q);   /*初始化一辅助用的队列*/
	for(int 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);   /*将队中元素出队列,赋值为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;   /*指针指向下一个邻接点*/
				}
			}
		}
		
	}
}

4、最小生成树

  • 我们把构造联通网的最小代价生成树称为最小生成树(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加入生成树,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;   /*初始化最小权值为无穷,通常设置为不可能的大数字*/
		j = 1; k = 0;
		while(j < G.numVertexes)   /*循环全部顶点*/
		{
			if(lowcost[j] != 0 && lowcost[j] < 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++)
		{
			if(lowcost[j] != 0 && G.arc[k][j] < lowcost[j])
			{/*若下标为k顶点各边权值小于此前这些顶点未被加入生成树权值*/
				lowcost[j] = G.arc[k][j];   /*将较小权值存入lowcost*/
				adjvex[j] = k;   /*将下标为k的顶点存入adjvex*/
			}
		}
	}
}

-克鲁斯卡尔(Kruskal)算法

  • edge边集数组结构定义代码:
/*对边集数组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];    /*定义一数组用来判断边与边是否形成回路*/
	
	for(int i = 0; i < G.numVertexes; i++)
		parent[i] = 0;   /*初始化数组值为0*/
	for(int 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;
}

5、最短路径

-迪杰斯特拉(Dijkstra)算法

#define MAXVEX 9
#define INFINITY 65535
typedef int Pathmatrirx[MAXVEX];   /*用于存储最短路径下标的数组*/
typedef int ShortPathTable[MAXVEX];   /*用于存储到各点最短路径的权值和*/

/*P[V]的值为前驱顶点下标,D[V]表示v0到v的最短路径长度和*/
void ShortestPath_Dijkstra(MGraph G, int v0, Pathmatrirx *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;   /*当前所知离v0顶点的最近距离*/
		for(w = 0; w < G.numVertexes; w++)   /*寻找离v0最近的顶点*/
		{
			if(!final[w] && (*D)[w] < min)
			{
				k = w;
				min = (*D)[w];   /*w顶点离v0顶点更近*/
			}
		}
		final[k] = 1;   /*将目前找到的最近的顶点置为0*/
		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;
	for(v = 0; v < G.numVertexes; v++)   /*初始化D与P*/
	{
		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)
			{
				if((*D)[v][w] > (*D)[v][k] + (*D)[k][w])
				{/*如果经过下标为k顶点路径比原两点间路径更短,将当前两点间权值设为更小的一个*/
					(*D)[v][w] = (*D)[v][k] + (*D)[k][w];
					(*P)[v][w] = (*P)[v][k];   /*路径设置经过下标为k的顶点*/
				}
			}
		}
	}
}

拓扑排序

  • 在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的有限关系,这样的有向图为顶点表示活动的网,我们称为AVO网(Activity On Vertex Network)。AOV网中不能存在回路。
  • 设G = (V, E)是一个具有n个顶点的有向图,V中的顶点序列v1,v2,…,vn满足若从顶点vi到vj有一条路径,则在顶点序列中顶点vi必在顶点vj之前。则我们称这样的顶点序列为一个拓扑序列。拓扑排序就是对一个有向图构造拓扑序列的过程。
    结构代码:
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(GraphAdiList GL)
{
	EdgeNode *e;
	int i, k, gettop;
	int top = 0;  /*用于栈指针下标*/
	int count = 0;   /*用于统计输出顶点的个数*/
	int *stack;   /*建栈存储入度为0的顶点*/
	stack = (int *)malloc(GL->numVerexes * 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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值