Prim算法和Kruskal都是典型的贪心算法,也就是在求解最小生成树时,我们总是不从全局考虑,而每次仅仅是考虑局部最优解。下面的算法都基于无向图的邻接多重表。
设R是有n个定点的对称连通关系。
//找无向图的最小生成树的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;
}
算法是将图中所有的边按权值进行排序,每次取权值最小的边且保证选出的边与生成树已包含的边不构成回路。
此算法的关键在于如何判断是否构成回路,这里巧妙地运用了一种叫做并查集的数据结构,也就是到达一个点的出发点,若要新加入的这条边中的两个点的并查集包含相同的出发点,那么这个路径的加入将必然会带来回路,不可取。
设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;
}