js 实现无向图获取所有的最小闭环

思路基于此文档

export function extract_cycles(vertices, edges) {
    let cycles = [];
    while (vertices.length > 0) {
        let v = leftBottomVertex(vertices),
            walk = reduceWalk(closedWalkFrom(v));
        if (walk.length > 2) cycles.push(walk);
        removeEdge(walk[0], walk[1]);
        vertices = removeFilamentAt(walk[0], vertices);
        vertices = removeFilamentAt(walk[1], vertices);
    };
    return cycles;
};

function leftBottomVertex(vertices) {
    return vertices.reduce( (m,v) => {
        let dx = v.x - m.x;
        if (dx < 0) return v;
        if (dx > 0) return m;
        return v.y - m.y < 0 ? m : v;
    } );
}

function closedWalkFrom(v) {
    let walk = [], curr = v, prev;
    do {
        walk.push(curr);
        [curr, prev] = getNext(curr, prev);
    } while (curr !== v);
    return walk;
}

function reduceWalk(w) {
    for (let i=1; i < w.length; i++) {
        let idup = w.lastIndexOf(w[i]);
        if (idup > i) w.splice(i+1, idup - i);
    }
    return w;
}

function withoutVertex(v, vertices) {
    return vertices.filter(vi => vi !== v);
}

function removeEdge(v1, v2) {
    v1.adj = withoutVertex(v2, v1.adj);
    v2.adj = withoutVertex(v1, v2.adj);
}

function removeFilamentAt(v, vertices) {
    let current = v, next;
    while (current && current.adj.length < 2) {
        vertices = withoutVertex(current, vertices);        
        next = current.adj[0];
        if (next) removeEdge(current, next);
        current = next;
    }
    return vertices;
}

function getNext(v, prev) {
    let next = (v.adj.length == 1) ? v.adj[0] : best_by_kind(prev, v, prev ? 'ccw' : 'cw');
    return [next, v];
}

function best_by_kind(v_prev, v_curr, kind) {
    let d_curr, adj = v_curr.adj;
    if (v_prev) {
        d_curr = vsub(v_curr, v_prev);
        adj = withoutVertex(v_prev, adj);
    } else {
        d_curr = {x: 0, y: -1};
    }
    
    return adj.reduce((v_so_far, v) => better_by_kind(v, v_so_far, v_curr, d_curr, kind));
};

function better_by_kind(v, v_so_far, v_curr, d_curr, kind) {
    let d = vsub(v, v_curr),
        d_so_far = vsub(v_so_far, v_curr),
        is_convex = dot_perp(d_so_far, d_curr) > 0,
        curr2v = dot_perp(d_curr, d),
        vsf2v = dot_perp(d_so_far, d),
        v_is_better;
    if (kind == 'cw') {
        v_is_better = (is_convex && (curr2v >= 0 || vsf2v >= 0)) || (!is_convex && curr2v >= 0 && vsf2v >= 0);
    } else {
        v_is_better = (!is_convex && (curr2v < 0 || vsf2v < 0)) || (is_convex && curr2v < 0 && vsf2v < 0);
    }
    return v_is_better ? v : v_so_far;
};

function vsub(a, b) {
    return {x: a.x - b.x, y: a.y - b.y};
}

function dot_perp(a, b) {
    return a.x * b.y - b.x * a.y;
}

调用测试

vertices = [{x: 0, y: 0, adj: []}, {x: 5, y: 5, adj: []}, {x: 5, y: 0, adj: []}];

// edges just define adjacent vertices for each vertex
[ [0,1], [1,2], [2,0] ].forEach( (i1,i2) => {
    let v1 = vertices[i1], v2 = vertices[i2];
    v1.adj.push(v2);
    v2.adj.push(v1);
});

let result = extract_cycles(vertices);

# cycles => [];
# filaments => [ [[1,2], [5,6]] ];
# result => [{x: 0, y: 0, ...}, {x: 5, y: 5, ...}, {x: 5, y: 0, ...}]

最终实现需求效果

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值