2019-05-31 09:45:11 +00:00
|
|
|
|
package bipartitegraph
|
|
|
|
|
|
2020-04-14 07:04:33 +00:00
|
|
|
|
import (
|
|
|
|
|
. "github.com/onsi/gomega/matchers/support/goraph/edge"
|
|
|
|
|
. "github.com/onsi/gomega/matchers/support/goraph/node"
|
|
|
|
|
"github.com/onsi/gomega/matchers/support/goraph/util"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// LargestMatching implements the Hopcroft–Karp algorithm taking as input a bipartite graph
|
|
|
|
|
// and outputting a maximum cardinality matching, i.e. a set of as many edges as possible
|
|
|
|
|
// with the property that no two edges share an endpoint.
|
2019-05-31 09:45:11 +00:00
|
|
|
|
func (bg *BipartiteGraph) LargestMatching() (matching EdgeSet) {
|
|
|
|
|
paths := bg.maximalDisjointSLAPCollection(matching)
|
|
|
|
|
|
|
|
|
|
for len(paths) > 0 {
|
|
|
|
|
for _, path := range paths {
|
|
|
|
|
matching = matching.SymmetricDifference(path)
|
|
|
|
|
}
|
|
|
|
|
paths = bg.maximalDisjointSLAPCollection(matching)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (bg *BipartiteGraph) maximalDisjointSLAPCollection(matching EdgeSet) (result []EdgeSet) {
|
|
|
|
|
guideLayers := bg.createSLAPGuideLayers(matching)
|
|
|
|
|
if len(guideLayers) == 0 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-14 07:04:33 +00:00
|
|
|
|
used := make(map[int]bool)
|
2019-05-31 09:45:11 +00:00
|
|
|
|
|
|
|
|
|
for _, u := range guideLayers[len(guideLayers)-1] {
|
|
|
|
|
slap, found := bg.findDisjointSLAP(u, matching, guideLayers, used)
|
|
|
|
|
if found {
|
|
|
|
|
for _, edge := range slap {
|
|
|
|
|
used[edge.Node1] = true
|
|
|
|
|
used[edge.Node2] = true
|
|
|
|
|
}
|
|
|
|
|
result = append(result, slap)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (bg *BipartiteGraph) findDisjointSLAP(
|
|
|
|
|
start Node,
|
|
|
|
|
matching EdgeSet,
|
|
|
|
|
guideLayers []NodeOrderedSet,
|
2020-04-14 07:04:33 +00:00
|
|
|
|
used map[int]bool,
|
2019-05-31 09:45:11 +00:00
|
|
|
|
) ([]Edge, bool) {
|
|
|
|
|
return bg.findDisjointSLAPHelper(start, EdgeSet{}, len(guideLayers)-1, matching, guideLayers, used)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (bg *BipartiteGraph) findDisjointSLAPHelper(
|
|
|
|
|
currentNode Node,
|
|
|
|
|
currentSLAP EdgeSet,
|
|
|
|
|
currentLevel int,
|
|
|
|
|
matching EdgeSet,
|
|
|
|
|
guideLayers []NodeOrderedSet,
|
2020-04-14 07:04:33 +00:00
|
|
|
|
used map[int]bool,
|
2019-05-31 09:45:11 +00:00
|
|
|
|
) (EdgeSet, bool) {
|
2020-04-14 07:04:33 +00:00
|
|
|
|
used[currentNode.ID] = true
|
2019-05-31 09:45:11 +00:00
|
|
|
|
|
|
|
|
|
if currentLevel == 0 {
|
|
|
|
|
return currentSLAP, true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, nextNode := range guideLayers[currentLevel-1] {
|
2020-04-14 07:04:33 +00:00
|
|
|
|
if used[nextNode.ID] {
|
2019-05-31 09:45:11 +00:00
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
edge, found := bg.Edges.FindByNodes(currentNode, nextNode)
|
|
|
|
|
if !found {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if matching.Contains(edge) == util.Odd(currentLevel) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
currentSLAP = append(currentSLAP, edge)
|
|
|
|
|
slap, found := bg.findDisjointSLAPHelper(nextNode, currentSLAP, currentLevel-1, matching, guideLayers, used)
|
|
|
|
|
if found {
|
|
|
|
|
return slap, true
|
|
|
|
|
}
|
|
|
|
|
currentSLAP = currentSLAP[:len(currentSLAP)-1]
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-14 07:04:33 +00:00
|
|
|
|
used[currentNode.ID] = false
|
2019-05-31 09:45:11 +00:00
|
|
|
|
return nil, false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (bg *BipartiteGraph) createSLAPGuideLayers(matching EdgeSet) (guideLayers []NodeOrderedSet) {
|
2020-04-14 07:04:33 +00:00
|
|
|
|
used := make(map[int]bool)
|
2019-05-31 09:45:11 +00:00
|
|
|
|
currentLayer := NodeOrderedSet{}
|
|
|
|
|
|
|
|
|
|
for _, node := range bg.Left {
|
|
|
|
|
if matching.Free(node) {
|
2020-04-14 07:04:33 +00:00
|
|
|
|
used[node.ID] = true
|
2019-05-31 09:45:11 +00:00
|
|
|
|
currentLayer = append(currentLayer, node)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(currentLayer) == 0 {
|
|
|
|
|
return []NodeOrderedSet{}
|
|
|
|
|
}
|
|
|
|
|
guideLayers = append(guideLayers, currentLayer)
|
|
|
|
|
|
|
|
|
|
done := false
|
|
|
|
|
|
|
|
|
|
for !done {
|
|
|
|
|
lastLayer := currentLayer
|
|
|
|
|
currentLayer = NodeOrderedSet{}
|
|
|
|
|
|
|
|
|
|
if util.Odd(len(guideLayers)) {
|
|
|
|
|
for _, leftNode := range lastLayer {
|
|
|
|
|
for _, rightNode := range bg.Right {
|
2020-04-14 07:04:33 +00:00
|
|
|
|
if used[rightNode.ID] {
|
2019-05-31 09:45:11 +00:00
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
edge, found := bg.Edges.FindByNodes(leftNode, rightNode)
|
|
|
|
|
if !found || matching.Contains(edge) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
currentLayer = append(currentLayer, rightNode)
|
2020-04-14 07:04:33 +00:00
|
|
|
|
used[rightNode.ID] = true
|
2019-05-31 09:45:11 +00:00
|
|
|
|
|
|
|
|
|
if matching.Free(rightNode) {
|
|
|
|
|
done = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for _, rightNode := range lastLayer {
|
|
|
|
|
for _, leftNode := range bg.Left {
|
2020-04-14 07:04:33 +00:00
|
|
|
|
if used[leftNode.ID] {
|
2019-05-31 09:45:11 +00:00
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
edge, found := bg.Edges.FindByNodes(leftNode, rightNode)
|
|
|
|
|
if !found || !matching.Contains(edge) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
currentLayer = append(currentLayer, leftNode)
|
2020-04-14 07:04:33 +00:00
|
|
|
|
used[leftNode.ID] = true
|
2019-05-31 09:45:11 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(currentLayer) == 0 {
|
|
|
|
|
return []NodeOrderedSet{}
|
|
|
|
|
}
|
|
|
|
|
guideLayers = append(guideLayers, currentLayer)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
}
|