4克鲁斯卡尔算法—集合运算应用
上图表述了生成树的算法过程
(选最短边,不构成回路)
只要有交集就合并
“原图”的数据对象可以使用邻接表来表达:
IntgData[7][7]
“边长排序图”的数据可以用线性表保存,数据结构:
算法描术:
算法过程,本质上是集合操作,集合的初始状态是空集,然后:
1)点对(1,3)进入集合Vertex[ ]={ {1,3} } 集合里有一个子集,将对应的边选入线性表( Vertex[7 ]={ 0,0,0,0,0,0,0})
2) 点对(4,6)进入集合Vertex={ {1,3},{4,6} } 集合里有两个子集,将对应的边选入线性表。
3)点对(2,5)进入集合Vertex={ {1,3},{4,6},{2,5} } 集合里有三个子集,将对应的边选入线性表。
4)点对(3,6)进入集合Vertex={ {1,3},{4,6},{2,5},{3,6} },点对(3,6)与两个集合{1,3},{4,6}有交集,这时需要将它们变为一个集合,即进行集合并运算。最后集合变为Vertex={ {1,3,4,6},{2,5} },将对应的边选入线性表。
5)点对(1,4)不能进入集合,因为它是集合Vertex的子集,点对(1,4)的边会使生成树构成回路。
6)…依据上述规则,不断选入点对,对于有N个顶点的图,选N-1个点对(N-1条边)即可
总结:点对入选规则
1)不是子集且无交集,入选; 2)有交集,入选;合并相关集合;3)是子集,不入选。
从程序的观点看:
可以使用数组Vertex[]作为集合来描述顶点选入情况。Vertex[]的下标为顶点编号,值为该顶点是否被选。例Vertex[1]=0,表示顶点1不在集合中;Vertex[1]≠0表示顶点1在集合中。另外还需要一个集合编号,对应上述步骤,Vertex的值为:
1)Vertex[]={0,1,0,1,0,0,0} 下标——顶点编号
2)Vertex[]={0,1,0,1,2,0,2}
3)Vertex[]={0,1,3,1,2,3,2}
4)Vertex[]={0,1,3,1,1,3,1}
……
typedefstruct
{
int vtx1; //点对与边长
int vtx2;
int length;
} dataSet;
typedefstruct
{
dataSetVEdge[50];
int count;
} sVEList, *LPListVE;
Vertex[N ]={ 0,0,0,0,0,0,0})
intgData[][N]={ { 0,0,0,0,0,0,0},
;
sVEList LA={0},LB={0};
原始数据读入LA中
Int n=0;
For(i=1;i<=6;i++)
For(j=1;j<=6;j++)
{ if(gdata[i][j] && i<j)
{ LA.VEdge[n].vtx1=I;
LA.VEdge[n].vtx1=j;
LA.VEdge[n].length=gdata[i][j]
LA.count++
}
}
对LA排序
Int k=0 -指向LA头部+1
Int m=1 集合标号
第一条边进入(1,3)
Vertex[N ]={ 0,1,0,1,0,0,0})
第一条边信息进入LB={0}
Vertex[N ]={ 0,1,0,1,2,0,2}) {Vertex[x1]== Vertex[x2]==0}
(通过m++实现)
第2条边信息进入LB={0}
// 克鲁斯卡尔2.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include<functional> #include<iostream> #include<algorithm> #include<vector> using namespace std; typedef struct { unsigned v1; unsigned v2; unsigned weight; }Edges; typedef struct//顺序表定义 { vector<Edges> vec; }Sqlist; void initList(Sqlist &list, const vector<vector<int>> &a)//将顶点对、边长数据读入线性表 { size_t i, j; Edges temp_edges; for (i = 1;i<a.size();i++) for (j = 1;j<i;j++) { if (a[i][j]) { if (i<j) //保证(x,y)中x存放小的顶点数 { temp_edges.v1 = i; temp_edges.v2 = j; } else { temp_edges.v1 = j; temp_edges.v2 = i; } temp_edges.weight = a[i][j]; list.vec.push_back(temp_edges); } } } bool isShorter(const Edges &r_edg, const Edges &l_edg) { return r_edg.weight < l_edg.weight; } void listSort(Sqlist &s) //线性表按边长排序 { sort(s.vec.begin(), s.vec.end(), isShorter); } void printList(const Sqlist &s)//打印线性表 { for (size_t i = 0;i<s.vec.size();i++) { cout << "(" << s.vec[i].v1 << "-->" << s.vec[i].v2 << ",weight is " << s.vec[i].weight << ")" << '\t'; if ((i + 1) % 2 == 0) cout << '\n'; } cout << endl; } void kruskal() { vector<vector<int>> IntgData= { { 0,0,0,0,0,0,0 }, //邻接矩阵创建初始化 { 0,0,6,1,5,0,0 }, { 0,6,0,5,0,3,0 }, { 0,1,5,0,5,6,4 }, { 0,5,0,5,0,0,2 }, { 0,0,3,6,0,0,6 }, { 0,0,0,4,2,6,0 } }; int Vertex[7] = { 0 }; //初始化顶点集Vertex为空集 int setNum = 1; //集合编号 int selectlocation = 0; //所选点对在线性表A位置 size_t seletarc = 1;//选择边的个数; Sqlist A; Sqlist B; //将顶点对、边长数据读入线性表A initList(A, IntgData); //线性表A按边长排序 listSort(A); //printList(A); Edges temp_edges; while (seletarc<IntgData.size()-1) { //A中选一条边,根据他的两个顶点判断该边是否为生成树的边 int x = A.vec[selectlocation].v1; int y = A.vec[selectlocation].v2; //顶点对是Vertex子集,取A中下一个点 if (Vertex[x] == Vertex[y] && Vertex[x] != 0) { selectlocation++; } else { //顶点对与Vertex无交集,进入集合 if (Vertex[x] == 0 && Vertex[y] == 0) { Vertex[x] = setNum; Vertex[y] = setNum; setNum++; } else if (Vertex[x] == 0 && Vertex[y] != 0)//顶点对与一个子集相交,没有交的进入到vertex中 { Vertex[x] = Vertex[y]; } else if (Vertex[x] != 0 && Vertex[y] == 0) { Vertex[y] = Vertex[x]; } else if (Vertex[x] != 0 && Vertex[y] != 0)//顶点对与两个子集相交,相交的两个子集并运算 { for (int i = 1;i<IntgData.size();i++) { int temp = Vertex[y]; if (Vertex[i] == temp) Vertex[i] = Vertex[x];//相邻点复制,取Vertex[y]的值覆盖所有Vertex[x] } } //所选的边进入线性表B temp_edges.v1 = x; temp_edges.v2 = y; temp_edges.weight = A.vec[selectlocation].weight; B.vec.push_back(temp_edges); seletarc++; }//else }//while //最后打印B集合 cout << "最小生成树边为:" << endl; printList(B); } int main() { kruskal(); return 0; }