图的广度和深度遍历,Prim和Kruskal

采用 Prim 算法进行通信网的构建,采用 Kruskal 算法进行通信网的构建 (C语言的实现)

在 n 个城市之间建立通信联络网,则连通 n 个城市只需要 n-1 条线路。要求在
最节省经费的前提下建立这个通信网。

算法设计

主要算法设计:
结构设计:

//定义一个顶点结构
typedef struct Vertex{
    char city[50];          //城市名称
    char name;              //代号
    bool visited = false;   //访问标记
    int num;                //序号
}Vertex;
//定一个边结构
typedef struct Edge{
    int weight;//代价
    char city[50];
    char v1;
    char v2;
    int mount=0;
}Edge;
//定义一个Kruskal边
typedef struct K_Edge{
    int start;
    int end;
    int weight;
}K_Edge;
//图的定义
typedef struct Graph{
    int numbers;                    //图中实际的顶点数
    Vertex vex[Max_Size];           //容量
    int adjMax[Max_Size][Max_Size]; //容量
    int current_Vertex = 0;
}Graph;

(一):图的深度优先遍历:

设计思路:
从图中的任意一个顶点开始,访问该顶点,然后将该顶点的访问状态改为已访问状态,然后进栈。之后开始以该顶点找到一个与他相连的下一个顶点进行上述的操作,然后这个顶点进栈,更改他的访问状态,然后访问下一个没有被访问的顶点。如果他没有下一个没有被访问的顶点,然后他就出栈,之后读取栈顶元素,然后该元素还有没有没有被访问的顶点,重复上述步骤直到栈为空为止,此时整个图就遍历完成。
上代码(C):

//深度优先遍历
void dfs(Graph*g){
    int stack[Max_Size];//栈
    int top = 0;
    int current =0;//邻接矩阵的访问行数
    g->vex[0].visited = true;   //标记为已经访问状态
    stack[0] = g->vex[0].num;   //将该顶点的序号压入栈中
    printf("%c ",g->vex[0].name);//打印顶点代号
    while(top != -1){            //栈不为空
        int mark = 0;            //用于判断是否找到通的边
        for(int i = current+1;i<g->numbers;i++){
                //访问没有被访问过的顶点而且是通的
            if(g->adjMax[current][i] != NoEdge&&g->vex[i].visited==false){
                stack[++top] = i;//找到的顶点的序号进栈
                g->vex[i].visited = true;//标记为已经访问
                printf("%c ",g->vex[i].name);
                mark++;
                break;//找到之后就退出for循环
            }
        }
        if(mark!=0){        //for循环之后找到了通的顶点
            current = stack[top];
        }else {             //序号出栈成为新的访问行号
            current = stack[--top];
        }
    }
}

(二):图的广度优先遍历:

设计思路:
方法同图的深度优先遍历,要创建一个新的队列,同样从图中的任意一个顶点开始,一个顶点入队,访问该顶点并标记他的访问状态为已经访问,已经找到的顶点入队,然后找另外一个与他相连的顶点,并且没有访问的,重复上述操作直到队首顶点没有还没有访问的顶点,然后出队,再从队首读取顶点,重复上述步骤,直到最终的顶点全部出队为止,此时图的所有顶点已经全部遍历。
算法实现C语言:

//广度优先遍历
void bfs(Graph*g){
    int queue[Max_Size];
    int current,head,foot;
    current=head=foot=0;
    g->vex[0].visited = true;
    queue[0] = g->vex[0].num;foot++;
    printf("%c ",g->vex[0].name);//打印顶点代号
    while(head!=foot){//队列不为空
        for(int i=current+1;i<g->numbers;i++){
            if(g->adjMax[current][i] != NoEdge&&g->vex[i].visited==false){
                queue[foot++] = g->vex[i].num;
                g->vex[i].visited = true;
                printf("%c ",g->vex[i].name);
            }
        }
        current = queue[++head];
    }
}

(三):最小生成树(Prim):

设计思路:
次数 从图中取出的顶点集合U 图中剩余的顶点集合U_V 选出的符合条件的边的集合TE
U————————U_V——————TE
1 {A}————{B,C,D,E,F}————{A-C}
2 {A,C}————{B,D,E,F} ————{A-C,C-F}
3 {A,C,F} ————{B,D,E} ————{A-C,C-F,F-D}
4 {A,C,F,D} ————{B,E} ————{A-C,C-F,F-D,C-B}
5 {A,C,F,D,B} ————{E} ————{A-C,C-F,F-D,C-B,B-E}
从图中所有的顶点当中取出一个,它放入U集合当中,剩余的顶点在U_V集合中,然后两个集合当中的顶点,各选出一个顶点组成边,然后比较边的权值大小,导出权值最小的边,让他存入集合TE当中,进行图中顶点数n-1次循环,找出符合条件的n-1条边,然后用这些边将顶点连接起来,形成最小生成树.
算法实现C语言代码:

//Prim算法
int Prim(Graph* g,Edge TE[]){
    char U[Max_Size];int u=0;//取出的顶点集合数组
    char V_U[Max_Size];int v=0;//剩余顶点的集合
    int te=0;int top=g->numbers;
    for(int i=0;i<g->numbers;i++){
        V_U[i]=g->vex[i].name;//初始化剩余数组
    }
    U[u]=V_U[v++];//向取出集合中添加一个顶点
    int index1=0,index2=0;char tmp;
    for(int i=0;i<g->numbers-1;i++){
        if(u!=0){
            U[u]=V_U[index2];
            for(int i=index2;i<top-1;i++){
                V_U[i]=V_U[i+1];
            }
            top--;//从剩余顶点集合中取出顶点后删除该顶点
        }
        int min_weight,min;
        index1=0,index2=v;//从各个集合的第一个顶点开始寻找最短的边
        min_weight=get_weight(g,U[index1],V_U[index2]);
        for(int j=0;j<=u;j++){
            for(int k=v;k<top;k++){
                min = get_weight(g,U[j],V_U[k]);
                if(min<=min_weight){
                    index1 = j,index2 = k;
                    min_weight = min;
                }
            }
        }//结束时找到了最小的边
        u++;
        TE[te].v1=U[index1];
        TE[te].v2=V_U[index2];
        TE[te++].weight=min_weight;
    }//结束时所有的边找到

    for(int i=0;i<te;i++){
        printf("\t%c----%d----%c\n",TE[i].v1,TE[i].weight,TE[i].v2);
    }
    int sum=0;
    for(int i=0;i<te;i++){
        sum = sum +TE[i].weight;
    }
    return sum;
}

(四)最小生成树(Kruskal):

设计思路:
先设计一个顶点标号数组Vset[ Max_Size];存储顶点的标号.
选出的符合条件的边的集合TE.
阅读中顶点的既定顺序对数组进行初始化
Vset A(0) B(1) C(2) D(3) E(4) F(5)
值 0 1 2 3 4 5
定义一个边的结构体数组E[Max_Size ],用于存取边的信息;然后将该数组的权值按升序排列
起点——终点——权值
0——2——1
3——5——2
1——4——3
2——5——4
0——3——5
1——2——5
2——3——5
0——1——6
2——4——6
4——5——6

//定义一个Kruskal边
typedef struct K_Edge{
    int start;
    int end;
    int weight;
}K_Edge;

Vset A(0) B(1) C(2) D(3) E(4) F(5)
值 0 1 2 3 4 5
值 0 1 0 3 4 5
值 0 1 0 3 4 3
值 0 1 0 3 1 3
值 0 1 0 0 1 0
值 0 0 0 0 0 0
然后从E中,选取权值最小的边,判断元素的起点和终点的值对应的Vset的数组中的值是否相等,如果不相等则说明,该条边可以选取; 如果相等则说明加入后会形成回环,该条边不可选取. 刚选出的满足条件的边存入TE数组中,并将Vset数组中,起点和终点对应的值变更为起点的值.进行图中顶点数n-1次循环,找出符合条件的n-1条边,然后用这些边将顶点连接起来,形成最小生成树.
算法实现C语言代码:

//Kruskal算法
int Kruskal(Graph*g,Edge TE[]){
    int vset[g->numbers];//标号辅助数组
    K_Edge E[Max_Size];//Kruskal边
    int k=0;//E数组的下标从0开始
    for(int i=0;i<g->numbers;i++){//记录由图产生的边集合
        for(int j=i+1;j<g->numbers;j++){
            if(g->adjMax[i][j]!=0&&g->adjMax[i][j]!=NoEdge){
                E[k].start=i;E[k].end=j;E[k].weight=g->adjMax[i][j];
                k++;
            }
        }
    }
    //边集合数组代价升序排序
    for(int j=k-1;j>0;j--){
        for (int i = 0; i < j; i++){
            if(E[i].weight>E[i+1].weight){
                int tmp;
                tmp=E[i].weight;
                E[i].weight=E[i+1].weight;
                E[i+1].weight=tmp;

                tmp=E[i].start;
                E[i].start=E[i+1].start;
                E[i+1].start=tmp;

                tmp=E[i].end;
                E[i].end=E[i+1].end;
                E[i+1].end=tmp;
            }
        }
    }
    for(int i=0;i<k;i++){
        printf("\n\t%d===%d====%d",E[i].start,E[i].end,E[i].weight);
    }
    printf("\n\n");

    for(int i=0;i<g->numbers;i++) vset[i]=i;
    k=1;//k表示当前构成数的第几条边,初值为1
    int j=0;int u1,v1,sn1,sn2;int te=0;
    while(k<g->numbers){//生成边数小于 g->numbers 时进入循环
        u1 = E[j].start;v1 = E[j].end;
        sn1 = vset[u1];sn2 = vset[v1];//分别得到两个顶点所属的集合编号
        if(sn1!=sn2){
            TE[te].v1=g->vex[u1].name;
            TE[te].v2=g->vex[v1].name;
            TE[te++].weight=E[j].weight;//复制信息
            k++;
            for(int i=0;i<g->numbers;i++){
                if(vset[i]==sn2) vset[i]=sn1;//标记同化
            }
        }
        j++;
    }
    for(int i=0;i<te;i++){
        printf("\t%c----%d----%c\n",TE[i].v1,TE[i].weight,TE[i].v2);
    }
    int sum=0;
    for(int i=0;i<te;i++){
        sum = sum +TE[i].weight;
    }
    return sum;
}

输入输出结果

在这里插入图片描述在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_45744501/article/details/102680655