数据结构课程设计报告——求无向连通网的所有不同构的最小生成树

一、课程设计内容与要求

        用字符文件提供数据建立连通带权无向网络邻接矩阵存储结构。编写程序,求所有不同构的最小生成树。要求输出每棵最小生成树的各条边(用顶点无序偶表示)、最小生成树所有边上的权值之和;输出所有不同构的生成树的数目。

二、程序设计报告

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;

}

  • 20
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值