【数据结构】图的邻接矩阵表示,遍历,最小生成树和最短路径

这个文件展示了图的邻接矩阵表示,两种遍历算法,最小生成树和最短路径算法,参考书为《大话数据结构》

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#define MAXVEX 100      //最大顶点数
#define MAXEDGE 100     //最大边集数组值
#define INFINITY 65535  //表示无穷大
#define ERROR 0
#define OK 1

//队列的DAT//
typedef int Elemtype; //BFS中队列存放的是图元素下标
typedef int Status;
typedef struct QNode {
	Elemtype data;
	struct QNode *next;
}*QuenePrt;
typedef struct {
	QuenePrt front, rear;    //指针形式的对头和队尾指针
}*LinkQuene;

//构建一个队列//
Status InitQuene(LinkQuene q) {
	q->front = q->rear = (QNode *)malloc(sizeof(QNode));   //这个是头结点
	if (!q->front) {
		exit(ERROR);
	}
	q->front->next = NULL;
	return OK;
}

//插入队列操作//
Status InsertQuene(LinkQuene q, Elemtype e) {
	//将e插入到队列的队尾中
	//首先定义一个队列结点
	//然后将e复制给结点的数据域
	//然后队尾next指向该结点
	//最后队尾指针等于该节点
	QuenePrt p;
	p = (QNode *)malloc(sizeof(QNode));//申请链表的一个结点空间
	p->data = e;
	p->next = NULL;
	q->rear->next = p;
	q->rear = p;
	return OK;
}

//出队操作//
Status DeleteQuene(LinkQuene q, Elemtype *e) {
	//有三种情况
	//第一种,有很多元素,直接删除
	//第二种,只有一个元素,删除后front等于rear
	//第三种,没有元素,返回ERROE
	QuenePrt p;
	if (q->front == q->rear)
		return ERROR;
	p = q->front->next;
	*e = p->data;
	q->front->next = p->next;
	if (q->rear == p)
		q->rear = q->front;//如果只有一个结点那么尾结点指回头结点
	free(p);
	return OK;
}
//探空操作//
Status QueneEmpty(LinkQuene Q) {
	//如果是空队列返回1,否则返回0
	if (Q->front == Q->rear)
		return true;
	else
		return false;
}

typedef char VertexType;//顶点类型
typedef int EdgeType;   //边上权值类型
//邻接矩阵结构//
typedef struct {
	VertexType vexs[MAXVEX];     //用于存放顶点信息
	EdgeType arc[MAXVEX][MAXVEX];//用于存放权值信息
	int Vertex_num, Edge_num;    //分别用于存放顶点数和边数
}MGraph;

//构建有向图//
void CreateMGraph(MGraph *G) {
	int i, j, k, w;
	printf("请输入图的顶点数:");
	scanf_s("%d", &G->Vertex_num);
	getchar();
	printf("请输入图的边数:");
	scanf_s("%d", &G->Edge_num);
	getchar();
	for (i = 0; i < G->Vertex_num; i++) {
		//读入顶点信息
		printf("请输入第%d个顶点信息:",i+1);
		scanf_s("%c", &G->vexs[i]);
		getchar();
	}
	//初始化邻接矩阵
	for (i = 0; i < G->Vertex_num; i++) {
		for (j = 0; j < G->Vertex_num; j++) {
			G->arc[i][j] = 0;
		}
	}
	for (i = 0; i < G->Vertex_num; i++) {
		for (j = 0; j < G->Vertex_num; j++) {
			if (j == i)
				continue;
			printf("请输入从%c到%c边的权值:",G->vexs[i], G->vexs[j]);
			scanf_s("%d", &w);
			G->arc[i][j] = w;  //赋值
		}
	}
}

//打印邻接矩阵//
void printMGraph(MGraph *G) {
	printf("\t");
	for (int i = 0; i < G->Vertex_num; i++) {
		printf("V%c\t", G->vexs[i]);
	}
	printf("\n\n");
	for (int i = 0; i < G->Vertex_num; i++) {
		printf("V%c\t", G->vexs[i]);
		for (int j = 0; j < G->Vertex_num; j++) {
			if (G->arc[i][j] == INFINITY)
				printf("∞\t");
			else
				printf("%d\t", G->arc[i][j]);
		}
		printf("\n\n");
	}
}

//邻接矩阵的深度优先遍历算法//
boolean visited[MAXVEX];//访问标志数组
void DFS(MGraph *G, int i) {
	//在图中,对下标为i的元素进行递归遍历
	int j;
	visited[i] = true;//表示访问过
	printf("%c\t", G->vexs[i]);//打印结点
	for (j = 0; j < G->Vertex_num; j++) {
		if ((G->arc[i][j] != INFINITY || G->arc[i][j] != 0) && visited[j] == false) {
			//如果对j号结点有通路,并且没有被访问过的话
			DFS(G, j);//递归访问j下标的元素
		}
	}
}
//深度优先遍历操作
void DFSTraverse(MGraph *G) {
	int i;
	for (i = 0; i < G->Vertex_num; i++) {
		//先对访问数组进行初始化
		visited[i] = false;
	}
	for (i = 0; i < G->Vertex_num; i++) {
		if (visited[i] == false) {
			DFS(G, i);//对未访问过的结点进行深度优先搜索遍历
		}
	}
}

//邻接矩阵的广度优先搜索遍历//
//BFS使用了队列
void BFSTraverse(MGraph *G) {
	int i, j;
	LinkQuene Q;
	Q = (LinkQuene)malloc(sizeof(LinkQuene));
	InitQuene(Q);
	for (i = 0; i < G->Vertex_num; i++) {
		visited[i] = false;
	}
	//接着开始遍历
	for (i = 0; i < G->Vertex_num; i++) {
		if (visited[i] == false) {
			//如果没有访问过
			visited[i] = true;//表示访问
			printf("%c\t", G->vexs[i]);
			InsertQuene(Q, i);//将i入队列
			while (QueneEmpty(Q) != 1) {
				//如果队列不空的话
				DeleteQuene(Q, &i);//出队列并且把值传给i
				for (j = 0; j < G->Vertex_num; j++) {
					if ((G->arc[i][j] != INFINITY || G->arc[i][j] != 0) && visited[j] == false) {
						//如果该边存在并且另一个结点没有被访问过
						visited[j] = true; //表示被访问过
						printf("%c\t", G->vexs[j]);
						InsertQuene(Q, j);//将j入队列
					}
				}
			}
		}
	}
}

//prim算法的最小生成树//
void minispanTree_prim(MGraph *G) {
	//adjvex表示最小权值边的起始顶点,j表示结束顶点
	int min, i, j, k;//会用到的变量
	int adjvex[MAXVEX];//用来存放边的其中一个顶点
	int lowcost[MAXVEX];//用来存放当前已经连通部分的最小权值(对应于adjvex中的顶点)
	lowcost[0] = 0;//初始化第一个数值为0,表示第一个顶点已经加入最小生成树
	adjvex[0] = 0;//第一个顶点下标
	for (i = 1; i < G->Vertex_num; i++) {
		lowcost[i] = G->arc[0][i];
		adjvex[i] = 0;//一开始最小生成树的顶点只有0
	}
	//初始化完成,接下来开始生成最小生成树
	for (i = 1; i < G->Vertex_num; i++) {
		//这里表示有n个顶点要弄出n-1条边(包括v0有n个顶点)
		min = INFINITY;//初始化最小值为无穷
		j = 1;
		k = 0;//k用于存放当前权值最小边的其中一个下标
		while (j < G->Vertex_num) {
			//循环出了第一个外的全部顶点
			if (lowcost[j] != 0 && lowcost[j] < min) {
				//寻找没有完成(不为0),并且是最小的顶点的下标
				//也就是在可接触的最小权值边(lowcost)中选一个最小的
				min = lowcost[j];
				k = j;
			}
			j++;
		}
		//此时,k存放的就是当前可以接触到的最小权值边的一个顶点
		//adjvex[k]记录的是当前所能接触到的最小权值边的另一个顶点
		printf("(%d,%d)\n", adjvex[k], k);
		lowcost[k] = 0;//表示这个顶点已经加入到最小生成树中
		//接下来要寻找新的可以接触到的最小权值边
		for (j = 1; j < G->Vertex_num; j++) {
			if (lowcost[j] != 0 && G->arc[k][j] < lowcost[j]) {
				//k表示新的最小生成树顶点,可以通过它来寻找最小权值边
				lowcost[j] = G->arc[k][j];
				adjvex[j] = k;//找到了之后要把其中一个顶点改为k,表示到k的最小权值边
			}
		}
	}
}

//Kruskal算法的最小生成树//
//定义边集数组
typedef struct {
	int begin;//开始顶点的下标
	int end;  //结束顶点的下标
	int weight;//边的权重
}Edge;
//生成边集数组
void Create_array(MGraph *G,Edge edges[]) {
	int i,j,count=0;
	for (i = 0; i < G->Vertex_num; i++) {
		for (j = i + 1; j < G->Vertex_num; j++) {
			//因为是无向图,所以只要遍历矩形的一半(对角线也排除了)
			if (G->arc[i][j] != 0 && G->arc[i][j] != INFINITY) {
				//如果边存在的话
				edges[count].begin = i;//开始顶点的下标
				edges[count].end = j;//结束顶点的下标
				edges[count].weight = G->arc[i][j];//该边的权重
				count++;
			}
		}
	}
	//接下来给边集数组排序(只会冒泡法了)
	//出循环时边集数组的数量是count-1
	//排序要求从小到大
	for (i = 0; i < count; i++) {
		for (j = i + 1; j < count; j++) {
			if (edges[i].weight > edges[j].weight) {
				//如果前一个小于后一个的话就交换位置
				Edge temp;
				temp = edges[i];
				edges[i] = edges[j];
				edges[j] = temp;
			}
		}
	}
	//打印边集数组
	printf("边集数组为:\n");
	printf("\tbegin\tend\tweight\n");
	for (i = 0; i < count; i++) {
		printf("edges[%d] %d\t%d\t%d\n", i, edges[i].begin, edges[i].end, edges[i].weight);
	}
}
//查询函数,用于查找如果加入这条边的话是否存在存在回路
int Find(int *parent, int f) {
	while (parent[f] > 0) {
		//大于零就表示该边已经加入了最小生成树
		//要通过该边结点来寻找其他的可以加入最小生成树的边
		f = parent[f];
	}
	return f;
}
//算法主体
void MiniSpanTree_Kruskal(MGraph *G) {
	int i, n, m;
	Edge edges[MAXEDGE];//用于存放边集数组
	int parent[MAXVEX];	//用于存放最小生成树的边,0表示还没有加入最小生成树
						//数组下标表示边的起始点下标,数组内的值表示边的结束点下标
	//先生成边集数组
	Create_array(G, edges);
	//初始化parent数组
	for (i = 0; i < G->Vertex_num; i++) {
		parent[i] = 0;
	}
	//接着循环遍历每一条边
	printf("生成最小生成树(Kruskal算法):\n");
	for (i = 0; i < G->Edge_num; i++) {
		n = Find(parent, edges[i].begin);
		m = Find(parent, edges[i].end);
		if (n != m) {
			//不相等就表示不存在回路
			parent[n] = m;//表示加入最小生成树
			printf("(%d,%d) %d\n", edges[i].begin, edges[i].end, edges[i].weight);
		}
	}
}

//最短路径问题之Dijkstra算法//
typedef int Pathmatirx1[MAXVEX]; //用于记录最短路径所经过的路径下标
typedef int ShortPathTable1[MAXVEX];//用于记录起始点到下标顶点的最短路径权值

//算法主体
void ShortestPath_Dijkstra(MGraph *G, int v0, Pathmatirx1 *P, ShortPathTable1 *D) {
	int v, w, k, min;
	int final[MAXVEX];//用于记录顶点是否被纳入最短路径,纳入为1,否则为0
	for (v = 0; v < G->Vertex_num; v++) {
		final[v] = 0;
		(*D)[v] = G->arc[v0][v]; //填入v0与各点间的权值
		(*P)[v] = 0;			 //初始化路径
	}
	//初始化第一个顶点
	(*D)[v0] = 0;   //第一个顶点到第一个顶点的权值为0
	final[v0] = 1;  //表示第一个顶点已经加入最短路径
	//开始主循环,求v0到各个顶点的最短路径
	for (v = 1; v < G->Vertex_num; v++) {
		//从1号顶点开始
		min = INFINITY;
		for (w = 0; w < G->Vertex_num; w++) {
			if (final[w] == 0 && (*D)[w] < min) {
				//如果这个顶点没有加入最短路径并且v0到这个顶点的路径最短
				k = w;
				min = (*D)[w];
			}
		}
		//出循环的时候已经找到了目前可以达到的最短路径
		final[k] = 1;
		//接下来更新最短路径表
		for (w = 0; w < G->Vertex_num; w++) {
			if (final[w] == 0 && (min + G->arc[k][w]) < (*D)[w]) {
				//如果该顶点没有纳入最短路径并且v0到这个顶点路径比之前的要短的话
				(*D)[w] = min + G->arc[k][w];//更新最短路径(v0到w)
				(*P)[w] = k;				 //存放前驱顶点(k到w)
			}
		}
	}
	//打印最短路径
	printf("最短路径为:\n");
	for (v = 1; v < G->Vertex_num; v++) {
		printf("(%d,%d)\n", (*P)[v], v);
	}
}

//最短路径问题之Floyd算法//
typedef int Pathmatirx[MAXVEX][MAXVEX];
typedef int ShortPathTable[MAXVEX][MAXVEX];

void ShortestPath_Floyd(MGraph *G, Pathmatirx *P, ShortPathTable *D) {
	int v, w, k;
	//先初始化距离矩阵和路径矩阵
	for (v = 0; v < G->Vertex_num; v++) {
		for (w = 0; w < G->Vertex_num; w++) {
			(*D)[v][w] = G->arc[v][w]; //直接用权值初始化距离矩阵先
			(*P)[v][w] = w;            //用终点顶点的下标初始化路径矩阵
		}
	}
	//接下来开始主循环
	//最外层循环为中间路径
	//中间循环为起始点
	//最里层循环为终点
	for (k = 0; k < G->Vertex_num; k++) {
		for (v = 0; v < G->Vertex_num; v++) {
			for (w = 0; w < G->Vertex_num; w++) {
				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];  //这是路径v到w的下一个顶点是v到k所存顶点
				}
			}
		}
	}
	//打印距离矩阵
	printf("距离矩阵为:\n");
	printf("\t");
	for (v = 0; v < G->Vertex_num; v++) {
		printf("V%c\t", G->vexs[v]);
	}
	printf("\n\n");
	for (v = 0; v < G->Vertex_num; v++) {
		printf("V%c\t",G->vexs[v]);
		for (w = 0; w < G->Vertex_num; w++) {
			printf("%d\t", (*D)[v][w]);
		}
		printf("\n\n");
	}
	printf("\n\n");
	//打印路径矩阵
	printf("路径矩阵为:\n\t");
	for (v = 0; v < G->Vertex_num; v++) {
		printf("V%c\t", G->vexs[v]);
	}
	printf("\n\n");
	for (v = 0; v < G->Vertex_num; v++) {
		printf("V%c\t",G->vexs[v]);
		for (w = 0; w < G->Vertex_num; w++) {
			printf("%d\t", (*P)[v][w]);
		}
		printf("\n\n");
	}
	printf("\n");
	//打印最短路径
	for (v = 0; v < G->Vertex_num; v++) {
		for (w = v + 1; w < G->Vertex_num; w++) {
			printf("V%d-V%d weight:%d\t", v, w, (*D)[v][w]);
			k = (*P)[v][w];
			printf("Path:V%d", v);
			while (k != w) {
				//当k不是终点的时候
				printf(" -> V%d", k);//打印下一个顶点
				k = (*P)[k][w];     //继续寻找下一个顶点
			}
			printf(" -> V%d", w);
			printf("\n");
		}
		printf("\n");
	}
}

int main()
{
	MGraph *G;
	G = (MGraph *)malloc(sizeof(MGraph));
	CreateMGraph(G);
	system("cls");
	printf("图的邻接矩阵为:\n");
	printMGraph(G);
	//hsystem("pause");
	/*printf("深度优先搜索遍历的结果是:\n");
	DFSTraverse(G);
	printf("\n");
	printf("广度优先搜索的结果是:\n");
	BFSTraverse(G);*/
	//printf("生成的最小权值边(prim算法):\n");
	//minispanTree_prim(G);
	//MiniSpanTree_Kruskal(G);
	Pathmatirx *P;
	ShortPathTable *D;
	P = (Pathmatirx *)malloc(sizeof(MAXVEX * MAXVEX));
	D = (ShortPathTable *)malloc(sizeof(MAXVEX * MAXVEX));
	//ShortestPath_Dijkstra(G, 0, P, D);
	ShortestPath_Floyd(G, P, D);
    return 0;
}

 

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: C语言中,最小生成树可以使用Prim算法或Kruskal算法来实现。其中Prim算法是基于点的贪心算法,Kruskal算法是基于边的贪心算法。 的建立可以使用邻接矩阵邻接表来实现。邻接矩阵是一个二维数组,其中每个元素表示两个顶点之间是否有边相连。邻接表则是一个链表数组,其中每个链表表示一个顶点的所有邻居。 的遍可以使用深度优先搜索(DFS)或广度优先搜索(BFS)来实现。DFS是一种递归算法,从一个起点开始,尽可能深地访问每个节点,直到无法继续为止。BFS则是一种迭代算法,从一个起点开始,逐层访问每个节点,直到所有节点都被访问为止。 最路径可以使用Dijkstra算法或Bellman-Ford算法来实现。Dijkstra算法是一种贪心算法,从一个起点开始,逐步扩展到所有节点,每次选择当前距离最的节点进行扩展。Bellman-Ford算法则是一种动态规划算法,通过多次松弛操作来逐步缩小起点到其他节点的距离。 ### 回答2: C语言是一门非常流行的编程语言,同时也是计算机领域中最重要的编程语言之一。在计算机科学中,最小生成树、的建立与遍、最路径等是一个非常重要的概念,涉及到许多实际的问题,比如路由算法、人际关系、网络通信等领域。C语言正是通过数据结构与算法来实现这些问题的解决。 最小生成树是论中的一个重要部分,是一张无向中的边集合,使得该的所有点都联通,同时所有边的权值之和最小。在C语言中,我们可以用prim算法或者Kruskal算法来实现最小生成树的建立,这两种算法在时间复杂度上都非常优秀。在使用prim算法时,我们需要将中所有的边按权重从小到大排序,然后使用贪心算法来进行选取。 的建立与遍也是C语言中非常关键的一个部分。我们可以使用邻接矩阵或者邻接表来表示。在使用邻接矩阵时,我们需要用一个二维数组来表示中边的连通性;而在使用邻接表时,我们用一个链表来表示每个节点的边。通过这些数据结构,我们可以进行的遍,包括深度优先遍和广度优先遍。 最路径问题也是论中一个非常重要的问题,有许多实际的应用,比如路由算法、电信网络等领域。C语言通过Dijkstra算法和Bellman-Ford算法等方式来实现最路径的计算。在使用Dijkstra算法时,我们需要使用一个优先队列来存储每个节点到起点的最路径,并逐步扩展出整个的最路径;而在使用Bellman-Ford算法时,则需要对中的所有边进行多次松弛操作,计算出整个中的最路径。 总之,最小生成树、的建立与遍、最路径等概念是计算机科学中非常重要的部分,C语言则是实现这些问题的关键工具之一。学习C语言、数据结构与算法,不仅可以让我们了解计算机科学的精髓,还可以开拓我们的思维和创造力。 ### 回答3: C语言是一种强大的编程语言,它可用于解决各种问题,包括建立最小生成树,的遍以及最路径问题。 最小生成树指的是在一个包含n个节点的连通中,通过连接n-1条边,生成一棵边权值之和最小的生成树。使用C语言实现最小生成树算法需要使用一些基本数据结构,包括队列、堆、树等。其中,堆是至关重要的数据结构,它可以在较小的时间内对边的权值进行排序,并确保最小的边被优先处理。 另一个问题是的遍。可以通过深度优先搜索和广度优先搜索等算法进行实现。在深度优先搜索中,选择任一一条未遍的边,并沿着这条边搜索下去,直到没有新的节点可以被遍。而广度优先搜索则是从起始节点开始,遍与起始节点相邻的所有节点,然后遍与这些节点相邻的所有节点,依此类推。 最路径问题是指,在有向或无向中,找到从起点到终点的路径,使得该路径上的边权值之和最小。对于这种问题,Dijkstra算法是一个高效的解决方案。该算法使用了一个优先队列、一个数组和一个set(用于标记已经处理的节点)。通过该算法,可以找到从起点到其他节点的最路径。 总之,C语言在论算法中发挥了重要的作用。通过使用各种数据结构和算法,可以解决最小生成树、的遍和最路径等问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值