C语言邻接表结构实现克鲁斯卡尔算法

C语言邻接表结构实现克鲁斯卡尔算法

C语言 数据结构


克鲁斯卡尔算法简介

克鲁斯卡尔算法是一种用来查找最小生成树的一种算法,由Joseph Kruskal在1956年发表。用来解决同样问题的还有Prim算法和Boruvka算法等。三种算法都是贪心算法的应用。和Boruvka算法不同的地方是,Kruskal算法在图中存在相同权值的边时也有效。
克鲁斯卡尔算法适合顶点较多而边较少的情况,因为克鲁斯卡尔算法需要对图中的边的权值进行排序。

基本思想及步骤

基本思想

在一个包含n个顶点而边集为空的子图中,把子图中各个顶点看成各棵树上的根结点,之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,即把两棵树合成一棵树,反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直到森林中只有一棵树,也即子图中含有 n-1 条边为止。

步骤

  1. 将图G中的顶点及边的信息中读取保存
  2. 将图中的边按权值进行排序
  3. 从权值最小的边开始,如果这条边连接的两个结点不在同一个联通分量里面,就将其加入最小生成树
  4. 重复3直至图G中所有的顶点都在同一个联通分量里面

时间复杂度

克鲁斯卡尔算法的平均时间复杂度为O(|E|log|E|),其中E和V分别是图的边集和点集,可见克鲁斯卡尔算法的时间复杂度主要取决于边数。

数据结构

一、图的数据结构

typedef struct Node {
	int dest;                      //邻接边的弧头顶点序号
	int cost;					   //邻接边权值
	struct Node *next;             //邻接边单链表的结点结构体
}Edge;

typedef struct {
	Datatype data;                 //顶点数据元素
	int source;                    //邻接边的弧尾顶点序号
	Edge *adj;                     //邻接边的头指针
}AdjHeight;                        //数组的数据元素类型结构体

 typedef struct {
	AdjHeight a[MaxVertices];       //邻接表数组
	int numOfVerts;                 //顶点个数
	int numOfEdges;                 //边个数
}AdjLGraph;                          //邻接表结构体

二、克鲁斯卡尔的数据结构

typedef struct edges edge;
struct edges {
	int col;
	int row;
	int cost;
};

该数据结构主要用来存储从邻接表中得到的所有边及其权值。

程序

程序可以从github或者csdn上下载:克鲁斯卡尔算法github,克鲁斯卡尔算法csdn其中Kruskal文件夹中即为克鲁斯卡尔算法,可用vs打开

初始化一个图

void CreateGraph(AdjLGraph *G, Datatype v[], int n, RowCol d[], int e) {
	int i, k;
	
	AdjInitiate(G);
	
	for (i = 0; i < n; i++) {
		InsertVertex(G,i,v[i]);
	}
	for (k = 0; k < e; k++)
		InsertEdge(G,d[k].row,d[k].col,d[k].cost);

}

从邻接表中取出所有边并去掉重复

/*
	遍历图,将边加入edge,并去掉重复的边
*/
edge *VisitGraph(AdjLGraph *G) {
	
	edge *e = (edge *)malloc((G->numOfEdges/2)*sizeof(edge));
	int i,j;
	int num = 0;
	int LiveFlag = 1;
	//AdjHeight *FirstElem = NULL;
	Edge *operatorElem = NULL;

	for (i = 0; i < G->numOfVerts; i++) {

		operatorElem = G->a[i].adj;

		while (operatorElem != NULL) {
			//遍历列表,去掉重复的边
			for (j = 0; j < num ; j++) {
				if ((e[j].col == G->a[i].source && e[j].row == operatorElem->dest)
					|| (e[j].row == G->a[i].source && e[j].col == operatorElem->dest)) {
					LiveFlag = 0;
					break;
				}
				else
					LiveFlag = 1;
			}

			if (G->a[i].source > operatorElem->dest && LiveFlag)
			{
				e[num].col = operatorElem->dest;
				e[num].row = G->a[i].source;
				e[num].cost = operatorElem->cost;

				num++;
			}
			else if (LiveFlag) {
				e[num].col = G->a[i].source;
				e[num].row = operatorElem->dest;
				e[num].cost = operatorElem->cost;

				num++;
			}
			else
				;
			operatorElem = operatorElem->next;
		}

		
	}

	return e;
}

排序所有边

/*
	对edges排序,采用冒泡排序法
*/

void GraphBubbleSort(edge *e,int num) {
	int i = 0;
	int j = 0;
	int tempcol, temprow, tempcost;
for (i = 0; i < num; i++) {
	for (j = 0; j < num - 1; j++) {
		if (e[j].cost > e[j + 1].cost) {
			tempcol = e[j].col;
			temprow = e[j].row;
			tempcost = e[j].cost;

			e[j].col = e[j + 1].col;
			e[j].row = e[j + 1].row;
			e[j].cost = e[j + 1].cost;

			e[j + 1].col = tempcol;
			e[j + 1].cost = tempcost;
			e[j + 1].row = temprow;
		}
	}
}

}

克鲁斯卡尔算法

edge *Kruskal(AdjLGraph *G,edge *e) {
	int num = G->numOfVerts;
	//Datatype *T = (Datatype *)malloc(num*sizeof(Datatype));  //顶点集合
	int *TLinkerFlag = (int *)malloc(num*sizeof(int));       //顶点连通区域集合
	edge *result = (edge *)malloc((G->numOfVerts - 1)*sizeof(edge)); //输出边集合
	int Flag = 1;
	int countflag = 0;
	int i = 0;
	int k = 0;
	int j = 1;
	int m = 0;
	int tempmin, tempmax;
	int tempflag;

for (i = 0; i < num; i++) {
	TLinkerFlag[i] = 0;
}

for (i = 0; i < (G->numOfVerts - 1); i++) {
	result[i].col = 0;
	result[i].cost = 0;
	result[i].row = 0;
}

i = 0;

while (Flag) {

	//判断两个顶点是否属于同一个连通分量
	if (TLinkerFlag[e[i].col] == TLinkerFlag[e[i].row] && TLinkerFlag[e[i].col] != 0) {

	}
	else {
		//加入边
		result[k].col = e[i].col;
		result[k].row = e[i].row;
		result[k].cost = e[i].cost;
		k++;
		//更改TLinkerFlag
		if (e[i].col > e[i].row) {
			tempmin = e[i].row;
			tempmax = e[i].col;
		}
		else {
			tempmin = e[i].col;
			tempmax = e[i].row;
		}
		
		if (TLinkerFlag[tempmin] != 0) {
			if (TLinkerFlag[tempmax] != 0) {
				tempflag = TLinkerFlag[tempmax];
				for (m = 0; m < num; m++) {
					if (TLinkerFlag[m] == tempflag) {
						TLinkerFlag[m] = TLinkerFlag[tempmin];
					}
				}

			}
			else {
				TLinkerFlag[tempmax] = TLinkerFlag[tempmin];
			}
		}
		else {
			if (TLinkerFlag[tempmax] != 0) {
				TLinkerFlag[tempmin] = TLinkerFlag[tempmax];
			}
			else {
				TLinkerFlag[tempmin] = j;
				TLinkerFlag[tempmax] = j;
				j++;
			}
		}
	}

	tempflag = TLinkerFlag[0];

	for (m = 0 , countflag = 0; m < num; m++) {
		if (TLinkerFlag[m] == tempflag)
			countflag++;
	}
	if (countflag == num)
		Flag = 0;


	i++;
}

return result;     

}
在克鲁斯卡尔算法中,用数组标记联通分量,如果两个顶点所对应的数组相应位置数值相同表示在同一个联通分量中,如果不在同一个联通分量,则保留该边,否则删除这条最终所有的顶点所对应的标记dou’xian都相同时认为最小生成树完成。

你可能感兴趣的:(C语言邻接表结构实现克鲁斯卡尔算法)