我该如何在Java中实现二分图?

9

更新

迄今为止,有些答案建议使用邻接表。在Java中,邻接表会是什么样子呢?…没有指针,对吧 :)


我正在尝试在Java中实现二分图,以将文件中的信息分成两组。我找到了这个例子,它确实做到了:

http://users.skynet.be/alperthereal/source_files/html/java/Bipartite.java.html

但是,我想要实现我的自己版本…如果你看看我的先前的帖子,你就能理解为什么我想自己做这件事。

因此,我必须读取一个文件,从中可以轻松获得顶点数量,但边的数量却不容易。一个示例行可能是"PersonA PersonB",可以读作"PersonA说PersonB"。所以读取这些行...

"A says B"
"C says B"
"D says B"
"B says D"
"E says A & C"

...产生以下分组:

{A,D,C} and {B,E}.

我该如何实现这个二分图?有没有好的资源可以帮助我完成这个任务?在创建BipartiteGraph类时,我应该考虑哪些事情(算法)...也许是遍历/排序算法?


嗨,我相信你引用了一张图片。请重新上传一下好吗?现在它看不见了。 - mtk
5个回答

5
使用邻接表实现应该非常直观。如果它是无向二分图,我可能会建议使用关联矩阵。
因此,您需要为每个节点创建一个链接列表的数组或某种动态分配列表的数组。这样做应该很自然,例如在您的示例中,您有一条边:
Person A-> Person B
然后,您将转到对应于Person A的数组索引,并将对应于Persona B的索引推回:
[Person A] = Person B
然后,也许您会得到另一个边缘
Persona A-> Person C
然后,您的索引看起来像:
[Persona A] = Person B,Person C
作为最后一个示例,这将是您示例图的邻接表:
[A] B
[B] D
[C] B
[D] B
[E] A,C
每个索引都具有可以从该节点到达的节点列表。
“创建BipartiteGraph类时应考虑和思考哪些事物(算法)...也许是遍历/排序算法?”
这真的取决于您想要使用图形做什么...
参考:Sun论坛上带有代码的类似问题 有向加权图的邻接表

我想将它变成二分图,这样我就可以将列表分成两组 {A, D, C} 和 {B, E} 进行排序。 - Hristo
我添加了两个引用,第二个引用有点复杂,但如果你感兴趣的话可以看一下。 - JSchlather
看起来不错...我会查看这些参考资料。现在我的下一个问题是...是否可能将其转换为二分图,而不是创建邻接表,然后将其转换为图,然后对组进行排序?那似乎效率低下。这可以通过添加到邻接表中然后扫描它的过程来完成吗? - Hristo
1
将其转化为二分图,需要将其划分为两个集合。我暂时想不到一个用于生成二分图的流算法。你可以在获取新节点时将它们分配到组中,然后在获取新边时重新排列这些组。因此,如果你有A和B作为节点,那么当你得到A->B时,你知道它们不能在同一组中。问题就变成了要交换哪一个,并且是否能保证你的数据是二分图。 - JSchlather
嗯...我不知道你对这个东西了解多少,但你认为这个会起作用吗:http://www.personal.kent.edu/~rmuhamma/Algorithms/MyAlgorithms/GraphAlgor/breadthSearch.htm - Hristo
显示剩余2条评论

3

请试试这个:

Bipartite.java

/*************************************************************************
 *  Compilation:  javac Bipartite.java
 *  Dependencies: Graph.java 
 *
 *  Given a graph, find either (i) a bipartition or (ii) an odd-length cycle.
 *  Runs in O(E + V) time.
 *
 *
 *************************************************************************/

/**
 *  The <tt>Bipartite</tt> class represents a data type for 
 *  determining whether an undirected graph is bipartite or whether
 *  it has an odd-length cycle.
 *  The <em>isBipartite</em> operation determines whether the graph is
 *  bipartite. If so, the <em>color</em> operation determines a
 *  bipartition; if not, the <em>oddCycle</em> operation determines a
 *  cycle with an odd number of edges.
 *  <p>
 *  This implementation uses depth-first search.
 *  The constructor takes time proportional to <em>V</em> + <em>E</em>
 *  (in the worst case),
 *  where <em>V</em> is the number of vertices and <em>E</em> is the number of edges.
 *  Afterwards, the <em>isBipartite</em> and <em>color</em> operations
 *  take constant time; the <em>oddCycle</em> operation takes time proportional
 *  to the length of the cycle.
 */
public class Bipartite {
    private boolean isBipartite;   // is the graph bipartite?
    private boolean[] color;       // color[v] gives vertices on one side of bipartition
    private boolean[] marked;      // marked[v] = true if v has been visited in DFS
    private int[] edgeTo;          // edgeTo[v] = last edge on path to v
    private Stack<Integer> cycle;  // odd-length cycle

    /**
     * Determines whether an undirected graph is bipartite and finds either a
     * bipartition or an odd-length cycle.
     * @param G the graph
     */
    public Bipartite(Graph G) {
        isBipartite = true;
        color  = new boolean[G.V()];
        marked = new boolean[G.V()];
        edgeTo = new int[G.V()];

        for (int v = 0; v < G.V(); v++) {
            if (!marked[v]) {
                dfs(G, v);
            }
        }
        assert check(G);
    }

    private void dfs(Graph G, int v) { 
        marked[v] = true;
        for (int w : G.adj(v)) {

            // short circuit if odd-length cycle found
            if (cycle != null) return;

            // found uncolored vertex, so recur
            if (!marked[w]) {
                edgeTo[w] = v;
                color[w] = !color[v];
                dfs(G, w);
            } 

            // if v-w create an odd-length cycle, find it
            else if (color[w] == color[v]) {
                isBipartite = false;
                cycle = new Stack<Integer>();
                cycle.push(w);  // don't need this unless you want to include start vertex twice
                for (int x = v; x != w; x = edgeTo[x]) {
                    cycle.push(x);
                }
                cycle.push(w);
            }
        }
    }

    /**
     * Is the graph bipartite?
     * @return <tt>true</tt> if the graph is bipartite, <tt>false</tt> otherwise
     */
    public boolean isBipartite() {
        return isBipartite;
    }

    /**
     * Returns the side of the bipartite that vertex <tt>v</tt> is on.
     * param v the vertex
     * @return the side of the bipartition that vertex <tt>v</tt> is on; two vertices
     *    are in the same side of the bipartition if and only if they have the same color
     * @throws UnsupportedOperationException if this method is called when the graph
     *    is not bipartite
     */
    public boolean color(int v) {
        if (!isBipartite)
            throw new UnsupportedOperationException("Graph is not bipartite");
        return color[v];
    }

    /**
     * Returns an odd-length cycle if the graph is not bipartite, and
     * <tt>null</tt> otherwise.
     * @return an odd-length cycle (as an iterable) if the graph is not bipartite
     *    (and hence has an odd-length cycle), and <tt>null</tt> otherwise
     */
    public Iterable<Integer> oddCycle() {
        return cycle; 
    }

    private boolean check(Graph G) {
        // graph is bipartite
        if (isBipartite) {
            for (int v = 0; v < G.V(); v++) {
                for (int w : G.adj(v)) {
                    if (color[v] == color[w]) {
                        System.err.printf("edge %d-%d with %d and %d in same side of bipartition\n", v, w, v, w);
                        return false;
                    }
                }
            }
        }

        // graph has an odd-length cycle
        else {
            // verify cycle
            int first = -1, last = -1;
            for (int v : oddCycle()) {
                if (first == -1) first = v;
                last = v;
            }
            if (first != last) {
                System.err.printf("cycle begins with %d and ends with %d\n", first, last);
                return false;
            }
        }

        return true;
    }

    /**
     * Unit tests the <tt>Bipartite</tt> data type.
     */
    public static void main(String[] args) {
        // create random bipartite graph with V vertices and E edges; then add F random edges
        int V = Integer.parseInt(args[0]);
        int E = Integer.parseInt(args[1]);
        int F = Integer.parseInt(args[2]);

        Graph G = new Graph(V);
        int[] vertices = new int[V];
        for (int i = 0; i < V; i++) vertices[i] = i;
        StdRandom.shuffle(vertices);
        for (int i = 0; i < E; i++) {
            int v = StdRandom.uniform(V/2);
            int w = StdRandom.uniform(V/2);
            G.addEdge(vertices[v], vertices[V/2 + w]);
        }

        // add F extra edges
        for (int i = 0; i < F; i++) {
            int v = (int) (Math.random() * V);
            int w = (int) (Math.random() * V);
            G.addEdge(v, w);
        }

        StdOut.println(G);

        Bipartite b = new Bipartite(G);
        if (b.isBipartite()) {
            StdOut.println("Graph is bipartite");
            for (int v = 0; v < G.V(); v++) {
                StdOut.println(v + ": " + b.color(v));
            }
        }
        else {
            StdOut.print("Graph has an odd-length cycle: ");
            for (int x : b.oddCycle()) {
                StdOut.print(x + " ");
            }
            StdOut.println();
        }
    }
}

1
这是C#的实现,但概念也可用于Java。我使用邻接矩阵表示图形。检查图形中是否存在循环奇数周期。
如果图形存在一个分区(称为u和v),其中(u并集v)=Graph且(u交集v)=null,则称该图形为Bipartite Graph。 如果您考虑下面的图片,1、2、3、4、5、6、7是图形G中的顶点。让我们将左侧的顶点(1、4、5、6)视为U,将右侧的顶点(2、3、7)视为V。
考虑目前图形中没有红色连接。您可以看到从u到v和v到u有连接,因为它是无向图。但是,在分区内没有连接。这就是我要使用的概念。

enter image description here

考虑下面所示的图,它与上面的图相同,只是绘制得更像树形结构。在这种情况下,如果您可以看到交替级别1、3、5上存在的节点,则它们可以一起形成一个分区,2、4可以形成另一个分区。因此,我们可以轻松地说这个图是二分图。如果同一级别的元素之间有红边怎么办?那么该图就不是二分图。如果您能修改BFS算法,我们可以实现这一点。

enter image description here

Here is the code for that.

   int[,] BPGraph = new int[7,7]{
                                    {0,1,0,1,0,0,0},
                                    {1,0,1,0,1,1,0},
                                    {0,1,0,1,0,0,1},
                                    {1,0,1,0,1,1,0},
                                    {0,1,0,1,0,0,1},
                                    {0,1,0,1,0,0,1},
                                    {0,0,1,0,1,1,0}
                                };

    int[] BPArray = new int[7] { 0, 0, 0, 0, 0, 0, 0 };

    public Boolean BiPartite()
    {
        Queue<int> VertexQueue = new Queue<int>();
        int level = 0;
        int nextlevel=0;
        Boolean BPFlg = true;

        VertexQueue.Enqueue(0);

        while(VertexQueue.Count!=0)
        {
            int current = VertexQueue.Dequeue();
            level = BPArray[current];

            if (level == 0)
                level = 1;

            if (level == 2)
                nextlevel=1;
            else
                nextlevel=2;

            if(BPArray[current]==0) 
                BPArray[current] = level;

            for (int i = 0; i < 7; i++)
            {
                if (BPGraph[current, i] == 1)
                {
                    if (BPArray[i] == 0)
                    {
                        BPArray[i] = nextlevel;
                        VertexQueue.Enqueue(i);
                    }
                    else if (BPArray[i] == level)
                    {
                        BPFlg = false;
                        break;
                    }
                }
            }
            if (!BPFlg)
                break;
        }
        return BPFlg;

    }

0

有向图是指连接节点A和B的边具有方向性;如果从A到B有一条边,这并不意味着从B到A也有一条边。在您的示例中,边具有方向性。(从B到D将成为两条边,一条从B到D,另一条从D到B。)

一种实现方法类似于链表,节点根据需要相互引用。回到您的示例,nodeA将引用nodeB,但反之则不然。nodeE将引用nodeAnodeC等等。您实际上正在创建一种(某种程度上的)数据结构,其中包含节点和可能的边缘概念。有许多方法可以实现它。

一个可能的Java实现方式是创建一个名为AdjacencyList的类,该类具有包含顶点及其相邻顶点的Map<Vertex, List<Vertex>>。然后,AdjacencyList将能够对其Map执行操作。

关于算法和需要注意的事项,请查看Wikipedia上的二分图属性,特别是:

  • 当且仅当一个图不包含奇环时,它才是二分图。因此,二分图不能包含大小为3或更大的团。
  • 每个树都是二分图。
  • 具有偶数个顶点的循环图是二分图。

一个好的测试是实现一个2-着色算法来确认该图确实是二分图。深度优先搜索、广度优先搜索是实现的好练习。


所有节点都创建完成后,我该如何遍历它?我能否使用标准的BFS / DFS和其他与图相关的算法来实现你所建议的内容? - Hristo
你应该能够做到。我在算法课上也不得不做类似的事情。 - Feanor
那很有道理。不过,是否有可能将其作为二分图从文件中读取,而不是创建一个邻接列表,然后将其转换为图形,再对组进行排序。这似乎效率低下。 - Hristo

0

1.) 随机选择一个节点。将其放在二分图的“左”侧。

2.) 选择与步骤1中选择的节点相邻的所有节点,并将它们全部放在“右”侧。

3.) 剩余的所有节点都属于二分图的“左”侧。

结束


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接