org.jgrapht.alg.partition.BipartitePartitioning Maven / Gradle / Ivy
/*
* (C) Copyright 2016-2021, by Dimitrios Michail, Alexandru Valeanu and Contributors.
*
* JGraphT : a free Java graph-theory library
*
* See the CONTRIBUTORS.md file distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the
* GNU Lesser General Public License v2.1 or later
* which is available at
* http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html.
*
* SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later
*/
package org.jgrapht.alg.partition;
import org.jgrapht.*;
import org.jgrapht.alg.interfaces.*;
import java.util.*;
import static org.jgrapht.GraphTests.isEmpty;
/**
* Algorithm for computing bipartite partitions thus checking whether a graph is bipartite or not.
*
*
* The algorithm runs in linear time in the number of vertices and edges.
*
* @param the graph vertex type
* @param the graph edge type
*
* @author Dimitrios Michail
* @author Alexandru Valeanu
*/
public class BipartitePartitioning
implements
PartitioningAlgorithm
{
/* Input graph */
private Graph graph;
/* Cached bipartite partitioning */
private boolean computed = false;
private Partitioning cachedPartitioning;
/**
* Constructs a new bipartite partitioning.
*
* @param graph the input graph;
*/
public BipartitePartitioning(Graph graph)
{
this.graph = Objects.requireNonNull(graph, "graph cannot be null");
}
/**
* Test whether the input graph is bipartite.
*
* @return true if the input graph is bipartite, false otherwise
*/
public boolean isBipartite()
{
if (isEmpty(graph)) {
return true;
}
try {
// at most n^2/4 edges
if (Math.multiplyExact(4, graph.edgeSet().size()) > Math
.multiplyExact(graph.vertexSet().size(), graph.vertexSet().size()))
{
return false;
}
} catch (ArithmeticException e) {
// ignore
}
return this.getPartitioning() != null;
}
/**
* {@inheritDoc}
*/
@Override
public Partitioning getPartitioning()
{
if (computed) {
return cachedPartitioning;
}
Set unknown = new LinkedHashSet<>(graph.vertexSet());
Set odd = new LinkedHashSet<>();
Deque queue = new ArrayDeque<>();
while (!unknown.isEmpty()) {
if (queue.isEmpty()) {
queue.add(unknown.iterator().next());
}
V v = queue.removeFirst();
unknown.remove(v);
for (E e : graph.edgesOf(v)) {
V n = Graphs.getOppositeVertex(graph, e, v);
if (unknown.contains(n)) {
queue.add(n);
if (!odd.contains(v)) {
odd.add(n);
}
} else if (odd.contains(v) == odd.contains(n)) {
computed = true;
cachedPartitioning = null;
return null;
}
}
}
Set even = new LinkedHashSet<>(graph.vertexSet());
even.removeAll(odd);
computed = true;
cachedPartitioning = new PartitioningImpl<>(Arrays.asList(even, odd));
return cachedPartitioning;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isValidPartitioning(Partitioning partitioning)
{
Objects.requireNonNull(partitioning, "Partition cannot be null");
if (partitioning.getNumberPartitions() != 2)
return false;
Set firstPartition = partitioning.getPartition(0);
Set secondPartition = partitioning.getPartition(1);
Objects.requireNonNull(firstPartition, "First partition class cannot be null");
Objects.requireNonNull(secondPartition, "Second partition class cannot be null");
if (graph.vertexSet().size() != firstPartition.size() + secondPartition.size()) {
return false;
}
for (V v : graph.vertexSet()) {
Collection extends V> otherPartition;
if (firstPartition.contains(v)) {
otherPartition = secondPartition;
} else if (secondPartition.contains(v)) {
otherPartition = firstPartition;
} else {
// v does not belong to any of the two partitions
return false;
}
for (E e : graph.edgesOf(v)) {
V other = Graphs.getOppositeVertex(graph, e, v);
if (!otherPartition.contains(other)) {
return false;
}
}
}
return true;
}
}