算法 Bellman-Ford及其队列优化

屏幕快照 2018-04-27 下午4.08.22.png

/*
Bellman-Ford 思想:对每一条边进行松弛操作(dis[v[i]]>dis[u[i]]+w[i])。共进行(n-1)轮,每进行一轮松弛操作,相当于扩充了一些新的边
当进行完k轮时,就已经找到从源点发出“最多经过k条边”到达各个顶点的最短路。n个顶点,最短路径上最多有n-1条边
如果进行n-1轮,还可以对边进行松弛成功,则此图一定存在负权回路~

负权回路:即回路权值之和为负

以此题而言:第一轮松弛操作后,dis[3]=inf(无穷大),但第二轮后dis[3] =-1; 是因为在第一轮后dis[2]=2;
dis[3] = dis[2]+2->3; 即dis[3] = dis[1]+1->2 + 2—>3;所以每一轮都相当于扩充了一些新边。
当进行完k轮时,就已经找到从源点发出“最多经过k条边”到达各个顶点的最短路

*/

#pragma mark - - Bellman-Ford 算法
-(void)test1 {
    
    // 存储边数据 例如:{2,3,2} 表示顶点2到顶点3的权值为2
    // e[0][3] 没有任何意义,只是为了让边的遍历从1开始
    int e[6][3] = {{0,0,0},{2,3,2},{1,2,-3},{1,5,5},{4,5,2},{3,4,3}};
    
    // u[6] v[5] w[5] 分别存储两个顶点和权值
    int u[6];
    int v[6];
    int w[6];
    for (int i=1; i<=5; i++) {
        u[i] = e[i][0];
        v[i] = e[i][1];
        w[i] = e[i][2];
    }
    
    int n=5;  // 顶点数
    int m=5; // 边数
    int inf=9999; // 表示∞
    // dis[6] 存储源点到其他顶点的权值
    int dis[6];
    for (int i=0; i<=n; i++) {
        dis[i] =inf;
    }
    
    // 设置源点为顶点1
    dis[1] =0;
    
    
    // 共进行n-1轮
    for (int k=1; k<=n-1; k++) {
        // 对边进行松弛操作
        for (int i=1; i<=m; i++) {
            if (dis[v[i]] > dis[u[i]] + w[i]) {
                dis[v[i]] = dis[u[i]] +w[i];
            }
        }
    }
    
    // 判断是否负权回路
    int flag=0;
    for (int i=1; i<=m; i++) {
        if (dis[v[i]] > dis[u[i]] + w[i]) {
            flag=1;
        }
    }
   
    if (flag==1) {
        printf("该图存在回路\n");
    }else {
        // 打印数据
        for (int i=1; i<=n; i++) {
            printf("%4d",dis[i]);
        }
        printf("\n");
    }
    
}

// 对上述算法进行优化
/*
在实际操作中,有时只进行了K轮(k<n-1),dis[]数组就不再发生变化,所以我们可以根据dis[]的变化来提前
退出循环

*/

-(void)test2 {
    // 存储边数据 例如:{2,3,2} 表示顶点2到顶点3的权值为2
    // e[0][3] 没有任何意义,只是为了让边的遍历从1开始
    int e[6][3] = {{0,0,0},{2,3,2},{1,2,-3},{1,5,5},{4,5,2},{3,4,3}};
    int n=5; // 顶点数
    int m=5; // 边数
    // u[] v[] w[] 分别存储顶点和权值
    int u[6];
    int v[6];
    int w[6];
    for (int i=1; i<=m; i++) {
        u[i] =e[i][0];
        v[i] =e[i][1];
        w[i] =e[i][2];
    }
    
    int dis[6]; // 存储源点到各个顶点的最短距离
    int inf=9999; // 表示无穷大
    
    for (int i=0; i<=n; i++) {
        dis[i] = inf;
    }
    
    // 设置源点为顶点1
    dis[1] =0;
    
    // 进行n-1轮
    for (int k=1; k<=n-1; k++) {
        // 检查是否松弛成功
        int check =0;
        // 对每一条边进行松弛操作
        for (int i=1; i<=m; i++) {
            if (dis[v[i]] > dis[u[i]] + w[i]) {
                dis[v[i]] = dis[u[i]] + w[i];
                check =1;
            }
        }
        
        if (check==0) {
            // check=0 表示当前这一轮 dis[] 没有变化,那么再进行一轮也不会有变化
            // 提前退出循环
            break ;
        }
    }
    
    // 判断是否存在负权回路
    int flag=0;
    for (int i=1; i<=m; i++) {
        if (dis[v[i]] > dis[u[i]] + w[i]) {
            flag =1;
        }
    }
    
    if (flag==1) {
        printf("该图存在回路\n");
    }else {
        // 打印数据
        for (int i=1; i<=n; i++) {
            printf("%4d",dis[i]);
        }
        printf("\n");
    }
    
    
}

/*
当对边进行松弛操作后,某些顶点的最短距离dis[k]已经固定下来,在后面的对边的松弛操作中,这个dis[k]
不会发生变化。如果dis[u[i]] 固定下来了,那么对顶点u[i]的所有边做松弛操作都不会有效果(dis[v[i]] = dis[u[i]] + w[i])。所以仅对最短路程发生变化的顶点的相邻边进行松弛操作~
/
/

Bellman—Ford的队列优化:每次仅对最短路程发生变化的点的相邻边进行松弛操作。即对一个顶点的所有边进行
松弛操作,如果dis[v[i]] > dis[u[i]] + w[i],则将dis[v[i]] 加入队列中,循环队列(head++)~

*/

#pragma mark - - Bellman-Ford的队列优化

-(void)test3 {
    // 存储边数据 例如:{2,3,2} 表示顶点2到顶点3的权值为2
    // e[0][3] 没有任何意义,只是为了让边的遍历从1开始
    int e[6][3] = {{0,0,0},{2,3,2},{1,2,-3},{1,5,5},{4,5,2},{3,4,3}};
    int n=5;
    int m=5;
    // 邻接表
    // first[i] 存储顶点i的第一条边的编号
    int first[6];
    // next[i] 存储边i的下一条边的编号
    int next[6];
    
    // 初始化first[]
    for (int i=0; i<=m; i++) {
        first[i] = -1;
    }
    
    // u[] v[] w[] 分别存储顶点和权值
    int u[6];
    int v[6];
    int w[6];
    // 邻接表 赋值
    for (int i=1; i<=m; i++) {
        u[i] = e[i][0];
        v[i] = e[i][1];
        w[i] = e[i][2];
        
        // 邻接表 核心代码
        next[i] =first[u[i]];
        first[u[i]] = i;
    }
    
    // 初始化 dis[]
    int dis[6];
    int inf = 9999;
    for (int i=0; i<=5; i++) {
        dis[i] = inf;
    }
    
    // 初始化 队列
    int que[10];
    int head=1;
    int tail=1;
    // 初始化标记数组(标记队列里的点)
    int book[10]= {0};
    
    // 源点1赋值
    // 赋值dis[]
    dis[1]=0;
    // 入队
    que[tail] = 1;
    tail ++;
    // 标记
    book[1] =1;
    
    
    // 核心代码
    // 循环队列
    // 如果存在负权回路,队列会一直执行
    int sum=0; // 记录松弛操作次数
    int flag=0; // 标记是否存在负权回路
    while (head<tail) {
        // 获取队首顶点t
        int t =que[head];
        // 获取顶点t的第一条边
        int k = first[t];
        // 获取顶点t的所有出边
        while (k!=-1) {
            sum++;
            // 对边进行松弛
            if (dis[v[k]] > dis[u[k]] + w[k]) {
                // 松弛成功
                dis[v[k]] = dis[u[k]] + w[k];
                // book[] 数组用来判断当前点是否在队列里
                if (book[v[k]] == 0) {
                    // 标记
                    book[v[k]] = 1;
                    // 入队
                    que[tail] = v[k];
                    tail++;
                }
               
            }
            // 下一条边
            k = next[k];
            
        }
        
        // 出队
        /*
         当一个顶点的最短路程变短后,需要对这个顶点的所有边进行松弛操作,但是如果这个顶点的最短路程
         再次变小,需要再次对这个顶点的所有边进行松弛操作~
         */
        book[que[head]] = 0; //把出队的点重新标记为0
        head ++ ; // head++ 才能继续执行队列里的其他点
        
        // 当图不存在负权回路时,最多会进行n*m次松弛操作
        // 如果sum(松弛操作次数)>n*m+1 ,那一定存在负权回路
        // 为什么是n*m,Bellman-Ford 算法最多执行(n-1)*m次就可以使源点到各个顶点的距离达到最小值
        if (sum>n*m+1) {
            flag =1;
            break ;
        }
        
    }
    
    
    // 打印
    if (flag==1) {
        printf("该图存在回路\n");
    }else {
        // 打印数据
        for (int i=1; i<=n; i++) {
            printf("%4d",dis[i]);
        }
        printf("\n");
    }
    
    
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容