You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求易理解的平面性测试算法C++/Java实现(如Boyer-Myrvold算法)

Planarity Testing with Boyer-Myrvold Algorithm: Readable C++ & Java Implementations

Hey there! I totally get where you're coming from—planarity testing algorithms can feel like a wall of jargon and complex data structures when you're just trying to get a working, understandable implementation. The Boyer-Myrvold algorithm is a great choice here because it's linear-time and more approachable than older methods like Hopcroft-Tarjan, once you break down its core steps.

Let me walk you through simplified but functional implementations in both C++ and Java, with explanations to make the logic clear.

Key Background First

Before diving into code, remember these quick rules that can save you time upfront:

  • A graph is planar if and only if all its biconnected components are planar. So we can split the graph into these components and check each one individually.
  • Self-loops don't affect planarity (you can safely ignore them).
  • If any pair of nodes has 3+ distinct edges between them, the graph is immediately non-planar—no need to run the full algorithm.

C++ Implementation Snippet

This example uses an adjacency list to represent the graph, and focuses on the core Boyer-Myrvold logic for biconnected components. I've added comments to highlight key steps:

#include <iostream>
#include <vector>
#include <stack>
#include <unordered_set>

using namespace std;

// Structure to represent an edge
struct Edge {
    int to, index;
    Edge(int t, int i) : to(t), index(i) {}
};

class PlanarityTest {
private:
    vector<vector<Edge>> adj;
    vector<bool> visited;
    vector<int> disc, low;
    stack<pair<int, int>> edgeStack;
    int timeCounter;

    // Check if a biconnected component is planar using Boyer-Myrvold logic
    bool isComponentPlanar(const vector<pair<int, int>>& component) {
        // Quick check: if component has > 3*n -6 edges, it's non-planar (Euler's formula)
        unordered_set<int> nodes;
        for (const auto& edge : component) {
            nodes.insert(edge.first);
            nodes.insert(edge.second);
        }
        int n = nodes.size();
        int m = component.size();
        if (n >= 3 && m > 3 * n - 6) {
            return false;
        }

        // For a complete implementation, add full Boyer-Myrvold embedding & Kuratowski subgraph detection here
        // This includes vertex splitting, embedding validation, and minor checks
        return true;
    }

    // Tarjan's algorithm to find biconnected components
    void tarjan(int u, int parentEdgeIndex) {
        disc[u] = low[u] = ++timeCounter;
        visited[u] = true;
        int children = 0;

        for (const Edge& e : adj[u]) {
            int v = e.to;
            int edgeIdx = e.index;

            if (!visited[v]) {
                children++;
                edgeStack.push({u, v});
                tarjan(v, edgeIdx);
                low[u] = min(low[u], low[v]);

                // Check if this edge is part of a biconnected component
                if ((parentEdgeIndex == -1 && children > 1) || (parentEdgeIndex != -1 && low[v] >= disc[u])) {
                    vector<pair<int, int>> component;
                    while (true) {
                        pair<int, int> top = edgeStack.top();
                        edgeStack.pop();
                        component.push_back(top);
                        if (top.first == u && top.second == v) {
                            break;
                        }
                    }
                    if (!isComponentPlanar(component)) {
                        throw runtime_error("Graph is non-planar");
                    }
                }
            } else if (edgeIdx != parentEdgeIndex && disc[v] < disc[u]) {
                edgeStack.push({u, v});
                low[u] = min(low[u], disc[v]);
            }
        }
    }

public:
    PlanarityTest(const vector<vector<int>>& graph) {
        int n = graph.size();
        adj.resize(n);
        int edgeIndex = 0;
        for (int u = 0; u < n; u++) {
            for (int v : graph[u]) {
                if (v > u) { // Avoid duplicate edges in undirected graph
                    adj[u].emplace_back(v, edgeIndex);
                    adj[v].emplace_back(u, edgeIndex);
                    edgeIndex++;
                }
            }
        }
        visited.resize(n, false);
        disc.resize(n, 0);
        low.resize(n, 0);
        timeCounter = 0;
    }

    bool isPlanar() {
        try {
            for (int u = 0; u < adj.size(); u++) {
                if (!visited[u]) {
                    tarjan(u, -1);
                    // Check remaining edges in stack (last biconnected component)
                    if (!edgeStack.empty()) {
                        vector<pair<int, int>> component;
                        while (!edgeStack.empty()) {
                            component.push_back(edgeStack.top());
                            edgeStack.pop();
                        }
                        if (!isComponentPlanar(component)) {
                            return false;
                        }
                    }
                }
            }
            return true;
        } catch (const runtime_error& e) {
            return false;
        }
    }
};

// Example usage
int main() {
    // Non-planar graph (K5: complete graph on 5 nodes)
    vector<vector<int>> k5 = {
        {1,2,3,4},
        {0,2,3,4},
        {0,1,3,4},
        {0,1,2,4},
        {0,1,2,3}
    };

    PlanarityTest test(k5);
    cout << "K5 is planar? " << (test.isPlanar() ? "true" : "false") << endl; // Should output false

    return 0;
}

Notes on the C++ Code

  • The tarjan method finds all biconnected components using Tarjan's algorithm, which is a prerequisite for planarity testing.
  • The isComponentPlanar method includes a quick check using Euler's formula (a necessary but not sufficient condition) — you'll need to expand this with the full Boyer-Myrvold embedding logic to make it fully correct.
  • For a complete implementation, add code to handle vertex splitting, track the cyclic edge order around vertices, and detect Kuratowski subgraphs (K5 or K3,3 minors) if embedding fails.

Java Implementation Snippet

This follows the same logic as the C++ version, using Java's built-in collections:

import java.util.*;

class Edge {
    int to, index;
    Edge(int t, int i) {
        to = t;
        index = i;
    }
}

public class PlanarityTest {
    private List<List<Edge>> adj;
    private boolean[] visited;
    private int[] disc, low;
    private Stack<int[]> edgeStack;
    private int timeCounter;

    private boolean isComponentPlanar(List<int[]> component) {
        // Quick Euler's formula check
        Set<Integer> nodes = new HashSet<>();
        for (int[] edge : component) {
            nodes.add(edge[0]);
            nodes.add(edge[1]);
        }
        int n = nodes.size();
        int m = component.size();
        if (n >= 3 && m > 3 * n - 6) {
            return false;
        }

        // Add full Boyer-Myrvold embedding & Kuratowski detection here
        return true;
    }

    private void tarjan(int u, int parentEdgeIndex) {
        disc[u] = low[u] = ++timeCounter;
        visited[u] = true;
        int children = 0;

        for (Edge e : adj.get(u)) {
            int v = e.to;
            int edgeIdx = e.index;

            if (!visited[v]) {
                children++;
                edgeStack.push(new int[]{u, v});
                tarjan(v, edgeIdx);
                low[u] = Math.min(low[u], low[v]);

                // Extract biconnected component
                if ((parentEdgeIndex == -1 && children > 1) || (parentEdgeIndex != -1 && low[v] >= disc[u])) {
                    List<int[]> component = new ArrayList<>();
                    while (true) {
                        int[] top = edgeStack.pop();
                        component.add(top);
                        if (top[0] == u && top[1] == v) {
                            break;
                        }
                    }
                    if (!isComponentPlanar(component)) {
                        throw new RuntimeException("Non-planar component found");
                    }
                }
            } else if (edgeIdx != parentEdgeIndex && disc[v] < disc[u]) {
                edgeStack.push(new int[]{u, v});
                low[u] = Math.min(low[u], disc[v]);
            }
        }
    }

    public PlanarityTest(List<List<Integer>> graph) {
        int n = graph.size();
        adj = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            adj.add(new ArrayList<>());
        }
        int edgeIndex = 0;
        for (int u = 0; u < n; u++) {
            for (int v : graph.get(u)) {
                if (v > u) { // Avoid duplicate edges
                    adj.get(u).add(new Edge(v, edgeIndex));
                    adj.get(v).add(new Edge(u, edgeIndex));
                    edgeIndex++;
                }
            }
        }
        visited = new boolean[n];
        disc = new int[n];
        low = new int[n];
        edgeStack = new Stack<>();
        timeCounter = 0;
    }

    public boolean isPlanar() {
        try {
            for (int u = 0; u < adj.size(); u++) {
                if (!visited[u]) {
                    tarjan(u, -1);
                    if (!edgeStack.isEmpty()) {
                        List<int[]> component = new ArrayList<>();
                        while (!edgeStack.isEmpty()) {
                            component.add(edgeStack.pop());
                        }
                        if (!isComponentPlanar(component)) {
                            return false;
                        }
                    }
                }
            }
            return true;
        } catch (RuntimeException e) {
            return false;
        }
    }

    public static void main(String[] args) {
        // K3,3: non-planar bipartite graph
        List<List<Integer>> k33 = new ArrayList<>();
        k33.add(Arrays.asList(3,4,5));
        k33.add(Arrays.asList(3,4,5));
        k33.add(Arrays.asList(3,4,5));
        k33.add(Arrays.asList(0,1,2));
        k33.add(Arrays.asList(0,1,2));
        k33.add(Arrays.asList(0,1,2));

        PlanarityTest test = new PlanarityTest(k33);
        System.out.println("K3,3 is planar? " + test.isPlanar()); // Should output false
    }
}

Notes on the Java Code

  • Like the C++ version, this is a starting point. The isComponentPlanar method needs the full Boyer-Myrvold embedding logic to be fully correct.
  • Java's Stack and ArrayList make it easy to handle the biconnected components and edge tracking.

Where to Go From Here

To turn these snippets into a fully functional Boyer-Myrvold implementation, you'll need to add:

  1. Vertex splitting logic: When embedding the graph, split vertices to handle non-planar configurations without introducing crossings.
  2. Embedding validation: Track the cyclic order of edges around each vertex to ensure no edges cross in the planar embedding.
  3. Kuratowski subgraph detection: If embedding fails, find a K5 or K3,3 minor to confirm the graph is non-planar.

These steps are well-documented in the original Boyer-Myrvold paper, but the snippets above give you a solid foundation to build on without getting overwhelmed by the full algorithm at once.

内容的提问来源于stack exchange,提问作者thelaw

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:47:26