数据结构(第八章)

图(下)
一.最小生成树
什么是最小生成树?
首先,它是一棵树,没有回路,加入有n个结点,那么它一定有n-1条边。其次,是生成树,也就是意味着它要包含全部顶点,n-1条边都在图里。最后,要求边的权重和最小。满足了这三点,它就是一颗最小生成树。
为了找出这样的一棵树,我们有两种算法
Prim算法
核心思想:我们需要一个邻接矩阵,存取各个顶点之间的权重,其次我们需要两个数组,一个用来存储相关顶点的下标,一个用来存储相关顶点的权重,任意选出一点作为起始点,然后从它的邻接点中,找一个权重最小的,然后连接起来,再以这个两个结点为树,向外扩展,也是找权重最小的,但是得注意不能构成回路,因为如果构成了回路,就满足我们的第一点要求,它就不是一个最小生成树了,然后按照此方法,如此循环,直到所有顶点被连接,大概就是这样。

void MiniSpanTree_Prim(MGraph G)
{
    int min,i,j,k;
    int adjvex[MAXVEX];//用来保存相关顶点的下标
    int lowcost[MAXVEX];//用来保存相关顶点间的权重
    lowcost[0]=0;//初始化第一个权值为0,即V0加入生成树
    adjvex[0]=0;//初始化第一个顶点的下标为0
    for(i=1;i<G.numVertexes;i++)//循环下标为0以外的全部顶点
    {
        lowcost[i]=G.arc[0][i];//将V0顶点与之有边的权重存入数组
        adjvex[i]=0;//初始化下标都为V0的下标
    }
    for(i=1;i<G.numVertexes;i++)
    {
        min=INF;//初始化最小权值为无穷大
        j=1;k=0;
        while(j<G.numVertexes)
        {
            if(lowcost[j]!=0&&lowcost[j]<min)//如果权重不为0,且权重小于min
            {
                min=lowcost[j];//让当前值为最小值
                k=j;//将当前最小值的下标存入K
            }
            j++;
        }
        printf("(%d,%d)",adjvex[k],k);//打印当前权重最小边
        lowcost[k]=0;//将当前顶点的权重设为0,表示此顶点已经完成任务
        for(j=1;j<G.numVertexes;j++)
        {
            if(lowcost[j]!=0&&G.arc[k][j]<lowcost[j])
            {//若下标为k的顶点都小于此前这些顶点未被加入生成树权重
                lowcost[j]=G.arc[k][j];//将较小值存入lowcost
                adjvex[j]=k;//将下标为k的顶点存入adjvex
            }
        }
    }
}

kruskal算法:
核心思想:克鲁斯布尔简单地来说其实就是”将森林合成树“,怎么说呢,我们运用了一个结构体数组,结构体里面存的是起点和终点,以及边的权重,为了防止出现回路的情况,我们还定义了一个数组,用于并查集,防止形成回路,开始的时候这个数组初始为0,然后我们对每一条边进行以下操作:定义一个Find函数,该函数用于查找连线顶点的尾部下标,然后我们判断一下一个结构体的开始和终止的Find值是否相同,如果相同就证明会形成回路,我们就舍弃该点,否则,我们就将该点的记录下来,并输出,就能得到我们的最小生成树了。

typedef struct
{
    int begin;
    int end;
    int weight;
}Edge;
int Find(int *parent,int f)//并查集
{
    while(parent[f]>0)
    f=parent[f];
    return f;
}
void MiniSpanTree_Kruskal(MGraph G)//生成最生成树
{
    int i,m;
    Edge edges[MAXEDGE];//定义边集数组
    int parent[MAXVEX];//定义一数组用来判断边与边是否形成回路
    for(i=0;i<G.numVertexes;i++)
    {
        parent[i]=0;//初始化数组的值为0
    }
    for(i=0;i<G.numVertexes;i++)//循环每一条边
    {
        n=Find(parent,edges[i].begin);
        m=Find(parent,edges[i].end);
        if(n!=m)//如果不相等,就说明此边没有与现有生成树形成回路
        {
            parent[n]=m;
            printf("(%d,%d) %d",edges[i].begin,edges[i].end,edges[i].weight);
        }
    }
}

二.拓扑排序
在了解拓扑排序之前,我们得先知道一个概念,那就是什么是AOV网
在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,这样的有向图为顶点所表示活动的网,我们称为AOV网。
拓扑序列:设G={V,E}是一个具有n个顶点的有向图,V中的顶点序列V1,V2…Vn,满足若从顶点Vi到Vj有一条路径,则在顶点序列中顶点Vi必须在Vj之前,则我们称这样的一个顶点序列为拓扑序列。
简单地解释一下,就比如我们小学的学习一样,如果你想读六年级,那么你必须是从五年级过来的,如果你想读五年级,那么你必定是从四年级来的,拓扑序列其实就是这个意思。
拓扑排序算法:
对AOV网进行拓扑排序的基本思路就是:从网中找一个入度为0的顶点,然后输出此顶点并删除,并且删除以此顶点为尾巴的弧,如此循环即可。
因为要进行删除,所以我们用邻接表就比较方便一些

typedef struct EdgeNode//边表结点
{
    int adjvex;//邻接点域,存储该顶点对应的下标
    int weight;//用于存储权值,对于非网图可以不需要
    struct EdgeNode *next;//邻域,指向下一个邻接点
}EdgeNode;
typedef struct VertexNode
{
    int in;//入度
    int data;//顶点域,存储顶点信息
    EdegeNode *firstedge;//边表头指针
}
typedef struct
{
    AdjList adjList;
    int numVertexes,numEdges;//图中当前顶点数和边数
}graphAdjList,*GraphAdjList;
status TopologicalSort(GraphAdjList GL)
{
    EdgeNode *e;
    int i,k,gettop;
    int top=0;//用于栈指针下标
    int count=0;//用于统计输出的顶点个数
    int *stack;//建栈存储入度为0的顶点
    stack=(int *)malloc(GL->numVertexes*sizeof(int));
    for(i=0;i<GL->numVertexes;i++)
    {
        if(GL->adjList[i].in=0)
        {
            stack[++top]=i;//将入度为0的顶点入栈
        }
    }
    while(top!=0)
    {
        gettop=stack[top--];//出栈
        printf("%d->",GL->adjList[gettop].data);//打印此顶点
        count++;//统计输出顶点数
        for(e=GL->adjList[gettop].firstedge;e;e=e->next)
        {//对此顶点弧表遍历
            k=e->adjvex;
            if(!(--GL->adjList[k].in))//将K号顶点邻接点的入度减1
            {
                stack[++top]=k;//若为0则入栈,以便于下次循环输出
            }
        }
    }
    if(count<GL->numvertexes)//如果count小于顶点数,说明存在环路
    {
        return ERROR;
    }
    else
    {
        return OK;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值