数据结构小结(八)图的使用

本文深入讲解了图论中的经典算法,包括最小生成树的Prim算法和Kruskal算法、拓扑排序、关键路径分析、Dijkstra算法及Floyd算法等,详细介绍了这些算法的原理、步骤及代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最小生成树

prim 算法

普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法
搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点,且其所有边的权值之和亦为最小。
该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克发现;并在1957年由美国计算机科学家罗伯特·普里姆
 独立发现;1959年,艾兹格·迪科斯彻再次发现了该算法。因此,在某些场合,普里姆算法又被称
 为DJP算法、亚尔尼克算法或普里姆-亚尔尼克算法。    

(1).输入:一个加权连通图,其中顶点集合为V,边集合为E;

(2).初始化:Vnew = {x},其中x为集合V中的任一节点(起始点),Enew = {},为空;

(3).重复下列操作,直到Vnew = V:

a.在集合E中选取权值最小的边,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并 且v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);

b.将v加入集合Vnew中,将边加入集合Enew中; 4).输出:使用集合Vnew和Enew来描述所得到的最小生成树。

void prim(AdjMatrix * G,int start){
    int i,e,k,m,min;
    local[start].lowcost = 0;

    for(i = 1;i <= G->vexnum;i++){      /*第一次初始化全局数组*/
        if(i != start){
            local[i].adjvex = start ;
            local[i].lowcost = G->arcs[start][i];   
        }
    }
    printf("start :%d\n",start);    /*打印开始结点*/
    for(e = 1; e <= G->vexnum-1;e++){
        min = INFINITY;
        for(k = 1;k <= G->vexnum;k++){
            if(local[k].lowcost != 0 && local[k].lowcost < min){
                m = k;
                min = local[k].lowcost;
            }
        }
        local[m].lowcost = 0;      
        printf("%d\n",m);            /*打印后边遍历的结点*/
        for(i = 1;i <= G->vexnum;i++){
            if(i != m && G->arcs[m][i] < local[i].lowcost){
                local[i].lowcost = G->arcs[m][i];
                local[i].adjvex = m;
            }
        }
    }

}

PS:时间复杂度为O(n^2),适合稠密图。

Kruskal 算法

这个算法思想不难,就是用代码实现费点事。

思想:适用贪心准则从剩下的边中选择不会产生回路且具有最小权值的边加入到生成树的边集中。

步骤:根据每一条边的权值,将边集排序

     建立顶点的集合
     建立边的空集合
     逐步从边集从权值小的开始,挑选边加入边的空集,必须保证顶点集合中不能有重复结点,防
止环的产生
     最后得到的边集即为所求。

代码实现步骤: 1.建立邻接表或邻接矩阵

2.建立排序集合

3.建立顶点集合

4.建立最终完成边的集合

5.挑选最小权值的边序偶

#define   MAXVEX      20
#define   INFINITY    32767


typedef int vextype ;

typedef struct AdjMatrix{

    int arcs[MAXVEX][MAXVEX];
    vextype vex[MAXVEX];
    int vexnum;
    int arcnum;

}AdjMatrix;        /*邻接矩阵*/


typedef struct group{

    int  vex1;
    int  vex2;
    int  weight;

}group;       /*表示一条边*/

typedef struct top_group{

    group  topgroup[MAXVEX];
    int vexnum;
    int arcnum;

}top_group;  /*边的排序集合*/


typedef struct arc{

    int vex1;
    int vex2;
    int weight;
    struct arc *next;

}arc;       /*顶点邻接表


typedef struct set{

    arc vexarry[MAXVEX];
    int array_count;

}set; /*顶点邻接表*/


int  charge(top_group *topgroup,set *S,int i){
    int t_vex1,t_vex2,t_weight;
    int j,k;

    t_vex1 = topgroup->topgroup[i].vex1;
    t_vex2 = topgroup->topgroup[i].vex2;
    t_weight = topgroup->topgroup[i].weight;

    for(j = 1;j <=topgroup->vexnum;j++ ){
        if(S->vexarry[j].vex1 == t_vex1){
            arc *p;
            p = (arc *)malloc(sizeof(struct arc));
            if(S->vexarry[j].next == NULL){
                p->vex1 = t_vex2;
                S->vexarry[j].next = p;
            }else{
                arc *temp;
                temp = S->vexarry[j].next;
                do{
                    if(NULL == temp->next){
                        break;
                    }else{
                        temp = temp->next;
                        if(temp->vex1 == t_vex2){
                            return 0;
                        }
                    }
                }while(1);

            }
        }else{
            if(S->vexarry[j].vex1 == t_vex2){
                arc *p;
                p = (arc *)malloc(sizeof(struct arc));
                if(S->vexarry[j].next == NULL){
                    p->vex1 = t_vex1;
                    S->vexarry[j].next = p;
                }else{
                    arc *temp;
                    temp = S->vexarry[j].next;
                    do{
                        if(NULL == temp->next){
                            break;
                        }else{
                            temp = temp->next;
                            if(temp->vex2 == t_vex1){
                                return 0;
                            }
                        }
                    }while(1);
                }
            }
        }
    }

    return 1;
}


void kruskal(AdjMatrix *G,top_group *topgroup,set *S){

    int i,j,k;
    int  flag;
    for(i = 1;i <= G->arcnum;i++){
        flag = charge(topgroup,S,i);
        if(flag == 1){
            printf("%d %d\n",topgroup->topgroup[i].vex1,topgroup->topgroup[i].vex2);
        }

    }

}

 

拓扑排序和关键路径

拓扑排序

有向图的弧可以看成是顶点之间的一种制约关系的一种描述。我们将顶点表示活动,弧表示活动优先关系的有向无环图,称为顶点表示活动的网,简称AOV 网,图中不允许出现回路。

对于一个AOV 网一般满足以下的情况的时候我们称之为拓扑序列。 (1)网中的所有顶点都在序列中。 (2)若顶点Vi到Vj存在一条关键路径,则在线性序列中,Vi一定存在Vj之前

构造一个拓扑序列的操作称之为拓扑排序。 实际上,拓扑排序就是离散数学中由某个集合上的一个偏序集上的一个偏序得到该集合上的一个全序操作。

代码实现步骤: 1.建立邻接表 2.初始化入度数组 3.开始拓扑 4.在拓扑的过程中依次打印出入度为0的结点

这里依然只展示核心步骤的代码,完全可执行代码,已附在目录中
void AdjList_create(AdjList *G){

    int i,j,k;
    int vex1,vex2,weight;
    if(!G){
        printf("G need cast a spece\n");
        return ;
    }

    printf("please enter the vexnum and arcnum\n");

    scanf("%d,%d",&G->vexnum,&G->arcnum);

    for(i = 1;i <= G->vexnum;i++){

        G->vertex[i].head  = NULL;

    }


    for(i = 1;i <= G->arcnum;i++){
        printf("please enter the vex1 and vex2 and weight\n");
        scanf("%d,%d,%d",&vex1,&vex2,&weight);
        add_node(G,vex1,vex2,weight);

    }

}

void Topsort(AdjList *G,int *indegree){   /*拓扑主函数*/

    int i,j,k;
    get_in_degree(G,indegree);    /*初始化存储度的数组*/
    for(i = 1;i <= G->vexnum;i++){
        k = get_zero(G,indegree);    /*获得度为0的项*/
        if(k != -1){
            printf("%d \n",k);
            set_sub(G,indegree,k);   /*获得度为0的项的时候它所有出度元素的入度--*/
        }
    }


}

void get_in_degree(AdjList *G,int *indegree){

    int i;
    ArcNode *temp;
    for(i = 1; i <= G->vexnum; i++){

        indegree[i] = 0;

    }

    for(i = 1;i <= G->vexnum;i++){

        temp = G->vertex[i].head;
        while(temp != NULL){
            indegree[temp->adjvex]++;
            temp = temp->next;
        }

    }

}

int get_zero(AdjList *G,int *indegree){

    int i,j;
    for(i = 1;i <= G->vexnum;i++){
        if(indegree[i] == 0){
            indegree[i] = -1;
            return i;
        }
    }
    return FAIL;   
}

int set_sub(AdjList *G,int *indegree,int a){
    int i,j;
    ArcNode *temp;
    temp = G->vertex[a].head;
    while(temp != NULL){
        indegree[temp->adjvex]--;
        temp = temp->next;
    }

}




关键路径

一些概念

关键路径一般有如下的性质: (1)只有在某项顶点所代表的事件发生后,从该顶点发出的所有有向边所代表的活动才能开始;

(2)只有在进入某一顶点的各个有向边所代表的活动均已完成,该顶点所代表的事件才能发生;

源点

网中只有一个入度为0的顶点称之为源点。

汇点

仅有一个出度为0的顶点称之为汇点。

关键路径

从汇点到源点中具有最长路径长度的路径称为关键路径。

思想以及步骤

关键路径的求解步骤:

1.根据拓扑排序求出每一个结点的最早发生时间和最迟发生时间

2.计算出每一个活动的最早开始时间和最晚发生时间

3.比较每一个活动的最早开始时间和最晚发生时间,如果相同这就是关键活动

这里我是分别求出了这4个数组的值。然后进行处理,确实比书上的逻辑复杂太多,仅仅就这个算法而言书上的逻辑与代码就很好了,我自己写的确实复杂了。

我写的代码在附录,有兴趣的可以看下,不过真心复杂难懂,自己看着都难受。

 

最短路径

前言

这是图论的最后一个部分了,吸取之前的教训,我总以为自己写的代码比书上的好,但是上次关键路径
把我教育了,我写得确实没书上的好,也有自己对算法不是很熟练的关系,所以最短路径这里我打算
把思路先搞清,然后先学习书上的代码,再看看自己有没有更好的想法,然后不要自以为是。先学会走
,才能学会跑。
以上。

Dijkstra 算法

最短路径一般分为两种情况:

单源点最短路径问题

每对顶点之间的最短路径问题

这个算法就是解决单源点最短路径的一个常用算法是Dijkstra ,说白一点就是求解一个点,到各个点 之间的最短路径的方法。

我尽量描述清楚这个算法的步骤吧。 存在一个集合S,存放源点逐步到达的顶点。

1.确定源点到它可以到达的顶点之间的距离。

2.选择一个最短距离,并将这个顶点入集合S。

3.将已经入过集合的顶点置为空,表示以后都不会选择它。

4.继续从1开始,直到所有顶点都被置空。

void Dijkstra(AdjMatrix *G,int start,int end,int *dist,int path[][MAXVEX]){
    int mindist,i,j,k,t = 1;
    for(i = 1;i <= G->vexnum;i++){  
        dist[i] = G->arcs[start][i];   /*初始化dist为start到每一个顶点的距离*/
        if(G->arcs[start][i] != INFINITY)   /*如果开始结点可以到达某棵结点,则路径集合中加入这个结点*/
            path[i][1] = start;
        path[i][0] = 0;            /*初始化很重要,不然会出错,也可以到外面初始化*/
    }
    path[start][0] = 1;        /*表示开始结点已经被表示为进入S集合*/
    for(i = 2;i <= G->vexnum;i++){   /*寻找各条最短路径*/

        mindist = INFINITY;          /*初始化最小距离为INFINITY*/
        for(j = 1;j <= G->vexnum;j++){  /*选取权值最小路径*/

            if(!path[j][0] && dist[j] < mindist){   /*未进入S且当前路径小与 

            最小*/
                k = j;                       
                mindist = dist[j];
            }
            if(mindist == INFINITY){ 
                continue;
            }

            path[k][0] = 1;
            for(j = 1;j <= G->vexnum;j++){  /*修改路径*/
                if(!path[j][0] && G->arcs[k][j] < INFINITY && dist[k]+G->arcs[k][j] < dist[j]){
                    dist[j] = dist[k] + G->arcs[k][j];
                    t = 1;
                    while(path[k][t] != 0){   /*将当前路径记录下来,复制置前
                    的路径然后添加最新的结点*/
                        path[j][t] = path[k][t];
                        t++;
                    }
                    path[j][t] = k;
                    path[j][t+1] = 0;
                }
            }
        }
    }


}

Floyd 算法

我还是尽量描述清楚这个算法的步骤吧。

前提:建立矩阵F,用于记录路径长度。能到达的路径都直接给于权值,不能到达的则设置无穷值。

这就是我们的F0矩阵,显然只是初始化了而已。

1.让顶点经过第一个顶点V0,然后比较路径(Vi,Vj)与路径(Vi,V0,Vj)的路径长度较短的一个。

2.往复这个步骤,然后经过n 次后就把n个顶点都考虑在路径中了。

3.最终得到的这个F矩阵就保存了所有的路径之间的关系了,注意中间斜对角线始终是0

这个代码比较好理解,就是循环多。

void  Floyd(AdjMatrix *G,int F[][MAXVEX]){

    int path[MAXVEX][MAXVEX];
    int i,j,k;

    for(i = 1;i <= G->vexnum;i++){
        for(j = 1;j <= G->vexnum;j++){
            F[i][j] = G->arcs[i][j];
            path[i][j] = INFINITY;
        }
    }

    for(i = 1;i <= G->vexnum;i++){
        for(j = 1;j <= G->vexnum;j++){
            for(k = 1;k <= G->vexnum;k++){
                if(F[i][j] > F[i][k] + F[k][j]){
                    F[i][j] = F[i][j] + F[k][j];
                    path[i][j] = F[i][k] + F[k][j];
                }
            }
        }
    }

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值