图的最小生成树 || Prim算法和Kruskal算法的c++实现

Prim算法和Kruskal都是典型的贪心算法,也就是在求解最小生成树时,我们总是不从全局考虑,而每次仅仅是考虑局部最优解。下面的算法都基于无向图的邻接多重表。

Prim算法:

算法描述:

基本步骤:

设R是有n个定点的对称连通关系。

  • 1)选取R的一个定点v1,设V = {v1}, E={}。
  • 2)选取与vj ∈ V邻接的V的最近邻元vi, 并且边(vi, vj)不与E中元素形成回路。添加vi到V中,添加(vi, vj)到E中。
  • 重复2),知道|E|=n - 1,于是V包含R的所有n个定点,E包含R的最小生成树。

代码实现:

//找无向图的最小生成树的Prim算法
//最小生成树:对于n个顶点,找到n-1条边
//选一个任意一点出发作为生成树的根,之后往树上添加顶点,使得两点之间存在一条边,并且该边权值再所有两点之间可选边中取最小
#include 
#include 
#include 
#include 
using namespace std;
#define MAX_VERTEX_NUM 20
enum VisitIf {unvisited, visited};
class EBox {
public:
	int ivex, jvex; //该边依附的两个顶点位置
	EBox *ilink, *jlink; //分别指向依附这两个顶点的下一条边
	int weight;
};

class VexBox {
public:
	string data;
	EBox *firstedge; //指向第一条依附该顶点的边
	VisitIf mark; //访问标记
};

class AMLGraph {
public:
	VexBox adjmulist[MAX_VERTEX_NUM];
	int vexnum, edgenum; //无向图的当前顶点数和边数
};

int LocateVex(VexBox adj[], int num, string v) {
	for (int i = 0; i < num; i++) {
		if (adj[i].data == v) {
			return i;
		}
	}
}

void CreateGraph(AMLGraph *G) {
	cout << "请输入顶点数和弧数: " << endl;
	cin >> G->vexnum >> G->edgenum;
	cout << "请输入顶点: " << endl;
	for (int i = 0; i < G->vexnum; i++) {
		cin >> G->adjmulist[i].data;
		G->adjmulist[i].mark = unvisited;
		G->adjmulist[i].firstedge = NULL;
	}
	for (int i = 0; i < G->edgenum; i++) {
		cout << "请输入边的两个顶点以及此边的权重: " << endl;
		string v1, v2;
		int vw;
		cin >> v1 >> v2 >> vw;
		int i1 = LocateVex(G->adjmulist, G->vexnum, v1);
		int i2 = LocateVex(G->adjmulist, G->vexnum, v2);
		//cout << i1 << "...." << i2 << endl;
		EBox *p = new EBox;
		p->ivex = i1;
		p->jvex = i2;
		p->weight = vw;
		p->ilink = G->adjmulist[i1].firstedge;
		p->jlink = G->adjmulist[i2].firstedge;
		G->adjmulist[i1].firstedge = p;
		G->adjmulist[i2].firstedge = p;
	}
}

int FirstAdjVex(AMLGraph G, int v, int &wei) {
	int i = v;
	if (G.adjmulist[i].firstedge) {
		if (G.adjmulist[i].firstedge->ivex == i) {
			wei = G.adjmulist[i].firstedge->weight;
			return G.adjmulist[i].firstedge->jvex;
		}
		else {
			wei = G.adjmulist[i].firstedge->weight;
			return G.adjmulist[i].firstedge->ivex;
		}
	}
	else
		return -1;
}
//v是G中的某个顶点,w是v的邻接顶点
//返回v的(相对于w的)下一个邻接顶点。若w是v的最后一个邻接点,则返回空

int NextAdjVex(AMLGraph G, int v, int w, int &wei) {
	int i1 = v;
	int i2 = w;
	EBox *p = G.adjmulist[i1].firstedge;
	while (p) {
		if (p->ivex == i1 && p->jvex != i2) {
			p = p->ilink;
		}
		else {
			if (p->ivex != i2 && p->jvex == i1) {
				p = p->jlink;
			}
			else {
				break;
			}
		}
	}
	if (p && p->ivex == i1 && p->jvex == i2) {
		p = p->ilink;
		if (p&&p->ivex == i1) {
			wei = p->weight;
			return p->jvex;
		}
		else if (p&&p->jvex == i1) {
			wei = p->weight;
			return p->ivex;
		}
	}
	if (p && p->ivex == i2 && p->jvex == i1) {
		p = p->jlink;
		if (p&&p->ivex == i1) {
			wei = p->weight;
			return p->jvex;
		}
		else if (p&&p->jvex == i1) {
			wei = p->weight;
			return p->ivex;
		}
	}
	return -1;
}

void PrintGraph(AMLGraph G)
{
    EBox *p;
    for(int i = 0; i < G.vexnum; ++i)
    {
        p = G.adjmulist[i].firstedge;
        while(p != NULL)
        {
            if(p->ivex == i)    //判断相等才能知道连接上的是ivex还是jvex;
            {
            	cout <<  G.adjmulist[p->ivex].data << "---" << p->weight << "---" << G.adjmulist[p->jvex].data << endl;
                p = p->ilink;
            }
            else
            {
               	cout <<  G.adjmulist[p->jvex].data << "---" << p->weight << "---" << G.adjmulist[p->ivex].data << endl;
                p = p->jlink;
            }
        }
    }
}

void MiniSpanTree_PRIM(AMLGraph G) {
	struct {
		int adjvex;
		int lowcost;
	}close[MAX_VERTEX_NUM];
	cout << "选择一个顶点出发建立生成树: " << endl;
	string v0;
	cin >> v0;
	int i0 = LocateVex(G.adjmulist, G.vexnum, v0);
	for (int i = 0; i < G.vexnum; i++) {
		if (i != i0) {
			close[i].adjvex = i0;
			close[i].lowcost = INT_MAX;
		}
	}
	int wei = 0;
	for (int w = FirstAdjVex(G, i0, wei); w >= 0; w = NextAdjVex(G, i0, w, wei)) {
		close[w].lowcost = wei;
	}
	close[i0].lowcost = 0;
	for (int i = 0; i < G.vexnum - 1; i++) {
		int min = INT_MAX;
		int minadj = -1;
		for (int j = 0; j < G.vexnum; j++) {
			if (close[j].lowcost != 0 && close[j].lowcost < min) {
				minadj = j;
				min = close[j].lowcost;
			}
		}
		cout << G.adjmulist[minadj].data << endl;
		close[minadj].lowcost = 0;
		for (int w = FirstAdjVex(G, minadj, wei); w >= 0; w = NextAdjVex(G, minadj, w, wei)) {
			if (close[w].lowcost > wei) {
				close[w].adjvex = minadj;
				close[w].lowcost = wei;
			}
		} 
	}
}


int main () {
	AMLGraph g;
	CreateGraph(&g);
	PrintGraph(g);
	MiniSpanTree_PRIM(g);
	return 0;
}

实现效果:

图的最小生成树 || Prim算法和Kruskal算法的c++实现_第1张图片

Kruskal算法:

  • 算法是将图中所有的边按权值进行排序,每次取权值最小的边且保证选出的边与生成树已包含的边不构成回路。

  • 此算法的关键在于如何判断是否构成回路,这里巧妙地运用了一种叫做并查集的数据结构,也就是到达一个点的出发点,若要新加入的这条边中的两个点的并查集包含相同的出发点,那么这个路径的加入将必然会带来回路,不可取。

算法描述:

设R是有n个顶点的对称连通关系。S={e1,e2,e3……ek}是R的加权边集合。

1)在S中选择最小权的边e1,设E = {e1} 用S- {e1}取代S。
2)在S中选最小权的边ei,并且不与E中的元素形成回路。用E∪{ei}代替E,并用S- {ei}取代S。
3)重复步骤知道|E|=n-1。

并查集:

我们可以把每个连通分量看成一个集合,该集合包含了连通分量的所有点。而具体的连通方式无关紧要,好比集合中的元素没有先后顺序之分,只有“属于”与“不属于”的区别。图的所有连通分量可以用若干个不相交集合来表示。
而并查集的精妙之处在于用数来表示集合。如果把x的父结点保存在uset[x]中(如果没有父亲,uset[x]=x),则不难写出结点x所在树的递归程序:

int FindSet(vector<int> uset, int i) {
	return uset[i] == i ? i : FindSet(uset, uset[i]);
}

意思是,如果uset[x]=x,说明x本身就是树根,因此返回x;否则返回x的父亲uset[x]所在树的根结点。

代码实现:

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
#define MAX_VERTEX_NUM 20
enum VisitIf {unvisited, visited};
class EBox {
public:
	int ivex, jvex; //该边依附的两个顶点位置
	EBox *ilink, *jlink; //分别指向依附这两个顶点的下一条边
	int weight;
};

class VexBox {
public:
	string data;
	EBox *firstedge; //指向第一条依附该顶点的边
	VisitIf mark; //访问标记
};

class AMLGraph {
public:
	VexBox adjmulist[MAX_VERTEX_NUM];
	int vexnum, edgenum; //无向图的当前顶点数和边数
};

int LocateVex(VexBox adj[], int num, string v) {
	for (int i = 0; i < num; i++) {
		if (adj[i].data == v) {
			return i;
		}
	}
}

void CreateGraph(AMLGraph *G) {
	cout << "请输入顶点数和弧数: " << endl;
	cin >> G->vexnum >> G->edgenum;
	cout << "请输入顶点: " << endl;
	for (int i = 0; i < G->vexnum; i++) {
		cin >> G->adjmulist[i].data;
		G->adjmulist[i].mark = unvisited;
		G->adjmulist[i].firstedge = NULL;
	}
	for (int i = 0; i < G->edgenum; i++) {
		cout << "请输入边的两个顶点以及此边的权重: " << endl;
		string v1, v2;
		int vw;
		cin >> v1 >> v2 >> vw;
		int i1 = LocateVex(G->adjmulist, G->vexnum, v1);
		int i2 = LocateVex(G->adjmulist, G->vexnum, v2);
		//cout << i1 << "...." << i2 << endl;
		EBox *p = new EBox;
		p->ivex = i1;
		p->jvex = i2;
		p->weight = vw;
		p->ilink = G->adjmulist[i1].firstedge;
		p->jlink = G->adjmulist[i2].firstedge;
		G->adjmulist[i1].firstedge = p;
		G->adjmulist[i2].firstedge = p;
	}
}

int FindSet(vector<int> uset, int i) {
	return uset[i] == i ? i : FindSet(uset, uset[i]);
}

void MiniSpanTree_Kruskal(AMLGraph G) {
	struct Edge {
		int u;
		int v;
		int weight;
		Edge () : u(0), v(0), weight(0) {}
		Edge (int _u, int _v, int _weight) {
			u = _u;
			v = _v;
			weight = _weight;
		}
		bool operator < (const Edge e) const {
			return this->weight < e.weight;
		}
	};
	vector<Edge> edg;
	EBox *p;
	for (int i = 0; i < G.vexnum; i++) {
		p = G.adjmulist[i].firstedge;
        while(p != NULL)
        {
            if(p->ivex == i)    //判断相等才能知道连接上的是ivex还是jvex;
            {
            	int i1 = LocateVex(G.adjmulist, G.vexnum, G.adjmulist[p->ivex].data);
				int i2 = LocateVex(G.adjmulist, G.vexnum, G.adjmulist[p->jvex].data);
				//cout << i1 << i2 << p->weight << endl;
                if (i1 < i2) {
                	edg.push_back(Edge(i1, i2, p->weight));
                }
                p = p->ilink; 
            }
            else
            {
            	int i1 = LocateVex(G.adjmulist, G.vexnum, G.adjmulist[p->jvex].data);
				int i2 = LocateVex(G.adjmulist, G.vexnum, G.adjmulist[p->ivex].data);
                //cout << i1 << i2 << p->weight << endl;
               	if (i1 < i2) {
                	edg.push_back(Edge(i1, i2, p->weight));
                }
                p = p -> jlink;
            }
        }
	}
	sort(edg.begin(), edg.end());
	vector<int> uset;
	uset.assign(G.vexnum, 0);
	for (int i = 0; i < G.vexnum; i++) {
		uset[i] = i;
	}
	for (int i = 0; i < edg.size(); i++) {
		int e1 = FindSet(uset, edg[i].u);
		int e2 = FindSet(uset, edg[i].v);
		if (e1 != e2) {
			cout << G.adjmulist[edg[i].u].data << "---" << edg[i].weight << "---" << G.adjmulist[edg[i].v].data << endl;
			uset[e2] = e1;
		}
	}
}

int main () {
	AMLGraph g;
	CreateGraph(&g);
	MiniSpanTree_Kruskal(g);
	return 0;
}

实现效果:

图的最小生成树 || Prim算法和Kruskal算法的c++实现_第2张图片

你可能感兴趣的:(数据结构,算法,c++,图)