复习:最小生成树

本文详细介绍了图论中的两种经典算法——Prim算法和Kruskal算法,用于求解无向连通网的最小生成树。Prim算法从顶点角度出发,通过逐步构建最小生成树,而Kruskal算法则从边的角度,按边权值从小到大选取边来构造最小生成树。这两种算法各有优势,Prim适合稠密图,Kruskal适合稀疏图。文章提供了C++实现代码,并分析了它们的时间复杂度和适用场景。
摘要由CSDN通过智能技术生成

一、基础概念

  • 生成树:在连通图中,包含图中全部顶点的一个极小连通子图(n个顶点的生成树有且仅有n-1条边,因为n个顶点至少要有n-1条边才能够保证连通)。
  • 生成森林:在非连通图中,由每个连通分量都可以得到一棵生成树,这些生成树就组成了非连通图的生成森林。
  • 生成树的代价:无向连通网的生成树上各边的权值纸盒称为该生成树的代价。
  • 最小生成树:在图中所有生成树中,代价最小的生成树。
  • MST性质:在这里插入图片描述

二、Prim算法(从顶点的角度出发)
设G=(V,E)是无向连通网,T=(U,TE)是G的最小生成树。
①基本思想:

  1. 设置初始状态为U={v}(v属于V)、TE={}。
  2. 重复执行[在所有i属于U,j属于V-U的边中找一条代价最小的边(i,j)并入TE,同时j并入U]直至U=V为止。
  3. 改进:对于U-V中的每个顶点,只需要保留从该顶点到U中某个顶点的最短边即可。

②存储结构:

  1. 图的存储结构:邻接矩阵
  2. 候选最短边集:数组
    adjvex[n]表示最短边的邻接点(adjvex[i] = j)
    lowcost[n]表示权值(lowcost[i] = w)
    其含义是候选最短边(i,j)的权值为w,其中i属于V-U,j属于U
    lowcost[i] = min{lowcost[i],边(i,j)的权值}
    adjvex[i] = j(如果边(i,j)的权值<lowcost[i])
const int MAX = 1000;
void Prim(int v)
{
	int i,j,k;
	int adjvex[MaxSize],lowcost[MaxSize];//邻接点矩阵,权值矩阵
	for(i=0 ; i<vertexNum ; i++)//初始化辅助数组,从v开始,U中进入第一个点v
	{
		lowcost[i] = edge[v][i];//目前U中只有一个v,权值数组中都是V-U点到v的距离
		adjvex[i] = v;
	}
	lowcost[v] = 0;//将顶点V加入集合U(赋一个常值表示加入则无需再开一个visited数组)

	for(k = 1 ; k<vertexNum ; k++)//对剩下U-V中每个顶点都进行迭代
	{
		j = MinEdge(lowcost,vertexNum);//查找最小权值并返回其下标
		cout<<j<<adjvex[j]<<lowcost[j]<<endl;
		lowcost[j] = 0;//将顶点j加入集合U
		for(i = 0 ; i<vertexNum ; i++)//对辅助数组进行调整
		{
			if(edge[i][j]<lowcost[i])//U中加入了新的j点,看是否需要更新V-U中顶点到U中顶点的最小权值
			{
				lowcost[i] = edge[i][j];
				adjvex[i] = j;
			}
		}
	}
}

//MinEdge函数的实现:查找最小权值并返回其下标
int MinEdge(int lowcost[],int vertexNum)
{
	int i,min;
	min = Max;//标记最小权值
	for(i = 0 ; i<vertexNum ; i++)
	{
		if(lowcost[i]<lowcost[min])
			min = i;
	}
	return min;
}
	

Prim算法的时间复杂度是O(n^2),与网中的边数无关,因此适用于求稠密图的最小生成树。
实例:以邻接矩阵为存储结构,实现的无向带权图的最小生成树算法Prim、深度广度优先遍历,完整代码如下:

#include <iostream>
using namespace std;

const int MaxSize = 10;           //图中最多顶点个数
int visited[MaxSize]={0};
const int MAX = 1000; 

struct element			//改进:使用结构体数组代替lowcast数组以及adjvex数组
{
	int lowcost, adjvex;
};

int MinEdge(element *e,int vertexNum);

template <class DataType>
class MGraph
{
public:
	MGraph(DataType a[ ], int n, int e);    //构造函数,建立具有n个顶点e条边的图
	~MGraph( ) { }                     //析构函数为空
	void DFSTraverse(int v);              //深度优先遍历图
	void BFSTraverse(int v);               //广度优先遍历图
	void Prim(int v)  ;
private:
    DataType vertex[MaxSize];          //存放图中顶点的数组
    int edge[MaxSize][MaxSize];          //存放图中边的数组
    int vertexNum, edgeNum;             //图的顶点数和边数
};

template <class DataType>
MGraph<DataType>::MGraph(DataType a[ ], int n, int e)
{
	int i, j, k ,x;
	vertexNum = n;
	edgeNum = e;
	for (i = 0; i < vertexNum; i++)
		vertex[i] = a[i];
	for (i = 0; i < vertexNum; i++)
		for (j = 0; j < vertexNum; j++)
			edge[i][j] = MAX;
	for (k = 0; k < edgeNum; k++)
	{
		cout << "请输入两个边顶点的序号:";
		cin >> i >> j;			//输入边依附的两个顶点的编号 
		 
		cout << "请输入边的权值:"; 
		cin >> x;
		edge[i][j] = x;		//置边的标志为1 
		edge[j][i] = x;		//无向图 边是相通的 
	}
}

template <class DataType>
void MGraph<DataType>::DFSTraverse(int v)
{
	cout << vertex[v];
	visited[v] = 1;
	for (int j = 0; j < vertexNum; j++)
	{
		if (edge[v][j] != MAX && visited[j] == 0)DFSTraverse(j); //存在与v相连的j点且j点未被访问过 则进一步遍历j点 
	}
}

template <class DataType>
void MGraph<DataType>::BFSTraverse(int v)
{
	int w, j, Q[MaxSize];
	int front = -1, rear = -1;
	cout << vertex[v];
	visited[v] = 1;
	Q[++rear] = v;		//v点入队 
	while (front != rear)
	{
		w = Q[++front];	//Q出队值 w 
		for (j = 0; j < vertexNum; j++)//遍历所有可能与w相连的点 若有,则输出、标记、入队 
		{
			if (edge[w][j] != MAX && visited[j] == 0)//存在与w点相连的j点且j点未被访问过 则输出j点值 然后标记j点将j点入队 
			{
				cout << vertex[j];
				visited[j] = 1;
				Q[++rear] = j;
			}
		}
	}
}

template <class DataType>
void MGraph<DataType>::Prim(int v) 	//从顶点v出发 
{
	int i,j,k;
	element e[MaxSize];	//定义出发点结构体,里面包含当前出发点的所有邻点权值 
	for( i = 0;i<vertexNum;i++)
	{
		e[i].adjvex = v;
		e[i].lowcost = edge[v][i];
	}
	e[v].lowcost = 0;
	for(k = 1;k<vertexNum;k++)	//迭代n-1次 因为一开始已经取了一个起点 剩下n-1个 
	{
		j = MinEdge(e,vertexNum);	//找出还未连接的点中距离目前点最近的 
		cout<<"<"<<e[j].adjvex<<","<<j<<">"<<e[j].lowcost<<endl;	//输出j点 
		e[j].lowcost = 0;
		for(i = 0;i<vertexNum;i++)
		{
			if(edge[i][j]<e[i].lowcost)	//寻找j点到各个点比v到各个点路径短的,并替换 
			{
				e[i].lowcost = edge[i][j];	
				e[i].adjvex = j;	//j与i的距离 比v与i的距离近 因此i的最近点换成j 
			}
		}
	}
 } 
 
int MinEdge(element *e,int vertexNum)
{
	int min = MAX; 
	int res;
	for(int i = 0;i<vertexNum;i++)
	{
		if(e[i].lowcost!=0&&(e[i].lowcost<min))
		{
			res = i;
			min = e[i].lowcost 	;
		}
	}
	return res;
}
int main( )
{
	char ch[]={'A','B','C','D','E','F'};
	MGraph<char> MG(ch, 6, 9);
	for (int i=0; i<MaxSize; i++)
		visited[i]=0;
	cout<<"深度优先遍历序列是:";
	MG.DFSTraverse(0);
	cout<<endl;
	for (int i=0; i<MaxSize; i++)
		visited[i]=0;
    cout<<"广度优先遍历序列是:";
	MG.BFSTraverse(0);
	cout<<endl;
    cout<<"最小生成树的生成过程为:"<<endl;
    MG.Prim(0);
	cout<<endl;
    system("pause");
}

三、Kruskal算法(从的角度出发)
设G=(V,E)是无向连通网,T=(U,TE)是G的最小生成树。
①基本思想:

  1. 设置初始状态为U=V、TE={}。
  2. T中的顶点各自构成一个连通分量,然后按照边的权值由小到大的顺序,依次考察边集E中的各条边。
  3. 若被考察边的两个顶点属于两个不同的连通分量,则将此边加入到TE中,同时把两个连通分量连接为一个连通分量;若被考察边的两个顶点属于同一个连通分量,则舍去此边(以免造成回路)。
  4. 重复3过程,当T中的连通分量数为1时,此连通分量便为G的一棵最小生成树。

②存储结构:

  1. 图的存储结构:采用边集数组进行存储,为提高查找最短边的速度,可以先对边集数组按边上的权值排序。在这里插入图片描述
  2. 连通分量顶点所在的集合:用并查集实现集合的查找与合并,采用树的双亲表示法存储。parent[i]表示顶点i所在集合的根节点,初始时parent[i]=-1,对于边(u,v),设vex1和vex2分别为两个顶点所在集合的根节点,若vex1!=vex2,令parent[vex2] = vex1,实现两个集合的合并。
//定义边集数组的元素类型
struct EdgetType
{
	int from,to,weight;
}
//图的边集数组存储的类定义
const int MaxVertex = 10;//图中最多顶点数
const int MaxEdge = 100;//图中最多边数
template<typename DataType>
class EdgeGraph
{
	public:
		EdgeGraph(DataType a[], int n, int e);//构造函数
		~EdgeGraph();
		void Kruskal();
	private:
		int FindRoot(int parent[], int v);//求顶点v所在集合的根
		DataType vertex[MaxVertex];//存储顶点的一维数组
		EdgeType edge[MaxEdge];//存边的边集数组
		int vertexNum,edgeNum;
//kruskal算法的实现
void Kruskal()
{
	int num = 0, i, verx1, vex2;
	int parent[vertexNum];//双亲表示法存储并查集
	for(i = 0 ; i<vertexNum ; i++)
	{
		parent[i] = -1;//初始化n个连通分量
	}
	for(num = 0,i = 0 ; num<vertexNum ; i++)//依次考察最短边(i控制),直到T中的连通分枝数为1(vertexNum个顶点,vertexNum-1条边)为止(num控制)
	{
		vex1 = FindRoot(parent, edge[i].from);
		vex2 = FindRoot(parent, edge[i].to);
		if(vex1!=vex2)//如果两个点的根节点不同
		{
			cout<<"("<<edge[i].from<<","<<edge[i].to<<")"<<edge[i].weight;
			parent[vex2] = vex1;//合并集合
			num++;//最小生成树中的边的数量+1
		}
	}
}

//FindRoot函数的实现:求顶点v所在集合的根
int FindRoot(int parent[], int v)
{
	int t = v;
	while(parent[t]>-1)//求顶点t的双亲一直到根节点(-1)
		t = parent[t];
	return t;
}

Kruskal算法时间复杂度为O(elog2e),与网中的顶点数无关,适用于求稀疏图的最小生成树。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值