图的四种算法代码

  • Post author:
  • Post category:其他




prim

/*******************************
 最小代价生成树之普利姆算法思想:-----------贪心算法思想
	从图中任意取出一个顶点,把它当成一棵树,然后从与这棵树相邻接的边中选取一条最短(权值最小)
 的边,并将这条边及其所连接的顶点也并入这棵树中,此时得到了一颗有两个顶点的树。然后从于这棵树
 相邻接的边中选取一条最短的边,并将这条边及其所连接的顶点并入当前树中,得到一棵有三个顶点的树。
 以此类推,直到图中所有顶点都被并入树中为止,此时得到的生成树就是最小生成树。
 普利姆算法执行过程:
	从树中某个顶点V0开始,构造生成树的算法过程如下:
		1、将v0到其他顶点的所有边当做侯选边;
		2、重复以下步骤n-1次,使得其他n-1个顶点被并入生成树中。
			1)从侯选边中挑选出权值最小的边输出,并将于该边另一端相连接的顶点v并入生成树中
			2)考察所有剩余顶点vi,如果(vi,vj)的权值比lowcost[vi]小,则用(vi,vj)的权值更新lowcost[vj]
 ********************************/
 //普利姆算法的代码:
 
 struct MGraph
 {
	 int n; // 顶点总数
	 int edges[MAXSIZE][MAXSIZE];  // 边的权值
 };
 
void Prim(MGraph g,int v0,int sum)//v0是以该点为起点构造最小生成树
{
	int lowcost[MAXSIZE], vset[MAXSIZE];//vset是当前最小生成树的集合
	int i , j , k, mincost = 0;
	for (i = 0; i < g.n; ++i)
	{
		lowcost[i] = g.edges[v0][i];
		vset[i] = 0;
	}
	vset[v0] = 1;     // 将v0并入树中
	sum = 0;          // sum清零用来记录树的权值
	for (i = 0; i < g.n -1; ++i)
	{
		mincost = INF;//正无穷
		for (j = 0; j < g.n; ++j)
		{
			if (vset[j] == 0 && lowcost[j] < mincost)   // 选出当前生成树中到其余顶点最短边中的最短的一条
			{
				mincost = lowcost[j];
				k = j;
			}
		}
		vset[k] = 1;
		sum += mincost;  //此处记录了最小生成树的权值
			
		
		//以刚并入的顶点v为媒介选取新的侯选边
		for (j = 0; j < g.n; ++j)  // 执行过程中的第二步
		{
			if (vset[j] == 0 && g.edges[k][j] < lowcost[j])
			{
				lowcost[j] = g.edges[k][j];
			}
 
		}
	}
		
}
 
//解释:
/*
	普利姆算法主要部分是一个双层循环:外层循环内嵌两个并列的单层循环,单层循环内的操作都是常量级别的,
	因此普利姆算法的时间复杂度为O(n*n)。可见普利姆算法的时间复杂度只与图中的顶点数有关,与边数无关,
	所以普利姆算法适用于稠密图
*/



kruskal

/**
* 
克鲁斯卡尔算法:每次找出侯选边中权值最小的边,就将该边并入树中。重复此过程直到所有边都被检测完为止。
克鲁斯卡尔算法执行过程:
   将图中该边按照权值从小到大排序,然后从最小边开始扫描各边,病检测当前边是否为侯选边,即是否该边的
   并入会构成回路,如果不构成回路,则将该边并入当前生成树中,直到所有边都检测完为止。
   判断是否产生回路需要用到并查集。
   并查集保存了一颗或者几棵树,这些树有这样的特点:
   	1、通过树中一个节点可以找到器双亲结点,进而找到根结点(其实就是树的双亲存储结构)。
   这种特性有两个好处:
   	1、一是可以快速地将两棵个含有很多元素的集合合并为一个。两个集合就是并查集中的两棵树,只需要找到其中一棵树
   	的根,然后将其作为另一棵树中任何一个结点的孩子结点即可;
   	2、二是可以很方便地判断两个元素是否属于同一个集合。通过这两个元素所在的结点找到它们的根结点,如果它们有相同的
   	根,则说明它们属于同一个集合,否则属于不同集合。
   并查集可以用一维数组来简单的表示。
*/
struct Road
{
   int a, b; // a和b为同一条边所连接的两个顶点
   int w;    // 边的权值
   static bool  cmp(Road & a, Road & b)  //结构体比较运算子
   {
   	return a.w < b.w;
   }

};



Road road[MAXSIZE];

int  v[MAXSIZE];   // 定义并查集数组
int getRoot(int a)  //在并查集中查找根结点的算法
{
   while (a != v[a])
   {
   	a = v[a];
   }
   return a;
}

struct MGraph2
{
   int n; // 顶点总数
   int edges;  // 边的总数
};

void Kruskal(MGraph2 g,int & sum, Road road[])
{
   int i = 0; 
   int N, E, a, b;
   N = g.n;
   E = g.edges;
   sum = 0;

   for (int i = 0; i < N; ++i)
   {
   	v[i] = i;
   }
   sort(road, road + E,Road::cmp);		//对road数组的E条边按其权值从小到大进行排序
   
   for (i = 0; i < E; ++i)
   {
   	a = getRoot(road[i].a);
   	b = getRoot(road[i].b);
   	if (a != b)
   	{
   		v[a] = b;
   		sum += road[i].w;
   	}

   }

}



dijkstra

//迪杰斯特拉算法  ; 单源点最短路径
# define INF 1000000000
//dist[vi]表示当前已找到的从v0到每个终点vi的最短路径的长度
//path[vi]保存的是从v0到vi最短路径上vi的前一个顶点,初始值为:若v0到vi有边,则path[vi] = v0,否则path[vi] = -1
//set[]为标记数组,set[vi] = 0表示vi没有被纳入最终的最短路径集合中,否则表示vi已经被纳入到最短路径中
void DijKstra(MGraph g, int v, int dist[], int path[])
{
	int set[MAXSIZE];
	int min, i, j, u;
	//初始化操作
	for (int i = 0; i < g.n; ++i)
	{
		dist[i] = g.edges[v][i];
		set[i] = 0;
		if (g.edges[v][i] != INF)
		{
			path[i] = v;//和源点直接相连则说明该顶点前一个顶点为源点
		}
		else
		{
			path[i] = INF;
		}
	}
	
 
	//关键操作开始
	for (i = 0; i < g.n ; ++i)//每次选择一个离源点最近的点,一共只需选n-1次,因为源点不用算
	{
		min = INF;
		//下面这个循环每次从剩余顶点中选出一个顶点,通往这个顶点的路径在通往所有剩余顶点的路径中是长度最短的
		for (j = 0; j < g.n; ++j)
		{
			if (set[j] == 0 && dist[j] < min)
			{
				min = dist[j];
				u = j;
			}
		}
		set[u] = 1; // 将选出的顶点并入到最短路径中
		
		//下面这个循环以刚并入的顶点为中转站,对所有通往剩余顶点的路径进行检测
		for (j = 0; j < g.n; ++j)
		{
			//下面这个if语句会判断顶点u的加入是否会出现通往顶点j更短的路径,如果出现,则改变原来的路径及其长度,否则什么都不做
			if (set[j] == 0 && dist[u] + g.edges[u][j] < dist[j])
			{
				dist[j] = dist[u] + g.edges[u][j];
				path[j] = u;//能通过if的判断说明以u作为中转站能使该点到源点的距离缩短,即u作为当前点的前一个点
			}
		}
		//关键操作结束
	}
}
//迪杰斯特拉算法结束时:dist[]中存放了起点v到其余顶点的最短路径长度,path[]中存放了起点v到其余各顶点的最短路径



floyd

//弗洛伊德算法 : 多源点最短路径算法(或者说是任意一对顶点之间的最短路径)
//弗洛伊德算法的思想:动态规划
//path[i][j]的值表示,顶点i到j路径中编号最大的中转站。
//因为第一层循环k按照0~n-1顺序逐步开放新的中转站,
//每个新中转站k若能使A[i][j]变小,
//则都会用这个新k覆盖path[i][j]的原值。
//初始化为-1表示两点间没有中转站,是直接连接着的
void Floyd(MGraph g,int path[][MAXSIZE])
{
   int i, j, k;
   int A[MAXSIZE][MAXSIZE];

   //这个双重循环对数组A[][]和Path[][]进行初始化
   for (int i = 0; i < g.n; ++i)
   {
   	for (int j = 0; j < g.n; ++j)
   	{
   		A[i][j] = g.edges[i][j];
   		path[i][j] = -1;
   	}
   }

   //下面这个三层循环是本算法的主要操作,完成了以k为中间顶点对所有顶点对(i,j)进行检测和修改
   for (k = 0; k < g.n; ++k)
   {
   	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] = k;
   			}
   		}
   	}
   }

}

void printpath(int start,int end)
{
   printf("%d",start);
   findpath(start,end);
}

void findpath(int start,int end)//路径
{
   if(A[start][end]<INF && path[start][end]==-1)//若两点之间有路径且没有中转站
   {											//即start==end或者两点直连
   	printf("->%d",end);//每次都输出两个直连点中的被指点
   }
   else
   {
   	int mid = path[start][end];
   	findpath(start,mid);
   	findpath(mid,end);
   }
}





版权声明:本文为weixin_45670020原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。