一、课程设计内容与要求
用字符文件提供数据建立连通带权无向网络邻接矩阵存储结构。编写程序,求所有不同构的最小生成树。要求输出每棵最小生成树的各条边(用顶点无序偶表示)、最小生成树所有边上的权值之和;输出所有不同构的生成树的数目。
二、程序设计报告
2.1 总体设计
求无向带权连通图的所有不同构的最小生成树。
(1)从字符文件获取数据建立连通带权无向网络邻接矩阵存储结构。
(2)使用克鲁斯卡尔(Kruskal)算法。在按边的权值进行升序排序时,交换权值相等的边的先后顺序,求得该排序顺序下对应的最小生成树。
(3)将所有排序顺序对应的最小生成树存储起来,删除其中同构的最小生成树,即可得到该无向带权连通图的所有不同构的最小生成树。
2.2 详细数据结构设计
程序主要的数据结构有:无向带权连通图的数据结构和最小生成树的数据结构。
1、图的数据结构
该无向带权连通图使用邻接矩阵的数据结构。二维矩阵G[n][n]。
对其中一个点G[i][j],其下标i、j表示边的两个顶点在顶点值数组(string Data[n])中的位置,其值表示边的权值(0表示两顶点不邻接,即该边不存在)。
考虑到字符文件中的图的存储形式,以及权值读取的方便性。故采用邻接矩阵的数据结构。
2、最小生成树的数据结构
最小生成树使用单向链表的数据结构。
结构体Edges,为边的存储结构,包括两个顶点位置int Head和int Tail,和权值int Lowcost。用于存储最小生成树中每条边的信息。
结构体TG为链表结点,其中Edges *e申请内存存储最小生成树的边信息,next指向下一个结点,每一个链表结点存储一个最小生成树。
考虑到最小生成树的数目不易确定,且为了存储的方便性。故采用单向链表的数据结构。
2.3 详细算法设计
求所有不同构的最小生成树算法(利用克鲁斯卡尔算法):
(1)MiniSpanTree_Kruskal函数。获取图的邻接矩阵中所有边信息,存入数组E中,按照权值升序排序(EdgesSort函数),同时创建一个数组E的副本数组E2用于后续使用。
(2)求所有的最小生成树。由克鲁斯卡尔算法可得,需交换权值相等的边在数组E中的位置,数组E所有的排列顺序得到所有的最小生成树,其中有同构的树。
1、寻找权值重复的数组E子段(GetRepPos函数)。
通过比较当前位置和后一个位置的边的权值,判断是否有权值重复的边。如果遇到有权 值重复的边,退出,并记录第一个重复边的位置。否则持续向后面的边移动,直到数组E的 最后。
然后以第一个重复边的位置为开始,通过比较当前边和后一位的边的权值,判断权值是 否相等。如果相等,则继续向后判断,同时相同权值边的个数加一;如果不等,则结束。
2、将该权值重复的数组E子段进行全排列(perm函数)。
若有n条权值相同的边,即将0—n-1这n位数进行全排列,通过递归实现。
遍历0—n-1这n位数字,选择其中未被选择(flag[i]=0)的一个数字i作为当前第k位a[k]选择 的数字,同时把数字i标记为已选择(flag[i]=1),而后进行下一位k-1位a[k+1]的选择,直到把0 —n-1这n个数字填入全部的n位中,完成一次排列。在使用完一个已选择数字后,要重新把 该数字设为未选择(flag[i]=0),以供其他位置选择。
在完成一次排列后,按数组a中的值,从副本数组E2中复制(Copy函数)边值存入数组E 对应位置中,完成对数组E子段的排列。
3、在完成一次排列后,继续调用GetRepPos函数,寻找下一段权值重复的边进行全排列perm函数。直到所有权值重复的数组E子段都进入全排列。
4、当数组E查找到结尾时,表示数组E的一种顺序已经排列好。以此数组E中的边的顺序求对应的最小生成树(NewTG函数)。
通过依次选择权值最小的边,判断该边的两个结点是否连通,若未连通则将与其中一个点连 通的所有点连通到另一个点上,若已连通则跳过,即克鲁斯卡尔算法。利用前插法,将 生成的最小生成树结点插入到链表头结点T后面。
(3)删除同构的最小生成树(DeleteRepeatTree函数)
通过遍历最小生成树链表T,从第一个结点开始,将当前结点与之后的所有结点进行比较(compare函数)。即遍历两个结点表示的最小生成树的每条边,分别将其权值存入边对应的头尾结点i中相连的第j条边的位置B1[i][j]、B2[i][j]。而后比较两个最小生成树对应的顶点的权值(第一个树的第i个顶点与第二个树的第j个顶点B1[i]、B2[j])(IsSame函数),判断两个顶点相连的边的权值是否相同。如果边的权值全部相同,表明两个顶点相同。如果两个树全部的顶点都相同,表明两树同构。如果两个最小生成树同构,则删除后一个结点,然后比较下一个结点,直到把当前结点之后的所有结点都比较过,然后把当前结点后移一个,重复上述操作。直到所有的最小生成树都进行比较,并删除了所有重复的最小生成树。
三、程序测试报告
测试1:
测试2:
四、源程序代码
#include<iostream>
#include<fstream>
#include<string>
#include<stack>
using namespace std;
typedef struct Edges{ //边信息
int Head;
int Tail;
int Lowcost;
}Edges;
typedef struct TG{ //最小生成树结点
Edges *e=NULL;
TG *next=NULL;
}TG;
class Graph{
public:
int n; //总顶点数
int **G; //邻接矩阵
string *Data; //顶点值
int EN; //总边数
Edges *E; //全部边的信息
Edges *E2; //副本,用于全排列中给E赋值
int ZEN; //最小生成树的边数(总顶点数-1)
TG T; //最小生成树的头结点
void CreateGraph(){ //字符文件读取
fstream file;
file.open("graph.txt",ios::in);
if(!file){
cout << "文件打开失败!" << endl;
exit(0);
}
string buf;
//处理顶点个数(第一行)
getline(file,buf);
n = stoi(buf);
//处理顶点值(第二行)
Data = new string[n];
getline(file,buf);
int j=0;
for(int i=0;i<n;i++){
while(j<buf.length() && buf[j]==' '){ //跳过空格
j++;
}
while(j<buf.length() && buf[j]!=' '){
Data[i] += buf[j++];
}
j++;
}
//建立邻接矩阵。字符文件中按顶点顺序,每一行存入该顶点与邻接点的权值
G = new int*[n];
for(int i=0;i<n;i++){
G[i] = new int[n];
}
int count=0; string c;
for(int i=0;i<n;i++){
getline(file,buf);
count = 0;
j=0;
while(count<buf.length()){
c = "";
while(count<buf.length() && buf[count]==' '){ //跳过空格
count++;
}
while(count<buf.length() && buf[count]!=' '){ //获取输入的权值
c += buf[count++];
}
G[i][j++] = stoi(c); //将字符串转变为int型
}
}
file.close();
}
void EdgesSort(Edges *e){ //将所有边e按权值升序排列
int k=0;
for(int i=0;i<EN-1;i++){
k = i;
for(int j=i+1;j<EN;j++){
if(e[k].Lowcost>e[j].Lowcost) k = j; //如果j的权值比k小
}
if(k != i){ //交换边
int temp;
temp = e[k].Head;e[k].Head = e[i].Head;e[i].Head = temp;
temp = e[k].Tail;e[k].Tail = e[i].Tail;e[i].Tail = temp;
temp = e[k].Lowcost;e[k].Lowcost = e[i].Lowcost;e[i].Lowcost = temp;
}
}
}
void Copy(Edges *p1,Edges *p2){ //把p2的值赋给p1
p1->Head = p2->Head;
p1->Tail = p2->Tail;
p1->Lowcost = p2->Lowcost;
}
void NewTG(Edges *e){ //求在当前所有边E的边序列下,求得的最小生成树
int Vexset[n];
for(int i=0;i<n;i++){ //所有结点全部独立
Vexset[i] = i;
}
int count = 0;
for(int i=0;i<EN;i++){ //遍历所有边
int v1 = E[i].Head;
int v2 = E[i].Tail;
int vs1 = Vexset[v1];
int vs2 = Vexset[v2];
if(vs1 != vs2){ //如果该两点未连通,可加入该边
//将该边存入最小生成树中
e[count].Head = v1;
e[count].Tail = v2;
e[count].Lowcost = E[i].Lowcost;
count++;
//将两点连通,即将与其中一点连通的点并入另一个点中
for(int j=0;j<n;j++){
if(Vexset[j]==vs2) Vexset[j] = vs1;
}
}
}
}
void perm(int *flag,int *a,int k,int g,TG *p,int Number){ //递归全排列
if(k==g){ //得到一次全排列
for(int i=0;i<g;i++){ //按照全排列,修改E中的边的顺序
Copy(E+Number+i,E2+Number+a[i]);
}
GetRepPos(p,Number+g); //进入下一个重复权值段的寻找
return;
}
for(int i=0;i<g;i++){
if(flag[i]==0){ //选择一个未被选择的值(a[k] = 0~n-1)
a[k] = i; //将该值存入当前位置(k = 0~n-1)
flag[i] = 1;
perm(flag,a,k+1,g,p,Number); //进入下一位置(k+1)的选值
flag[i] = 0;
}
}
}
void GetRepPos(TG *p,int Number){ //获取权值重复段的位置
while(1){ //在所有边E范围内找到权值相同的边。(权值相同的边进行交换,才能保证为最小生成树)
if(Number+1 >= EN){
break;
}
if(E[Number].Lowcost == E[Number+1].Lowcost){
break;
}
Number++;
}
if(Number >= EN-1){ //已经没有可以交换的边
TG *New1 = new TG; //申请结点
New1->e = new Edges[ZEN]; //申请存储最小生成树的内存
NewTG(New1->e);//求最小生成树
New1->next = p->next; //前插法建立链表
p->next = New1;
return;
}
int count=0; //权值相同的边的个数
while(1){
if(Number+count+1 >= EN){
break;
}
if(E[Number+count].Lowcost != E[Number+count+1].Lowcost){
break;
}
count++;
}
count++;
int flag[count],a[count]; //flag标识该位置的值是否已被选择,a存储当前位置k的值
for(int i=0;i<count;i++){
flag[i]=a[i]=0;
}
perm(flag,a,0,count,p,Number); //将该重复权值段放入全排列中
}
bool IsSame(int *B1,int *B2){ //判断两个顶点的权值是否相同
int L1 = 0; //获取两个顶点的边数
while(B1[L1]) L1++;
int L2 = 0;
while(B2[L2]) L2++;
if(L1!=L2) return false; //边数不等表示不匹配
bool C[L2];
for(int i=0;i<L2;i++) C[i] = false;
bool b;
for(int i=0;i<L1;i++){
b = false;
for(int j=0;j<L2;j++){
if(B1[i] == B2[j] && !C[j]){ //有边匹配
b = true;
C[j] = true;
break;
}
}
if(!b) return false; //有一条边没有匹配上,表示两顶点不匹配
}
return true; //两顶点匹配
}
bool compare(TG *p1,TG *p2){ //判断两个最小生成树是不是同构的
int B1[n][n-1]; //与顶点相连的边的权值
int B2[n][n-1];
bool C[n]; //判断该顶点是否被选取过
for(int i=0;i<n;i++){ //全部赋值为
C[i] = false;
for(int j=0;j<n-1;j++){
B1[i][j] = 0;
B2[i][j] = 0;
}
}
for(int i=0;i<ZEN;i++){ //将找到每条边的头尾结点,在对应位置附上权值
int H1 = p1->e[i].Head;
int T1 = p1->e[i].Tail;
int H2 = p2->e[i].Head;
int T2 = p2->e[i].Tail;
for(int j=0;j<n-1;j++){ //找到没有赋值的位置
if(H1 != -1 && B1[H1][j] == 0){
B1[H1][j] = p1->e[i].Lowcost;
H1 = -1;
}
if(T1 != -1 && B1[T1][j] == 0){
B1[T1][j] = p1->e[i].Lowcost;
T1 = -1;
}
if(H2 != -1 && B2[H2][j] == 0){
B2[H2][j] = p2->e[i].Lowcost;
H2 = -1;
}
if(T2 != -1 && B2[T2][j] == 0){
B2[T2][j] = p2->e[i].Lowcost;
T2 = -1;
}
}
}
bool b;
for(int i=0;i<n;i++){
b = false;
for(int j=0;j<n;j++){
if(IsSame(B1[i],B2[j]) && !C[j]){ //判断两个顶点的边的权值是否相同
C[j] = true; //标识该顶点已被选择
b = true; //标识匹配成功
break;
}
}
if(!b) return false; //如果有一个顶点没有匹配上,表示不同构
}
return true; //如果所有顶点的权值都匹配上,表示同构
}
void DeleteRepeatTree(){ //删除同构的最小生成树
TG *p1=T.next;
if(p1==NULL) return;
TG *p2 = p1->next;
TG *pr = NULL;
while(p1){
pr = p1;
p2 = pr->next;
while(p2){
if(compare(p1,p2)){ //两个最小生成树同构
TG *temp = p2;
pr->next = p2->next;
p2 = p2->next;
delete []temp->e;
delete temp;
continue;
}
pr = p2;
p2 = p2->next;
}
p1 = p1->next;
}
}
void MiniSpanTree_Kruskal(){
EN = 0;
for(int i=0;i<n;i++){
for(int j=i;j<n;j++){
if(G[i][j] > 0) EN++; //计算所有边的条数
}
}
E = new Edges[EN]; //申请所有边的存储空间
E2 = new Edges[EN];
int count = 0;
for(int i=0;i<n;i++){
for(int j=i;j<n;j++){
if(G[i][j] > 0){ //上三角矩阵获取边信息
E[count].Head = i;
E[count].Tail = j;
E[count].Lowcost = G[i][j];
E2[count].Head = i;
E2[count].Tail = j;
E2[count].Lowcost = G[i][j];
count++;
}
}
}
EdgesSort(E); //边排序
EdgesSort(E2);
ZEN = n-1; //最小生成树的边数
GetRepPos(&T,0); //获取所有最小生成树
DeleteRepeatTree(); //删除重复的最小生成树
}
void Show(){
TG *p = T.next;
int count = 0;
int WightSum=0;
while(p){
count++;
cout << "第" << count << "棵最小生成树:" << endl;
for(int i=0;i<ZEN;i++){
cout <<"("<<Data[p->e[i].Head] << "," << Data[p->e[i].Tail] << ")" << " ";
}
cout <<endl;
p = p->next;
}
p = T.next;
if(p){
for(int i=0;i<ZEN;i++){ WightSum += p->e[i].Lowcost; }
}
cout <<"权值之和为:"<<WightSum<<endl;
cout << "不同构的生成树的数目为:"<<count<<endl;
}
~Graph(){
for(int i=0;i<n;i++){
delete []G[i];
}
delete []G; //删除顶点
delete []Data;
delete []E;
delete []E2;
TG *pr,*p;
pr = p = T.next;
while(p){
pr = p;
p = p->next;
delete []pr->e;
delete pr;
}
}
};
int main(void)
{
Graph G;
G.CreateGraph(); //创建邻接矩阵
G.MiniSpanTree_Kruskal(); //求得最小生成树
G.Show();
return 0;
}