DS博客作业04--图

这个作业属于哪个班级 数据结构--网络2011/2012
这个作业的地址 DS博客作业04--图
这个作业的目标 学习图结构设计及相关算法
姓名 唐宇悦

0.PTA得分截图

1.本周学习总结

1.1 图的存储结构

1.1.1 邻接矩阵

有向图:

网:

邻接矩阵的结构体定义

#define MaxVertexNum 100;	//顶点数目的最大值
typedef char VertexType;	//顶点的数据类型
typedef int EdgeType;	//带权图中边上权值的数据类型
typedef struct{
	VertexType Vex[MaxVertexNum];	//顶点表
	EdgeType Edge[MaxVertexNum][MaxVertexNum];	//邻接矩阵,边表
	int vexnum, arcnum;	//图的当前顶点数和弧树
}MGraph;

建图函数

void CreateMGraph(MGraph &g, int n, int e)//建图 
{
	//n顶点,e弧数
	g.n = n;
	g.e = e;
	int i, j;
	int a, b;//下标
	for (i = 1; i <= n; i++)//先进行初始化
	{
		for (j = 1; j <= n; j++)
		{
			g.edges[i][j] = 0;
		}
	}
	for (i = 1; i <= e; i++)//无向图
	{
		cin >> a >> b;
		g.edges[a][b] = 1;
		g.edges[b][a] = 1;
	}
}

图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(邻接矩阵)存储图中的边或弧的信息。
设图G有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:

看一个实例,下图左就是一个无向图。

从上面可以看出,无向图的边数组是一个对称矩阵。所谓对称矩阵就是n阶矩阵的元满足aij = aji。即从矩阵的左上角到右下角的主对角线为轴,右上角的元和左下角相对应的元全都是相等的。
从上面可以看出,无向图的边数组是一个对称矩阵。所谓对称矩阵就是n阶矩阵的元满足aij = aji。即从矩阵的左上角到右下角的主对角线为轴,右上角的元和左下角相对应的元全都是相等的。
从这个矩阵中,很容易知道图中的信息。
(1)要判断任意两顶点是否有边无边就很容易了;
(2)要知道某个顶点的度,其实就是这个顶点vi在邻接矩阵中第i行或(第i列)的元素之和;
(3)求顶点vi的所有邻接点就是将矩阵中第i行元素扫描一遍,arc[i][j]为1就是邻接点;
而有向图讲究入度和出度,顶点vi的入度为1,正好是第i列各数之和。顶点vi的出度为2,即第i行的各数之和。

1.1.2 邻接表


邻接表的结构体定义

#define MAXVEX 100;	//图中顶点数目的最大值
type char VertexType;	//顶点类型应由用户定义
typedef int EdgeType;	//边上的权值类型应由用户定义
/*边表结点*/
typedef struct EdgeNode{
	int adjvex;	//该弧所指向的顶点的下标或者位置
	EdgeType weight;	//权值,对于非网图可以不需要
	struct EdgeNode *next;	//指向下一个邻接点
}EdgeNode;

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

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

建图函数

void CreateAdj(AdjGraph *&G, int n, int e)//创建图邻接表
{
	int i, j, a, b;
	G = new AdjGraph;
	for (i = 1; i <= n; i++)//邻接表头结点置零
	{
		G->adjlist[i].firstarc = NULL;
	}
	for (j = 1; j <= e; j++)//无向图
	{
		cin >> a >> b;	
		ArcNode *p,*q;
		p = new ArcNode;
		q = new ArcNode;
		p->adjvex = b;//用头插法进行插入
		q->adjvex = a;
		p->nextarc = G->adjlist[a].firstarc;
		G->adjlist[a].firstarc = p;
		q->nextarc = G->adjlist[b].firstarc;
		G->adjlist[b].firstarc = q;
	}
	G->n = n;
	G->e = e;
}

邻接矩阵是不错的一种图存储结构,但是,对于边数相对顶点较少的图,这种结构存在对存储空间的极大浪费。因此,找到一种数组与链表相结合的存储方法称为邻接表。
邻接表的处理方法是这样的:
(1)图中顶点用一个一维数组存储,当然,顶点也可以用单链表来存储,不过,数组可以较容易的读取顶点的信息,更加方便。
(2)图中每个顶点vi的所有邻接点构成一个线性表,由于邻接点的个数不定,所以,用单链表存储,无向图称为顶点vi的边表,有向图则称为顶点vi作为弧尾的出边表。
例如,下图就是一个无向图的邻接表的结构。

从图中可以看出,顶点表的各个结点由data和firstedge两个域表示,data是数据域,存储顶点的信息,firstedge是指针域,指向边表的第一个结点,即此顶点的第一个邻接点。边表结点由adjvex和next两个域组成。adjvex是邻接点域,存储某顶点的邻接点在顶点表中的下标,next则存储指向边表中下一个结点的指针。

1.1.3 邻接矩阵和邻接表表示图的区别

对于一个具有n个顶点e条边的无向图
它的邻接表表示有n个顶点表结点2e个边表结点
对于一个具有n个顶点e条边的有向图
它的邻接表表示有n个顶点表结点e个边表结点
如果图中边的数目远远小于n2称作稀疏图,这是用邻接表表示比用邻接矩阵表示节省空间;
如果图中边的数目接近于n2,对于无向图接近于n*(n-1)称作稠密图,考虑到邻接表中要附加链域,采用邻接矩阵表示法为宜。

  • 时间复杂度
    用邻接矩阵构造图时,若存储的是一个无向图,则时间复杂度为O(n^2 + n*e),其中,对邻接矩阵的初始化耗费的时间为O(n^2);
    对于DFS,BFS遍历来说,时间复杂度和存储结构有关:
    n表示有n个顶点,e表示有e条边。

1.若采用邻接矩阵存储,

时间复杂度为O(n^2);

2.若采用邻接链表存储,建立邻接表或逆邻接表时,

若输入的顶点信息即为顶点的编号,则时间复杂度为O(n+e);
若输入的顶点信息不是顶点的编号,需要通过查找才能得到顶点在图中的位置,则时间复杂度为O(n*e);

1.2 图遍历

1.2.1 深度优先遍历

⑴ 访问顶点v;
⑵ 从v的未被访问的邻接点中选取一个顶点w,从w出发进行深度优先遍历;
⑶ 重复上述两步,直至图中所有和v有路径相通的顶点都被访问到。
首先,我们访问V1结点,V1结点入栈并输出,然后发现V1与V2相连,因此我们将V2入栈,同理,V4和V5也入栈输出,因此遍历序列为V1 V2 V4 V5

接下来,V5入栈输出后,我们发现与V5相连的两个结点都已经访问过了,因此V5直接出栈,返回到了上一层,即V4,在V4结点处我们发现还有一个结点V8没有被访问到,因此将V8入栈并输出

同理,V8入栈后会直接出栈,因为与V8相邻的结点都已经被访问过了,接下来V4、V2也出栈,只留下了V1在堆栈中

这时我们会发现,V1还有结点没有访问到!所以V3、V6、V7相继入栈并输出

最后依次出栈,深度优先就遍历完了.
深度遍历代码

/**********深度优先遍历*********/
int visitDFS[maxSize];
void DFS(MGraph G,int i)
{
	int j;
	visitDFS[i] = 1;
	printf("%d ", G.Vex[i]);
	for (j = 0; j < G.vexnum; j++)
	{
		if (G.Edge[i][j] != 32767 && !visitDFS[j])
			DFS(G, j);
	}
}
 
void DFSTraverse(MGraph G)
{
	int i;
	for (i = 0; i < G.vexnum; i++)
		visitDFS[i] = 0;
	for (i = 0; i < G.vexnum; i++)
	{
		if (!visitDFS[i])
			DFS(G, i);
	}

深度遍历适用哪些问题的求解
1.求无向图的连通分量的个数
2.连通分量都包含哪些顶点
3.两个顶点是否在同一个连通分量中
4.单源路径问题
5.检测无向图中的环

1.2.2 广度优先遍历

在最开始时,V1入列,然后出列输出

紧接着,与V1相连的结点有V2和V3,因此V2和V3入列并输出

接下来V2出列,与V2相连的结点有V4和V5,所以V4和V5入列并输出

接下来同理,V3出列,V6和V7入列并输出

然后V4出列,与V4相连的还有V8,所以V8入列并输出

这样就遍历完了,后面的结点由于相邻结点都被访问过了,因此可以直接出列。
广度遍历代码

//广度优先遍历
int visitBFS[maxSize];
void BFS(MGraph G)
{
	int i, j;
	Queue Q;
	for (i = 0; i < G.vexnum; i++)
		visitBFS[maxSize] = 0;
	InitQueue(&Q);
	for (i = 0; i < G.vexnum; i++)
	{
		if (!visitBFS[i])
		{
			visitBFS[i] = 1;
			printf("%d ", G.Vex[i]);
			EnQueue(&Q, i);
 
			while (!IsEmpty(&Q))
			{
				DeQueue(&Q, &i);
				for (j = 0; j < G.vexnum; j++)
				{
					if (!visitBFS[j] && G.Edge[i][j] != 32767)
					{
						visitBFS[j] = 1;
						printf("%d ", G.Vex[j]);
						EnQueue(&Q, j);
					}
				}
			}
		}
	}
}

广度遍历适用哪些问题的求解。
BFS 的应用一:层序遍历
BFS 的应用二:最短路径

1.3 最小生成树

用自己语言描述什么是最小生成树。
我认为最小生成树就是边的权值最小的生成树

1.3.1 Prim算法求最小生成树


基于上述图结构求Prim算法生成的最小生成树的边序列

Prim算法思想:
逐渐长成一棵最小生成树。假设G=(V,E)是连通无向网,T=(V,TE)是求得的G的最小生成树中边的集合,U是求得的G的最小生成树所含的顶点集。初态时,任取一个顶点u加入U,使得U={u},TE=Ø。重复下述操作:找出U和V-U之间的一条最小权的边(u,v),将v并入U,即U=U∪{v},边(u,v)并入集合TE,即TE=TE∪{(u,v)}。直到V=U为止。最后得到的T=(V,TE)就是G的一棵最小生成树。也就是说,用Prim求最小生成树是从一个顶点开始,每次加入一条最小权的边和对应的顶点,逐渐扩张生成的。

实现Prim算法的2个辅助数组是什么?其作用是什么?Prim算法代码。
设置2个辅助数组:
1.closest[i]:最小生成树的边依附在U中顶点编号。
2.lowcost[i]表示顶点i(i ∈ V-U)到U中顶点的边权重,取最小权重的顶点k加入U。
并规定lowcost[k]=0表示这个顶点在U中

每次选出顶点k后,要队lowcost[]和closest[]数组进行修正

//最小生成树-Prim算法 
void Prim(Graph G)
{
	int v=0;//初始节点
	closedge C[MaxVerNum];
	int mincost = 0; //记录最小生成树的各边权值之和
	//初始化
	for (int i = 0; i < G.vexnum; i++)
	{
		C[i].adjvex = v;
		C[i].lowcost = G.Edge[v][i];
	}
	cout << "最小生成树的所有边:"<< endl;
	//初始化完毕,开始G.vexnum-1次循环
	for (int i = 1; i < G.vexnum; i++)
	{
		int k;
		int min = INF;
		//求出与集合U权值最小的点 权值为0的代表在集合U中
		for (int j = 0; j<G.vexnum; j++)
		{
			if (C[j].lowcost != 0 && C[j].lowcost<min)
			{
				min = C[j].lowcost;
				k = j;
			}
		}
		//输出选择的边并累计权值
		cout << "(" << G.Vex[k] << "," << G.Vex[C[k].adjvex]<<") ";
		mincost += C[k].lowcost;
		//更新最小边
		for (int j = 0; j<G.vexnum; j++)
		{
			if (C[j].lowcost != 0 && G.Edge[k][j]<C[j].lowcost)
			{   
				C[j].adjvex = k;
				C[j].lowcost= G.Edge[k][j];
			}
		}
 
	}
	cout << "最小生成树权值之和:" << mincost << endl;

分析Prim算法时间复杂度,适用什么图结构?
Prim算法的时间复杂度是O(n^2)的,因此适用于稠密图的最小生成树

1.3.2 Kruskal算法求解最小生成树

基于上述图结构求Kruskal算法生成的最小生成树的边序列
首先,在初始状态下,对各顶点赋予不同的标记(用颜色区别),如下图所示:

对所有边按照权值的大小进行排序,按照从小到大的顺序进行判断,首先是(1,3),由于顶点 1 和顶点 3 标记不同,所以可以构成生成树的一部分,遍历所有顶点,将与顶点 3 标记相同的全部更改为顶点 1 的标记,如下图所示:

其次是(4,6)边,两顶点标记不同,所以可以构成生成树的一部分,更新所有顶点的标记为:

其次是(2,5)边,两顶点标记不同,可以构成生成树的一部分,更新所有顶点的标记为

然后最小的是(3,6)边,两者标记不同,可以连接,遍历所有顶点,将与顶点 6 标记相同的所有顶点的标记更改为顶点 1 的标记:

继续选择权值最小的边,此时会发现,权值为 5 的边有 3 个,其中(1,4)和(3,4)各自两顶点的标记一样,如果连接会产生回路,所以舍去,而(2,3)标记不一样,可以选择,将所有与顶点 2 标记相同的顶点的标记全部改为同顶点 3 相同的标记:

选取的边的数量相比与顶点的数量小 1 时,说明最小生成树已经生成
实现Kruskal算法的辅助数据结构是什么?其作用是什么?Kruskal算法代码。
实现Kruskal算法的辅助数据结构是vset[]。vset[]用于判断顶点i,j是否属于同一个连通分量。

//最小生成树-Kruskal算法
void Kruskal(Graph G)
{
	//初始化
	sort(l.begin(), l.end(),cmp);
	int verSet[MaxVerNum];
	int mincost = 0;
	for (int i = 0; i < G.vexnum; i++)
		verSet[i] = i;
	cout << "最小生成树所有边:" << endl;
	//依次查看边
	int all = 0;
	for (int i = 0; i < G.arcnum; i++)
	{
		if (all == G.vexnum - 1)break;
		int v1 = verSet[l[i].from];
		int v2 = verSet[l[i].to];
		//该边连接两个连通分支
		if (v1 != v2)
		{
			cout << "(" << l[i].from << "," << l[i].to << ") ";
			mincost += l[i].weight;
			//合并连通分支
			for (int j = 0; j < G.vexnum; j++)
			{
				if (verSet[j] == v2)verSet[j] = v1;
			}
			all++;
		}
	}
	cout << "最小生成树权值之和:" <<mincost<<endl;

分析Kruskal算法时间复杂度,适用什么图结构?
时间复杂度为O(eloge)。和普里姆算法恰恰相反,更适合于求稀疏图的最小生成树。

1.4 最短路径

1.4.1 Dijkstra算法求解最短路径

迪杰斯特拉(Dijkstra)算法是典型最短路径算法,用于计算一个节点到其他节点的最短路径。
它的主要特点是以起始点为中心向外层层扩展(广度优先搜索思想),直到扩展到终点为止
基于上述图结构,求解某个顶点到其他顶点最短路径。(结合dist数组、path数组求解)
1.狄克斯特拉(Dijkstra)算法
过程:
S={入选顶点集合,初值V0},T={未选顶点集合}。
若存在<V0,Vi>,距离值为<V0,Vi>弧上的权值
若不存在<V0,Vi>,距离值为∞
从T中选取一个其距离值为最小的顶点W, 加入S
S中加入顶点w后,对T中顶点的距离值进行修改:重复上述步骤1,直到S中包含所有顶点,即S=V为止。
重复上述步骤1,直到S中包含所有顶点,即S=V为止。
图解:

Dijkstra算法需要哪些辅助数据结构

void Dijkstra(MGraph g, int v)//源点v到其他顶点最短路径 
{
	int dist[MAXV], path[MAXV],s[MAXV];
	int mindistance,u;//u为每次所选最短路径点

	for (int i = 0; i < g.n; i++)//初始化各数组
	{
		s[i] = 0;//初始已选入点置空
		dist[i] = g.edges[v][i];//初始化最短路径

		if (dist[i] < INF) path[i] = v;
		else path[i] = -1;//即无直接到源点V的边,因此初始化为-1
	}
	s[v] = 1;//源点入表示已选

	for (int j = 0; j < g.n; j++)//要将所有点都选入需循环n-1次
	{
		mindistance = INF;//每次选之前重置最短路径
		for (int i = 1; i < g.n; i++)//每次都遍历源点以外其他点来选入点
		{
			if (s[i] == 0 && dist[i] < mindistance)//在未选的点中找到最短路径
			{
				mindistance = dist[i];
				u = i;//u记录选入点
			}
		}
		s[u] = 1;//最后记录的u才为最后选入点

		for (int i = 1; i < g.n; i++)//修正数组值
		{
			if (s[i] == 0)//!!仅需修改未被选入点的,已选入的既定
			{
				if (g.edges[u][i] < INF && dist[u] + g.edges[u][i] < dist[i])//先判断选入点到与该点存在时再比较判断
				{
					dist[i] = dist[u] + g.edges[u][i];
					path[i] = u;
				}
			}
		}
	}
	Dispath(dist, path, s, g.n, v);

}

Dijkstra算法的时间复杂度,使用什么图结构,为什么?
Dijkstra算法 适用于求 稠密图 的最短路问题,因为 稠密图 的边数远远大于点数,Dijkstra算法的思路为进行 n 次循环,每次循环再遍历 n 个点确定一个还未确定最短距离的点中距离源点最近距离的点,然后再用这个点更新其所能到达的点(算法默认当前的点可以到达所有的点,因为没有到达的点之间的距离都已经初始化为正无穷大,所以不会被更新,不影响结果)。时间复杂度为 O(n2)。

1.4.2 Floyd算法求解最短路径

Floyd算法解决什么问题?
Floyd算法(Floyd-Warshall algorithm)又称为弗洛伊德算法、插点法,是解决给定的加权图中顶点间的最短路径的一种算法,可以正确处理有向图或负权的最短路径问题,同时也被用于计算有向图的传递闭包。
Floyd算法需要哪些辅助数据结构

void Floyd(Graph G)		
{     
    int A[MAXVEX][MAXVEX];	//建立A数组
    int path[MAXVEX][MAXVEX];	//建立path数组
    int i, j, k;
   for (i=0;i<G.n;i++)   		
       for (j=0;j<G.n;j++) 
       {       
          A[i][j]=G.edges[i][j];
	 if (i!=j && G.edges[i][j]<INF)
	      path[i][j]=i; 	//i和j顶点之间有一条边时
       else			 
	      path[i][j]=-1;
       }

  for (k=0;k<G.n;k++)		//求Ak[i][j]
 {     
     for (i=0;i<G.n;i++)
       for (j=0;j<G.n;j++)
	    if (A[i][j]>A[i][k]+A[k][j])	//找到更短路径
	    {   
                A[i][j]=A[i][k]+A[k][j];	//修改路径长度
	       path[i][j]=path[k][j]; 	//修改最短路径为经过顶点k
          }
  }
}	
1.两个二维数组
2.邻接矩阵

Floyd算法优势
Floyd算法适用于APSP(AllPairsShortestPaths),是一种动态规划算法,稠密图效果最佳,边权可正可负。此算法简单有效,由于三重循环结构紧凑,对于稠密图,效率要高于执行|V|次Dijkstra算法。
优点:容易理解,可以算出任意两个节点之间的最短距离,代码编写简单
缺点:时间复杂度比较高,不适合计算大量数据。

1.5 拓扑排序

拓扑排序指的是将有向无环图(又称“DAG”图)中的顶点按照图中指定的先后顺序进行排序。
找一个有向图,并求其对要的拓扑排序序列

对有向无环图进行拓扑排序,只需要遵循两个原则:
1.在图中选择一个没有前驱的顶点 V;
2.从图中删除顶点 V 和所有以该顶点为尾的弧。

进行拓扑排序时,首先找到没有前驱的顶点 V1,如图 2(1)所示;在删除顶点 V1 及以 V1 作为起点的弧后,继续查找没有前驱的顶点,此时, V2 和 V3 都符合条件,可以随机选择一个,例如图 2(2) 所示,选择 V2 ,然后继续重复以上的操作,直至最后找不到没有前驱的顶点。
所以,针对图 2 来说,拓扑排序最后得到的序列有两种:

  • V1 -> V2 -> V3 -> V4
  • V1 -> V3 -> V2 -> V4
    实现拓扑排序代码,结构体如何设计?
typedef struct {
	Vertex data;//顶点信息
	int count;//存放入度
	AreNode *firstarc;//头结点类型
}VNode;

书写拓扑排序伪代码,介绍拓扑排序如何删除入度为0的结点?

while(栈不空)
{
   出栈v,访问;
   遍历v所有邻接点
   {
      所有邻接点的入度-1
      当入度为0时,则入栈,以此实现入度为0时的删除操作
   }
}

如何用拓扑排序代码检查一个有向图是否有环路?
DFS判断有向图是否有环
对一个节点u进行DFS,判断是否能从u回到自己这个节点,即是否存在u到u的回路。
color数组代表每个节点的状态
-1代表还没访问,0代表正在被访问,1代表访问结束
如果一个状态为0的节点,与它相连的节点状态也为0,则有环

1.6 关键路径

什么叫AOE-网?

AOE网(Activity On Edge)即边表示活动的网,是与AOV网(顶点表示活动)相对应的一个概念。而拓扑排序恰恰就是在AOV网上进行的,这是拓扑排序与关键路径最直观的联系。AOE网是一个带权的有向无环图,其中顶点表示事件(Event),弧表示活动,权表示活动持续的时间。

什么是关键路径概念?

在项目管理中,关键路径是指网络终端元素的元素的序列,该序列具有最长的总工期并决定了整个项目的最短完成时间。
求关键路径必须在拓扑排序的前提下进行,有环图不能求关键路径;   (2) 只有缩短关键活动的工期才有可能缩短工期;   (3) 若一个关键活动不在所有的关键路径上,减少它并不能减少工期;   (4) 只有在不改变关键路径的前提下,缩短关键活动才能缩短整个工期。

什么是关键活动?

关键活动是为准时完成项目而必须按时完成的活动。即处于关键路径上的活动。所有项目都是由一系列活动组成,而在这些活动中存在各种链接关系和活动约束。其中有些活动如果延误就会影响整个项目工期。在项目中总存在这样一类直接影响项目工期变化的活动,这些活动就是关键活动。

2.PTA实验作业

2.1 六度空间


2.1.1 伪代码

宏定义邻接矩阵g[10001][10001],顶点数v,边数e,循环变量i,中间变量x,y用来输入
main函数
   定义浮点型数据 n,m来求最后所占比率
   输入顶点数v和边数e
   while e--   //构建邻接矩阵
        输入边关系 x y
        g[x][y] = g[y][x] = 1
   end while
   for i=1 to v do
        n=v  
        m=bfs(i) //调用bfs函数
   end for
   return 0

int bfs(int z)
   定义数组vis[10001]=0 表示是否被访问过的结点
   定义 last = z表示记录每层的最后一个元素
   i定义tail表示用于记录每一层压入栈时的结点
   定义 level = 0 表示与该结点距离是否为6
   定义count=1//表示结点
   创建int型栈q并入栈z
   将vis[z]置为1表示访问过了
   while q不为空 
      	z = q.front()//取栈顶元素于z
	q.pop()//去栈顶
	for i = 1to v do //广度遍历方法
		if g[z][i] 等于1 并且 vis[i]等于 0 then
				count++;
				vis[i] = 1;
				q.push(i);
				tail = i;
		end if 
         end for 
	if last 等于z then//一层全部从栈弹出,准备开始弹下一层,弹出的数等于当前层最后一个元素,并记录与last 
		level++;
		last = tail;//记录最后一个元素 
        end if 
		if  level 等于 6 break //符合题意的路径节点       
    return count

2.1.2 提交列表

2.1.3 本题知识点

1.图的广度遍历方法
2.栈的方法
3.使用了创建邻接矩阵方法

2.2 村村通


2.2.1 伪代码

用边存储结构读取数据
初始化vset数组使各数的起始节点设为本身
while k记录已保存的边数目!=顶点数-1
      寻找权值最小的边
      if 该边的权值为极大值
            图不连通 输出-1并退出循环
      寻找这边始末节点的起始节点
      if 起始节点不相等
            将其中一边的起始节点的起始节点记录为另一边的起始节点
            记录权值
      将该边的权值设为极大值
if 记录的边数=顶点数-1
      输出权值的和

2.2.2 提交列表

2.2.3 本题知识点

1.利用了prim最小生成树方法
2.邻接矩阵用法

posted @ 2021-05-28 22:32  唐yuy  阅读(63)  评论(1编辑  收藏  举报
levels of contents