【C++/管梅谷破圈算法】用邻接表实现破圈算法

#include <iostream>
#include <tuple>
#include <algorithm>
#include <limits>
#include <queue>
#include <vector>

#include <cstdio>
#include <cstdlib>

using std::vector;

struct Graph_link_node{
    double  val;
    int     next;
    int     to;
};

struct Edge{
    double val;
    int from, to;
public:
    Edge(double val, int from, int to){
        this->val = val;
        this->from= from;
        this->to  = to;
    }


};

class Graph{
private:
    int                             verticle;
    int                             edge;
    vector<info::Graph_link_node>   nodes;
    int                             count;
    vector<int>                     heads;
private:
    // used before initialization
    void set_count_zero(){ count = 1; }

    void add_node(int from, int to, double val){
        nodes[count].next = heads[from];
        nodes[count].to   = to;
        nodes[count].val  = val;
        heads[from]       = count;
        count++;
    }
public:
    Graph(FILE* input){
        fscanf(input, "%d%d", &verticle, &edge);
        heads.resize(verticle+10, -1);
        nodes.resize(2*edge+10);

        set_count_zero();
        for (int i=0; i<edge; i++){
            int from, to;
            double val;
            fscanf(input, " %d%d%lf", &from, &to, &val);
            add_node(from, to, val);
            add_node(to, from, val);
        }
    }

    void print(){
        putchar('\n');

        for (int from=0; from<verticle; from++){
            for (int index=heads[from]; index!=-1; index=nodes[index].next){
                int to = nodes[index].to;
                double val = nodes[index].val;
                printf("%2d\t%2d\t%6.2f\n", from, to, val);
            }
        }
    }



    // 管梅谷破圈算法
    // https://blog.csdn.net/qazwsxrx/article/details/104904220#:~:text=%E7%A0%B4%E5%9C%88%E6%B3%95%20%28%E7%AE%A1%E6%A2%85%E8%B0%B7%29%20%E7%AE%A1%E6%A2%85%E8%B0%B7%EF%BC%88%EF%BC%91%EF%BC%99%EF%BC%93%EF%BC%94%EF%BC%8D%EF%BC%89%E3%80%82%20%E6%88%91%E5%9B%BD%E8%91%97%E5%90%8D%E6%95%B0%E5%AD%A6%E5%AE%B6%EF%BC%8C%E6%9B%BE%E4%BB%BB%E5%B1%B1%E4%B8%9C%E5%B8%88%E8%8C%83%E5%A4%A7%E5%AD%A6%E6%A0%A1%20%E9%95%BF%E3%80%82%20%E4%B8%AD%E5%9B%BD%E8%BF%90%E7%AD%B9%E5%AD%A6%E4%BC%9A%E7%AC%AC%E4%B8%80%E3%80%81%E4%BA%8C%E5%B1%8A%E5%B8%B8%E5%8A%A1%E7%90%86%E4%BA%8B%EF%BC%8C%E7%AC%AC%E5%85%AD%E5%B1%8A%E5%85%A8%E5%9B%BD%E6%94%BF%E5%8D%8F%E5%A7%94%E5%91%98%E3%80%82%20%E4%BB%8E%20%E4%BA%8B%E8%BF%90%E7%AD%B9%E5%AD%A6%E5%8F%8A%E5%85%B6%E5%BA%94%E7%94%A8%E7%9A%84%E7%A0%94%E7%A9%B6%EF%BC%8C%E5%AF%B9%E6%9C%80%E7%9F%AD%E6%8A%95%E9%80%92%E8%B7%AF%E7%BA%BF%E9%97%AE%E9%A2%98%E7%9A%84%E7%A0%94%E7%A9%B6%E5%8F%96%E5%BE%97%E6%88%90%E6%9E%9C,%E5%86%A0%E5%90%8D%E4%B8%BA%E4%B8%AD%E5%9B%BD%E9%82%AE%E8%B7%AF%E9%97%AE%E9%A2%98%EF%BC%8C%E8%AF%A5%E9%97%AE%E9%A2%98%E8%A2%AB%E5%88%97%E5%85%A5%E7%BB%8F%E5%85%B8%E5%9B%BE%E8%AE%BA%E6%95%99%E6%9D%90%20%E5%92%8C%E8%91%97%E4%BD%9C%E3%80%82%20%E6%80%9D%E8%B7%AF%EF%BC%9A%E4%BB%8E%E8%B5%8B%E6%9D%83%E5%9B%BEG%E7%9A%84%20%E4%BB%BB%E6%84%8F%E5%9C%88%E5%BC%80%E5%A7%8B%20%EF%BC%8C%20%E5%8E%BB%E6%8E%89%E8%AF%A5%E5%9C%88%E4%B8%AD%E6%9D%83%E5%80%BC%E6%9C%80%E5%A4%A7%E7%9A%84%E4%B8%80%E6%9D%A1%E8%BE%B9%EF%BC%8C%E7%A7%B0%E4%B8%BA%E7%A0%B4%E5%9C%88%E3%80%82%20%E4%B8%8D%E6%96%AD%E7%A0%B4%E5%9C%88%EF%BC%8C%E7%9B%B4%E5%88%B0G%E4%B8%AD%E6%B2%A1%20%E6%9C%89%E5%9C%88%E4%B8%BA%E6%AD%A2%EF%BC%8C%E6%9C%80%E5%90%8E%E5%89%A9%E4%B8%8B%E7%9A%84G%E7%9A%84%E5%AD%90%E5%9B%BE%E4%B8%BAG%E7%9A%84%E6%9C%80%E5%B0%8F%E7%94%9F%E6%88%90%E6%A0%91%E3%80%82
    // 
    void guan_meigu(){
        // 算法大致思路:先找到图内任意一个圈
        // 然后破除掉该圈中权值最大的一个边
        // 直到整个图都没有圈为止

        printf("== heads ==\n");
        for (int i=0; i<verticle; i++){
            printf("%d\t%d\n", i, heads[i]);
        }
        printf("== nodes ==\n");
        int size = nodes.size();
        for (int i=0; i<size; i++){
            auto& node = nodes[i];
            printf("%2d\t%2d\t%2d\t%f\n", i, node.to, node.next, node.val);
        }

        // 用于保存顶点访问轨迹
        // a[i][0]用于存储前一个顶点
        // a[i][1]用于存储连接轨迹中顶点i和它后一个顶点的边所在结点
        // 如果a[i][1]==-1,说明该顶点的所有与之联通的结点都已访问
        // 此时通过a[i][0]来访问其前一个顶点
        // 如果a[i][0]==-1,则访问结束
        // 若 from->to 边在 nodes 中的下标为 index
        // 则 a[from] = (/, index);
        //    a[to]   = (from,  /);
        vector<vector<int>> a(verticle, vector<int>({-1, -1}));

        // 用来保存某条边是否被裁剪过
        vector<vector<int>> 
            is_trimmed(verticle, vector<int>(verticle, 0));

        int pre   = 0,
            index = heads[0],
            cur   = nodes[index].to,
            iindex= 0;
        a[0] = {-1, index};
        a[cur]={pre, -1};
        
        printf("== run algorithm ==\n");

        do{
            // 每次循环开始,获得一个pre顶点和index边
            // 检查该边是否可访问
            if (index == -1){// 如果不可访问当前边
POP:        // 可看做从访问队列中弹出一个顶点,故命名为POP
                // 将当前顶点切换到pre顶点
                cur = pre;
                // 将pre顶点切换到cur顶点的前顶点
                pre = a[cur][0];
                if (pre == -1) break;
                // 将当前顶点的前顶点置空
                a[cur][0] = -1;
                // 将index置为和前顶点相连的下一条边
                index = a[pre][1];
                index = nodes[index].next;
                a[pre][1] = index;
                continue;
            }
            // 从当前边获取当前顶点
            cur = nodes[index].to;
            // a[cur][0] = pre;
            if (is_trimmed[pre][cur]){ // 如果当前边被裁剪
                a[pre][1] = index = nodes[index].next;
                continue;                
            }
            if (a[cur][1] == -1){ // 如果cur没有被访问过
                // 则深度优先访问
                a[cur][0] = pre;
                a[cur][1] = index = heads[cur];
                pre = cur;
                continue;
            } else { // 否则,分为两种情况
                iindex = a[cur][1];
                if (nodes[iindex].to == pre) { // 说明遇到二元环
                    index = nodes[index].next;
                    a[pre][1] = index;
                    continue;
                }
                // 保存 a[cur] 状态
                // 因为下面访问环的时候要清空整个环的访问状态
                vector<int> cur_state = a[cur];
                int ppre = pre,
                    ccur = cur,
                    iindex = index;
                    a[ppre][0] = a[ppre][1] = -1;
                double max_distance = nodes[index].val;
                int max_from = ppre, max_to = ccur;

                while (ccur != pre){
                    // 沿着环遍历,同时清除环上的数据
                    iindex = a[ccur][1];
                    a[ccur][0] = a[ccur][1] = -1;
                    ppre = ccur;
                    ccur = nodes[iindex].to;
                    double distance = nodes[iindex].val;
                    if (distance >= max_distance){
                        max_distance    = distance;
                        max_from        = ppre;
                        max_to          = ccur;
                    }
                }
                
                //剪枝
                is_trimmed[max_from][max_to] = 
                    is_trimmed[max_to][max_from] = 1;

                // 恢复 a[cur] 状态
                a[cur] = cur_state;
                pre = cur;
                index = a[cur][1];
                continue;
            }
        } while (pre != -1);
        
        printf("\n");
        printf("== output result ==\n");
        // 重新遍历图,并输出边
        for (int from = 0; from < verticle; from++){
            for (index = heads[from]; index != -1; index = nodes[index].next){
                int to = nodes[index].to;
                if (from >= to)             continue;
                if (is_trimmed[from][to])   continue;
                printf("(%2d,%2d):%f\n", from, to, nodes[index].val);
            }
        }
        printf("== finish ==\n");
        return;
    }

};


int main(){
    const char* file_name = "data_graph.txt";
    FILE* file = fopen(file_name, "r");

    if (file == nullptr){
        printf("Unable to open %s\n", file_name);
        return 0;
    }

    printf("= guan_meigu =\n");
    graph.guan_meigu();
    putchar('\n');

    fclose(file);
    return 0;
}

输入数据与其对应的图:

10 16
0  1  4
0  2  8
1  2 11
1  3  8
2  4  7
2  6  1
3  4  2
3  5  2
3  7  7
3  8  4
4  6  6
5  6  2
6  8  2
7  8 14
7  9  9
8  9 10

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值