普利姆算法、克鲁斯卡尔算法、迪杰斯特拉算法、弗洛伊德算法C语言实现

首先我们需要一个图的数据结构

#include "stdio.h"
#include "stdlib.h"
#define MAX 32767


typedef struct Graph{
    char* vexs;
    int** arcs;
    int vexNum;
    int arcNum;
}Graph;

为了方便后续算法的使用,我们用MAX来表示两个节点之间没有连接

Graph* initGraph(int vexNum){
    Graph* g = (Graph*)malloc(sizeof(Graph));
    g->vexs = (char*)malloc(sizeof(char)*vexNum);
    g->arcs = (int**)malloc(sizeof(int*)*vexNum);
    for (int i=0;i<vexNum;i++){
        g->arcs[i] = (int*)malloc(sizeof(int*)*vexNum);
    }
    g->vexNum = vexNum;
    g->arcNum = 0;
    return g;
}

void createGraph(Graph* g,char* vexs,int* arcs){
    for (int i=0;i<g->vexNum;i++){
        g->vexs[i] = vexs[i];
        for (int j=0;j<g->vexNum;j++){
            g->arcs[i][j] = *(arcs+i*g->vexNum+j);
            if (g->arcs[i][j]!=0&&g->arcs[i][j]!=MAX){
                g->arcNum++;
            }
        }
    }
    g->arcNum/=2;
}

这是初始化图和创建图函数,这样我们可以使用下面的方式来创建图

int main(){
    Graph* g = initGraph(4);
    int arr[16] = {
        0,4,6,9,
        4,0,MAX,MAX,
        6,MAX,0,2,
        9,MAX,2,0
    };
    createGraph(g,"ABCD",(int*)arr)
}

 普利姆算法和克鲁斯卡尔算法

普利姆算法和克鲁斯卡尔算法是获取图的最小生成树的算法,两者都用到了MST性质,图的MST性质是:考虑图G的点集V的非空子集X,如果点u属于X,点v属于V\X(集合V对集合X的差集),且边(u,v)是权值最小的边,则边(u,v)是图G的最小生成树的一条边

typedef struct Edge{
    char vex; // 记录U集合中最小边的起始节点
    int weight; // 记录最小边的权值
}Edge;

Edge* initEdge(Graph* g,int index){
    // 初始化edge结构体,将edge数组都赋值为开始节点的字符和边的权重
    Edge* edge = (Edge*)malloc(sizeof(Edge)*g->vexNum);
    for (int i=0;i<g->vexNum;i++){
        edge[i].vex = g->vexs[index];
        edge[i].weight = g->arcs[index][i];

    }
    return edge;
}

int getminEdge(Edge* edge,Graph* g){
    // 找当前edge数组中权值最小的数据点对应的索引  edge数组的索引对应于图中节点的索引
    int index,min=MAX;
    for (int i=0;i<g->vexNum;i++){
        if (edge[i].weight!=0&&min>edge[i].weight){
            min = edge[i].weight;
            index = i;
        }
    }
    return index;
}

void prim(Graph* g,int index){
    // prim算法输出最小生成树
    int minIndex;
    Edge* edge = initEdge(g,index);
    for (int i=0;i<g->vexNum-1;i++){
        minIndex = getminEdge(edge,g);
        // 可以在这一行修改操作,进而导出最小生成树
        printf("v%c->v%c ,weight = %d\n",edge[minIndex].vex,g->vexs[minIndex],edge[minIndex].weight);
        // 更新edge数组
        edge[minIndex].weight = 0; // 当前所在的节点权值归零(标记之后不再考虑)
        for (int j=0;j<g->vexNum;j++){
            if (g->arcs[minIndex][j]<edge[j].weight){
                edge[j].weight = g->arcs[minIndex][j];
                edge[j].vex = g->vexs[minIndex];
            }
        // edge数组当中最终保留的是到j节点最短路径的起始节点(所以如果比之前还要大,就不更改)
        }
    }
}

首先是普利姆算法,普利姆算法可以理解为找点法:从起点开始,依次寻找权值最小的边,并且将这条边的终点放入集合X,然后将这条边的终点作为下次找点的起点。当集合X等于点集V的时候,就得到了以某个点为起点的最小生成树。

typedef struct Line{ // 记录边的结构体
    int start;
    int end;
    int weight;
}Line;

Line* initLine(Graph* g){ // 读取一下图的每一条边
    int index = 0;
    Line* line = (Line*)malloc(sizeof(Line)*g->arcNum);
    for (int i=0;i<g->vexNum;i++){
        for (int j=i+1;j<g->vexNum;j++){
            if (g->arcs[i][j]!=MAX){
                line[index].start = i;
                line[index].end = j;
                line[index].weight = g->arcs[i][j];
                index++;
            }
        }
    }
    return line;
}

void sortLine(Line* line,Graph* g){ // 对边排序
    Line tem;
    for (int i=0;i<g->arcNum-1;i++){
        for (int j=0;j<g->arcNum-1-i;j++){
            if (line[j].weight>line[j+1].weight){
                tem = line[j];
                line[j] = line[j+1];
                line[j+1] = tem;
            }
        }
    }
}

void krustkal(Graph* g){
    int* connected = (int*)malloc(sizeof(int)*g->vexNum);
    for (int i=0;i<g->vexNum;i++){
        connected[i] = i;
    }
    Line* line = initLine(g);
    sortLine(line,g);
    for (int j=0;j<g->arcNum;j++){
        int start = connected[line[j].start];
        int end = connected[line[j].end];
        if (start!=end){
            printf("v%c-->v%c  weight = %d\n",g->vexs[line[j].start],g->vexs[line[j].end],line[j].weight);
            // 这个循环只是一个连通分量的校正
            for (int k=0;k<g->vexNum;k++){
                if (connected[k]==end){
                    connected[k] = start; 
                }
            }
        }
    }
}

克鲁斯卡尔算法可以理解为找边法:将边的数据记录下来,然后按照权重排序,再依次联通最小的边,当生成树连接到每个点时,我们得到了最小生成树。为了方便编写,我们这里使用冒泡排序。

普利姆算法和克鲁斯卡尔算法在广义上是一种贪心算法,但是可以用数学证明,这种贪心算法是可以得到图的最小生成树的。

迪杰斯特拉算法和弗洛伊德算法

迪杰斯特拉算法和弗洛伊德算法是获取单源最小路径。

int getMin(int* distance,int* status,Graph* g){
    int res = MAX;
    int index;
    for (int i=0;i<g->vexNum;i++){
        if (!status[i]&&distance[i]<res){
            res = distance[i];
            index = i;
        }
    }
    return index;
}

// dijkstra算法获取单源最小路径
void dijkstra(Graph* g,int index){  
    int* status = (int*)malloc(sizeof(int)*g->vexNum); // 检查当前节点最短路径是否达到
    int* pre = (int*)malloc(sizeof(int)*g->vexNum); // 存储到达当前节点的最短路径的前驱节点
    int* distance = (int*)malloc(sizeof(int)*g->vexNum); // 计算路径长度
    for (int i=0;i<g->vexNum;i++){
        status[i] = i==index;
        if (g->arcs[index][i]>0&&g->arcs[index][i]!=MAX){
            pre[i] = index;
        }
        else {
            pre[i] = -1;
        }
        distance[i] = g->arcs[index][i];
        
    }
    for (int i=0;i<g->vexNum;i++){
        int index = getMin(distance,status,g);
        status[index] = 1;
        for (int j=0;j<g->vexNum;j++){
            if (!status[j]&&distance[index]+g->arcs[index][j]<distance[j]){
                distance[j] = distance[index] + g->arcs[index][j];
                pre[j] = index;
            }
        }
    }
    for (int k=0;k<g->vexNum;k++){
        printf("status = %d pre = %d distance = %d\n",status[k],pre[k],distance[k]);
    }
}

迪杰斯特拉算法更像是从结尾出发,倒推出最小路径,见注释使用三个数组,如果循环过程中的新路径比当前获取的新路径更短,则进行替换;如果当前节点的最短路径未获取,则我们将它作为最小路径。

void floyd(Graph* g){
    // 又叫 3 for 算法
    int distance[g->vexNum][g->vexNum];
    int pre[g->vexNum][g->vexNum];
    // 初始化distance数组和pre数组
    for (int i=0;i<g->vexNum;i++){
        for (int j=0;j<g->vexNum;j++){
            distance[i][j] = g->arcs[i][j];
            if (g->arcs[i][j]>=0&&g->arcs[i][j]!=MAX){
                pre[i][j] = i;
            }
            else {
                pre[i][j] = -1;
            }
        }
    }
    for (int i=0;i<g->vexNum;i++){
        for (int j=0;j<g->vexNum;j++){
            for (int k=0;k<g->vexNum;k++){
                if (distance[j][k]>distance[j][i]+distance[i][k]){
                    distance[j][k] = distance[j][i]+distance[i][k];
                    pre[j][k] = pre[i][k];
                }
            }
        }
    }
    for (int i=0;i<g->vexNum;i++){
        for (int j=0;j<g->vexNum;j++){
            printf("v%c -- v%c distance = %d\n",g->vexs[i],g->vexs[j],distance[i][j]);
        }
    }
}

弗洛伊德算法又叫3for算法,因为使用3个for循环就可以解决。我们依次将每个节点作为插入节点,并且使用一个pre数组来记录当前路径的倒数第二个节点,用distance数组来记录最短路径。如果新的路径比当前已知的最短路径短,则更新distance数组和pre数组,否则保留。这种算法考虑了每个节点之间的最短路径,有暴力算法之称。

回顾往期:栈,队列,二叉树的实现和基本方法

                  链表的python实现

                  

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值