All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.salesforce.jgrapht.alg.matching.MaximumWeightBipartiteMatching Maven / Gradle / Ivy

Go to download

This project contains the apt processor that implements all the checks enumerated in @Verify. It is a self contained, and shaded jar.

There is a newer version: 2.0.7
Show newest version
/*
 * (C) Copyright 2015-2017, by Graeme Ahokas and Contributors.
 *
 * JGraphT : a free Java graph-theory library
 *
 * This program and the accompanying materials are dual-licensed under
 * either
 *
 * (a) the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation, or (at your option) any
 * later version.
 *
 * or (per the licensee's choosing)
 *
 * (b) the terms of the Eclipse Public License v1.0 as published by
 * the Eclipse Foundation.
 */
package com.salesforce.jgrapht.alg.matching;

import java.util.*;

import com.salesforce.jgrapht.*;
import com.salesforce.jgrapht.alg.interfaces.*;
import com.salesforce.jgrapht.util.*;

/**
 * This class finds a maximum weight matching of a simple undirected weighted bipartite graph. The
 * algorithm runs in O(V|E|^2). The algorithm is described in The LEDA Platform of Combinatorial and
 * Geometric Computing, Cambridge University Press, 1999.
 * https://people.mpi-inf.mpg.de/~mehlhorn/LEDAbook.html Note: the input graph must be bipartite
 * with positive integer edge weights
 *
 * @param  the graph vertex type
 * @param  the graph edge type
 *
 * @author Graeme Ahokas
 */
public class MaximumWeightBipartiteMatching
    implements MatchingAlgorithm
{
    private final UndirectedGraph graph;
    private Set partition1;
    private Set partition2;

    private Map vertexWeights;
    private Map hasVertexBeenProcessed;
    private Map isEdgeMatched;

    private Set bipartiteMatching;

    /**
     * Construct a new instance of the algorithm. Supported graphs are simple undirected weighted
     * bipartite with positive integer edge weights.
     * 
     * @param graph the input graph
     * @param partition1 the first partition of the vertex set
     * @param partition2 the second partition of the vertex set
     * @throws IllegalArgumentException if the graph is not undirected
     */
    public MaximumWeightBipartiteMatching(Graph graph, Set partition1, Set partition2)
    {
        if (graph == null) {
            throw new IllegalArgumentException("Input graph cannot be null");
        }
        if (!(graph instanceof UndirectedGraph)) {
            throw new IllegalArgumentException("Only undirected graphs supported");
        }
        this.graph = TypeUtil.uncheckedCast(graph, null);
        if (partition1 == null) {
            throw new IllegalArgumentException("Partition 1 cannot be null");
        }
        this.partition1 = partition1;
        if (partition2 == null) {
            throw new IllegalArgumentException("Partition 2 cannot be null");
        }
        this.partition2 = partition2;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Matching computeMatching()
    {
        if (!GraphTests.isSimple(graph)) {
            throw new IllegalArgumentException("Only simple graphs supported");
        }
        if (!GraphTests.isBipartitePartition(graph, partition1, partition2)) {
            throw new IllegalArgumentException("Graph partition is not bipartite");
        }
        this.vertexWeights = new HashMap<>();
        this.hasVertexBeenProcessed = new HashMap<>();
        this.isEdgeMatched = new HashMap<>();

        initializeVerticesAndEdges();

        this.bipartiteMatching = maximumWeightBipartiteMatching();
        double weight = 0d;
        for (E edge : bipartiteMatching) {
            weight += graph.getEdgeWeight(edge);
        }
        return new MatchingImpl<>(bipartiteMatching, weight);
    }

    private void initializeVerticesAndEdges()
    {
        for (V vertex : graph.vertexSet()) {
            if (isTargetVertex(vertex)) {
                hasVertexBeenProcessed.put(vertex, true);
                setVertexWeight(vertex, (long) 0);
            } else {
                hasVertexBeenProcessed.put(vertex, false);
                setVertexWeight(vertex, maximumWeightOfEdgeIncidentToVertex(vertex));
            }
        }

        for (E edge : graph.edgeSet()) {
            isEdgeMatched.put(edge, false);
        }
    }

    private long maximumWeightOfEdgeIncidentToVertex(V vertex)
    {
        long maxWeight = 0;
        for (E edge : graph.edgesOf(vertex)) {
            if (graph.getEdgeWeight(edge) > maxWeight) {
                maxWeight = (long) graph.getEdgeWeight(edge);
            }
        }
        return maxWeight;
    }

    private boolean isSourceVertex(V vertex)
    {
        return partition1.contains(vertex);
    }

    private boolean isTargetVertex(V vertex)
    {
        return partition2.contains(vertex);
    }

    private long vertexWeight(V vertex)
    {
        return vertexWeights.get(vertex);
    }

    private void setVertexWeight(V vertex, Long weight)
    {
        vertexWeights.put(vertex, weight);
    }

    private long reducedWeight(E edge)
    {
        return (long) (vertexWeight(graph.getEdgeSource(edge))
            + vertexWeight(graph.getEdgeTarget(edge)) - graph.getEdgeWeight(edge));
    }

    private boolean isVertexMatched(V vertex, Set matchings)
    {
        for (E edge : matchings) {
            if (graph.getEdgeSource(edge).equals(vertex)
                || graph.getEdgeTarget(edge).equals(vertex))
            {
                return true;
            }
        }
        return false;
    }

    private void addPathToMatchings(List path, Set matchings)
    {
        for (int i = 0; i < path.size(); i++) {
            E edge = path.get(i);
            if ((i % 2) == 0) {
                isEdgeMatched.put(edge, true);
                matchings.add(edge);
            } else {
                isEdgeMatched.put(edge, false);
                matchings.remove(edge);
            }
        }
    }

    private void adjustVertexWeights(Map> reachableVertices)
    {
        long alpha = Long.MAX_VALUE;
        for (V vertex : reachableVertices.keySet()) {
            if (isSourceVertex(vertex) && (vertexWeights.get(vertex) < alpha)) {
                alpha = vertexWeights.get(vertex);
            }
        }

        long beta = Long.MAX_VALUE;
        for (V vertex : reachableVertices.keySet()) {
            if (isTargetVertex(vertex)) {
                continue;
            }
            for (E edge : graph.edgesOf(vertex)) {
                if (hasVertexBeenProcessed.get(Graphs.getOppositeVertex(graph, edge, vertex))
                    && !reachableVertices
                        .keySet().contains(Graphs.getOppositeVertex(graph, edge, vertex))
                    && (reducedWeight(edge) < beta))
                {
                    beta = reducedWeight(edge);
                }
            }
        }

        assert ((alpha > 0) && (beta > 0));

        long minValue = Math.min(alpha, beta);

        for (V vertex : reachableVertices.keySet()) {
            if (isSourceVertex(vertex)) {
                vertexWeights.put(vertex, vertexWeights.get(vertex) - minValue);
            } else {
                vertexWeights.put(vertex, vertexWeights.get(vertex) + minValue);
            }
        }
    }

    private Map> verticesReachableByTightAlternatingEdgesFromVertex(V vertex)
    {
        Map> pathsToVertices = new HashMap<>();
        pathsToVertices.put(vertex, new ArrayList<>());
        findPathsToVerticesFromVertices(Collections.singletonList(vertex), false, pathsToVertices);
        return pathsToVertices;
    }

    private void findPathsToVerticesFromVertices(
        List verticesToProcess, boolean needMatchedEdge, Map> pathsToVertices)
    {
        if (verticesToProcess.size() == 0) {
            return;
        }
        List nextVerticesToProcess = new ArrayList<>();
        for (V vertex : verticesToProcess) {
            for (E edge : graph.edgesOf(vertex)) {
                V adjacentVertex = Graphs.getOppositeVertex(graph, edge, vertex);
                if (hasVertexBeenProcessed.get(adjacentVertex) && (reducedWeight(edge) == 0)
                    && !pathsToVertices.keySet().contains(adjacentVertex))
                {
                    if ((needMatchedEdge && isEdgeMatched.get(edge))
                        || (!needMatchedEdge && !isEdgeMatched.get(edge)))
                    {
                        nextVerticesToProcess.add(adjacentVertex);
                        List pathToAdjacentVertex = new ArrayList<>(pathsToVertices.get(vertex));
                        pathToAdjacentVertex.add(edge);
                        pathsToVertices.put(adjacentVertex, pathToAdjacentVertex);
                    }
                }
            }
        }
        findPathsToVerticesFromVertices(nextVerticesToProcess, !needMatchedEdge, pathsToVertices);
    }

    private Set maximumWeightBipartiteMatching()
    {
        Set matchings = new HashSet<>();
        for (V vertex : partition1) {
            hasVertexBeenProcessed.put(vertex, true);
            while (true) {
                Map> reachableVertices =
                    verticesReachableByTightAlternatingEdgesFromVertex(vertex);
                boolean successful = false;
                for (V reachableVertex : reachableVertices.keySet()) {
                    if (isSourceVertex(reachableVertex) && (vertexWeight(reachableVertex) == 0)) {
                        addPathToMatchings(reachableVertices.get(reachableVertex), matchings);
                        successful = true;
                        break;
                    }
                    if (isTargetVertex(reachableVertex)
                        && !isVertexMatched(reachableVertex, matchings))
                    {
                        addPathToMatchings(reachableVertices.get(reachableVertex), matchings);
                        successful = true;
                        break;
                    }
                }
                if (successful) {
                    break;
                }
                adjustVertexWeights(reachableVertices);
            }
        }
        return matchings;
    }
}

// End MaximumWeightBipartiteMatching.java




© 2015 - 2025 Weber Informatics LLC | Privacy Policy