图的最小生成树的两种算法(Prim & Kruskal)(C/C++)

Prim算法:

假设有两个集合V和U,V存放图中已处理(已加入最小生成树)的顶点,U存放图中未处理的顶点。
每次从集合U中选取一个与 集合V中的顶点 之间权值最小的顶点。
例如初始时选定某一顶点a加入集合V,然后从集合U中选取一个与集合V中顶点之间权值最小的顶点,
此时V中只有a,所以就是选择U中与顶点a之间权值最小的顶点,
假设选中了顶点b,将其也加入集合V,此时最小生成树有a和b两个结点,
即集合V中有两个顶点a和b,所以要从U中寻找与顶点a或顶点b之间权值最小的顶点,
重复这个过程,直到集合U中不再有任何元素(或树的结点数等于图的顶点树)。

需要定义一个辅助数组来记录最小生成树。数组下标对应图的顶点数组的下标(意思是辅助数组的第 i 个位置代表图的第 i 个顶点),
数组每个元素为一个结构体,有两个成员,
成员1为另一个顶点的下标,即该顶点所连接的另一个顶点,亦即最小生成树中一个结点所连接的另一个结点;
成员2为两个顶点之间的权值。
举个栗子,假设在集合U中找到一个顶点u1,与集合V中的顶点v1之间权值最小,接下来则要获取顶点v1在顶点数组中的下标,假设是 i ,
则辅助数组中第 i 位元素需要赋值,其成员1即为顶点u1的下标,成员2即为顶点v1与顶点u1之间的权值。

//图结构的定义
typedef struct AMGraph
{
    int vex[ARRAYSIZE]={0};//顶点数组
    int arc[ARRAYSIZE][ARRAYSIZE];//邻接矩阵,表示顶点之间的关系
    int vexnum=0,arcnum=0;//顶点个数和弧个数
    int type=0;//1表示无向图,2表示有向图,3表示无向网,4表示有向网
}AMGraph;
//最小生成树
//辅助数组:其下标对应顶点数组的下标
typedef struct CloseDge
{
    int adjvex=-1;//当前下标的顶点所连接的另一顶点
    int lowcost=0;//两个顶点之间的权重
}CloseDge;
//Prim算法
void Prim(AMGraph &G,int start)
{
    //从起始顶点start出发,生成图G的最小生成树
    int Tnum=0;//树的结点数
    CloseDge cd[G.vexnum];//建立辅助数组
    //根据起始顶点初始化辅助数组
    for(int i=0;i<G.vexnum;i++)
    {
        cd[i].adjvex=start;
        cd[i].lowcost=G.arc[start][i];
    }
    cd[start].lowcost=0;//将已处理的顶点标记为0
    ++Tnum;//树结点增1
    
    int mincost=0;//记录最小权值
    int minvex=0;//记录最小权值的对应顶点

    while(Tnum<G.vexnum)
    {
        for(int i=0;i<G.vexnum;i++)//遍历每一个顶点
        {
            if(cd[i].lowcost==0)//如果顶点i在集合V中
            {
                mincost=G.arc[i][0];//先假设顶点i与顶点0之间权值最小
                for(int j=0;j<G.vexnum;j++)//遍历每一个顶点
                {
                    if(cd[j].lowcost!=0)//非集合V的元素
                    {
                        if(G.arc[i][j]<=mincost)//如果权值更小
                        {
                            mincost=G.arc[i][j];//更小最小值
                            minvex=j;//更新下标
                        }
                    }
                }
                //将找到的最小权值与辅助数组中的对应位置的权值比较
                if(cd[minvex].lowcost>mincost)//如果刚才找到的权值和对应顶点更小,则更新
                {
                    cd[minvex].lowcost=mincost;
                    cd[minvex].adjvex=i;
                }
            }
        }
        mincost=INT_MAX;
        for(int i=0;i<G.vexnum;i++)//遍历辅助数组,寻找权重最小的顶点
        {
            if(cd[i].lowcost!=0 && cd[i].lowcost<mincost)
            {
                mincost=cd[i].lowcost;
                minvex=i;
            }
        }
        //输出最小生成树
        cout << "(" << G.vex[cd[minvex].adjvex] << "," << G.vex[minvex] << ")" << G.arc[cd[minvex].adjvex][minvex] << endl;
        //将已处理的顶点标记为0
        cd[minvex].lowcost=0;
        Tnum++;
    }
}
Kruskal算法:

对比Prim算法与Kruskal算法,前者是对点进行操作,后者则是对边进行操作。
首先先将图中的所有边进行递增排序,以便我们每次都能选出一条最短边,
如果它和当前的最小生成树不构成回路则将其加入最小生成树,否则将其删除。
然后继续选取最短边,直至所有边都处理完毕。

//图常用的邻接矩阵和邻接表都不适合用于该算法,需要重新定义一种存储结构。
//定义边结点
typedef struct EdgeNode
{
    int v1=0,v2=0,weight=0;//边的两个顶点、权值
}EdgeNode;
//定义图结构
typedef struct myGraph
{
    int vex[MAXVEX]={0};//顶点
    EdgeNode edge[MAXEDGE];//边
    int vexnum=0,edgenum=0;//顶点数、边数
}myGraph;
int LocateVex(myGraph &G,int e);获取顶点位置
int get_root(myGraph &G,int *parent,int e);//获取根结点(返回 值为e的顶点在最小生成树中的根结点的对应图结构中的顶点下标)
void creat_graph(myGraph &G);//创建无向网
//最小生成树
//使生成树以一种随意的方式生长,初始时每个顶点构成一棵树,之后每生长一次就将两棵树合并,
//直到图中所有顶点都合并为一棵树。
void Kruskal(myGraph &G)
{
    //设置一个数组来存放每个顶点在树中的根结点,其下标对应顶点数组的下标,
    int parent[G.vexnum];
    for(int i=0;i<G.vexnum;++i) parent[i]=-1;//初始化数组

    //根据权值排序边数组
    for(int i=0;i<G.edgenum;++i)
        for(int j=i+1;j<G.edgenum;++j)
            if(G.edge[i].weight>G.edge[j].weight)
                swap(G.edge[i],G.edge[j]);

    int r1=-1,r2=-1;//获取顶点在树中的根结点
    for(int n=0,i=0;i<G.vexnum-1;++n)//i表示最小生成树的边数,n个顶点只需n-1条边
    {
        r1=get_root(G,parent,G.edge[n].v1);
        r2=get_root(G,parent,G.edge[n].v2);
        if(r1!=r2)//若两个顶点没有相同的根结点
        {
            parent[LocateVex(G,r2)]=r1;//合并两棵树,即连接两个根结点
            ++i;//边数累加
            cout << "(" << G.edge[n].v2 << "," << G.edge[n].v1 << ")" << G.edge[n].weight << endl;//输出生成树
        }
        //若两个顶点有相同的根结点,则不生成树,防止形成回路
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值