Commit 12947966 authored by Fokion Zervoudakis's avatar Fokion Zervoudakis

Use DFS to detect cycles.

parent 22a132bd
...@@ -30,32 +30,32 @@ class AdjMatrix<T extends Vertex> implements Graph<T> { ...@@ -30,32 +30,32 @@ class AdjMatrix<T extends Vertex> implements Graph<T> {
@Override @Override
public void addVertex(T u) { public void addVertex(T u) {
M[u.key][u.key] = u; M[u.getKey()][u.getKey()] = u;
} }
@Override @Override
public void addEdge(T u, T v) { public void addEdge(T u, T v) {
addVertex(u); addVertex(u);
addVertex(v); addVertex(v);
M[u.key][v.key] = v; M[u.getKey()][v.getKey()] = v;
} }
@Override @Override
public void removeEdge(T u, T v) { public void removeEdge(T u, T v) {
M[u.key][v.key] = null; M[u.getKey()][v.getKey()] = null;
} }
@Override @Override
public boolean hasEdge(T u, T v) { public boolean hasEdge(T u, T v) {
return M[u.key][v.key] != null; return M[u.getKey()][v.getKey()] != null;
} }
@Override @Override
public List<T> inEdges(T u) { public List<T> inEdges(T u) {
var L = new ArrayList<T>(); var L = new ArrayList<T>();
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
if (i != u.key) { if (i != u.getKey()) {
var v = M[i][u.key]; var v = M[i][u.getKey()];
if (v != null) { if (v != null) {
L.add(M[i][i]); L.add(M[i][i]);
} }
...@@ -68,8 +68,8 @@ class AdjMatrix<T extends Vertex> implements Graph<T> { ...@@ -68,8 +68,8 @@ class AdjMatrix<T extends Vertex> implements Graph<T> {
public List<T> outEdges(T u) { public List<T> outEdges(T u) {
var L = new ArrayList<T>(); var L = new ArrayList<T>();
for (int j = 0; j < n; j++) { for (int j = 0; j < n; j++) {
if (j != u.key) { if (j != u.getKey()) {
var v = M[u.key][j]; var v = M[u.getKey()][j];
if (v != null) { if (v != null) {
L.add(v); L.add(v);
} }
......
...@@ -27,12 +27,26 @@ class Bfs { ...@@ -27,12 +27,26 @@ class Bfs {
Vertex u = Q.remove(0); Vertex u = Q.remove(0);
for (Vertex v : G.outEdges(u)) { for (Vertex v : G.outEdges(u)) {
if (!v.visited) { if (!v.visited) {
v.setParent(u);
v.d = u.d + 1; v.d = u.d + 1;
v.p = u;
v.visited = true; v.visited = true;
Q.add(v); Q.add(v);
} }
} }
} }
} }
static class Vertex extends graph.Vertex {
int d = 0;
boolean visited = false;
Vertex(int key) {
super(key);
}
@Override
public String toString() {
return super.toString() + ":" + d;
}
}
} }
package graph;
import java.util.ArrayList;
import java.util.List;
import static graph.Color.BLACK;
import static graph.Color.WHITE;
class Bfs2 {
boolean isBipartite(Graph<Vertex> G, Vertex start) {
List<Vertex> Q = new ArrayList<>();
Q.add(start);
start.visited = true;
start.color = WHITE;
while (!Q.isEmpty()) {
Vertex u = Q.remove(0);
for (Vertex v : G.outEdges(u)) {
if (v.visited || v.color == u.color) {
return false;
} else {
v.setParent(u);
v.d = u.d + 1;
v.visited = true;
v.color = u.complement();
Q.add(v);
}
}
}
return true;
}
static class Vertex extends graph.Vertex {
int d = 0;
boolean visited = false;
Color color;
Vertex(int key) {
super(key);
}
Color complement() {
return (color == WHITE) ? BLACK : WHITE;
}
@Override
public String toString() {
return super.toString() + ":" + color + ":" + d;
}
}
}
package graph;
enum Color {
WHITE, GRAY, BLACK
}
package graph;
import java.util.ArrayList;
import java.util.List;
class Coloring {
class Bfs {
boolean isBipartite(Graph<Vertex> G, Vertex start) {
List<Vertex> Q = new ArrayList<>();
Q.add(start);
start.visited = true;
start.c = Color.WH;
while (!Q.isEmpty()) {
Vertex u = Q.remove(0);
for (Vertex v : G.outEdges(u)) {
if (v.visited || v.c == u.c) {
return false;
} else {
v.d = u.d + 1;
v.p = u;
v.c = complement(u.c);
v.visited = true;
Q.add(v);
}
}
}
return true;
}
}
private Color complement(Color c) {
return (c == Color.WH) ? Color.BK : Color.WH;
}
enum Color {
BK, WH
}
static class Vertex extends graph.Vertex {
Color c;
Vertex(int key) {
super(key);
}
@Override
public String toString() {
return super.toString() + ":" + c;
}
}
}
...@@ -6,8 +6,8 @@ class Dfs { ...@@ -6,8 +6,8 @@ class Dfs {
class It { class It {
/** /**
Uses iterative depth-first search backed by a last-in-first-out data Uses iterative depth-first search backed by a last-in-first-out data
structure to detect cycles, and topologically sort all vertices, in a structure to topologically sort all vertices in a directed acyclic graph
directed acyclic graph {@code G=(V,E)}. {@code G=(V,E)}.
<ul> <ul>
<li>time_worst=O(V+E) <li>time_worst=O(V+E)
<li>space_worst=O(V) <li>space_worst=O(V)
...@@ -31,7 +31,7 @@ class Dfs { ...@@ -31,7 +31,7 @@ class Dfs {
u.visited = true; u.visited = true;
for (Vertex v : G.outEdges(u)) { for (Vertex v : G.outEdges(u)) {
if (!v.visited) { if (!v.visited) {
v.p = u; v.setParent(u);
S.push(v); S.push(v);
} }
} }
...@@ -41,8 +41,8 @@ class Dfs { ...@@ -41,8 +41,8 @@ class Dfs {
class Rec { class Rec {
/** /**
Uses recursive depth-first search to detect cycles, and topologically Uses recursive depth-first search to topologically sort all vertices in
sort all vertices, in a directed acyclic graph {@code G=(V,E)}. a directed acyclic graph {@code G=(V,E)}.
<ul> <ul>
<li>time_worst=O(V+E) <li>time_worst=O(V+E)
<li>space_worst=O(V) <li>space_worst=O(V)
...@@ -62,10 +62,18 @@ class Dfs { ...@@ -62,10 +62,18 @@ class Dfs {
u.visited = true; u.visited = true;
for (Vertex v : G.outEdges(u)) { for (Vertex v : G.outEdges(u)) {
if (!v.visited) { if (!v.visited) {
v.p = u; v.setParent(u);
dfsVisit(G, v); dfsVisit(G, v);
} }
} }
} }
} }
static class Vertex extends graph.Vertex {
boolean visited = false;
Vertex(int key) {
super(key);
}
}
} }
package graph;
import static graph.Color.BLACK;
import static graph.Color.GRAY;
import static graph.Color.WHITE;
class Dfs2 {
/**
Uses recursive depth-first search to detect cycles in a directed acyclic
graph {@code G=(V,E)}. In the absence of cycles, all vertices in {@code G}
are sorted topologically.
<ul>
<li>time_worst=O(V+E)
<li>space_worst=O(V)
</ul>
@param G a graph
*/
void dfs(Graph<Vertex> G, Vertex u) {
u.color = GRAY;
for (Vertex v : G.outEdges(u)) {
if (v.color == GRAY) {
throw new RuntimeException();
} else if (v.color == WHITE) {
v.setParent(u);
v.color = GRAY;
dfs(G, v);
}
}
u.color = BLACK;
}
static class Vertex extends graph.Vertex {
Color color = WHITE;
Vertex(int key) {
super(key);
}
}
}
...@@ -4,10 +4,10 @@ class Path { ...@@ -4,10 +4,10 @@ class Path {
static String print(Vertex src, Vertex dest) { static String print(Vertex src, Vertex dest) {
if (dest.equals(src)) { if (dest.equals(src)) {
return dest + ""; return dest + "";
} else if (dest.p == null) { } else if (dest.getParent() == null) {
return null; return null;
} else { } else {
return print(src, dest.p) + " -> " + dest; return print(src, dest.getParent()) + " -> " + dest;
} }
} }
} }
package graph; package graph;
class Vertex { class Vertex {
int d = 0; private int key;
boolean visited = false;
Vertex p; private Vertex parent;
int key; Vertex() {
}
Vertex(int key) { Vertex(int key) {
this.key = key; this.key = key;
} }
int getKey() {
return key;
}
Vertex getParent() {
return parent;
}
void setParent(Vertex v) {
this.parent = v;
}
@Override @Override
public String toString() { public String toString() {
return key + ""; return key + "";
......
...@@ -6,26 +6,26 @@ import org.junit.jupiter.api.Test; ...@@ -6,26 +6,26 @@ import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
class ColoringTest_AdjList_1 { class Bfs2Test_AdjList_1 {
private Graph<Coloring.Vertex> G; private Graph<Bfs2.Vertex> G;
private Coloring.Vertex v0; private Bfs2.Vertex v0;
private Coloring.Bfs bfs; private Bfs2 bfs;
@BeforeEach @BeforeEach
void beforeEach() { void beforeEach() {
v0 = new Stub(0); v0 = new Bfs2.Vertex(0);
G = new AdjList<>(); G = new AdjList<>();
G.addVertex(v0); G.addVertex(v0);
bfs = new Coloring().new Bfs(); bfs = new Bfs2();
} }
@Test @Test
void itSearchesOneVertexWithZeroNeighbors() { void itSearchesOneVertexWithZeroNeighbors() {
assertTrue(bfs.isBipartite(G, v0)); assertTrue(bfs.isBipartite(G, v0));
var expected = "{0:WH:0=[]}"; var expected = "{0:WHITE:0=[]}";
var actual = G.toString(); var actual = G.toString();
assertEquals(expected, actual); assertEquals(expected, actual);
...@@ -33,11 +33,11 @@ class ColoringTest_AdjList_1 { ...@@ -33,11 +33,11 @@ class ColoringTest_AdjList_1 {
@Test @Test
void itSearchesOneVertexWithOneNeighbor() { void itSearchesOneVertexWithOneNeighbor() {
G.addEdge(v0, new Stub(1)); G.addEdge(v0, new Bfs2.Vertex(1));
assertTrue(bfs.isBipartite(G, v0)); assertTrue(bfs.isBipartite(G, v0));
var expected = "{0:WH:0=[1:BK:1], 1:BK:1=[]}"; var expected = "{0:WHITE:0=[1:BLACK:1], 1:BLACK:1=[]}";
var actual = G.toString(); var actual = G.toString();
assertEquals(expected, actual); assertEquals(expected, actual);
...@@ -45,27 +45,14 @@ class ColoringTest_AdjList_1 { ...@@ -45,27 +45,14 @@ class ColoringTest_AdjList_1 {
@Test @Test
void itSearchesOneVertexWithManyNeighbors() { void itSearchesOneVertexWithManyNeighbors() {
G.addEdge(v0, new Stub(1)); G.addEdge(v0, new Bfs2.Vertex(1));
G.addEdge(v0, new Stub(2)); G.addEdge(v0, new Bfs2.Vertex(2));
assertTrue(bfs.isBipartite(G, v0)); assertTrue(bfs.isBipartite(G, v0));
var expected = "{0:WH:0=[1:BK:1, 2:BK:1], 1:BK:1=[], 2:BK:1=[]}"; var expected = "{0:WHITE:0=[1:BLACK:1, 2:BLACK:1], 1:BLACK:1=[], 2:BLACK:1=[]}";
var actual = G.toString(); var actual = G.toString();
assertEquals(expected, actual); assertEquals(expected, actual);
} }
//<editor-fold desc="stubs">
class Stub extends Coloring.Vertex {
Stub(int key) {
super(key);
}
@Override
public String toString() {
return super.toString() + ":" + d;
}
}
//</editor-fold>
} }
...@@ -6,16 +6,16 @@ import org.junit.jupiter.api.Test; ...@@ -6,16 +6,16 @@ import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
class ColoringTest_AdjList_2 { class Bfs2Test_AdjList_2 {
private Graph<Coloring.Vertex> G; private Graph<Bfs2.Vertex> G;
private Coloring.Vertex v0; private Bfs2.Vertex v0;
private Coloring.Bfs bfs; private Bfs2 bfs;
@BeforeEach @BeforeEach
void beforeEach() { void beforeEach() {
v0 = new Stub(0); v0 = new Bfs2.Vertex(0);
G = new AdjList<>(); G = new AdjList<>();
bfs = new Coloring().new Bfs(); bfs = new Bfs2();
} }
@Test @Test
...@@ -25,7 +25,7 @@ class ColoringTest_AdjList_2 { ...@@ -25,7 +25,7 @@ class ColoringTest_AdjList_2 {
assertFalse(bfs.isBipartite(G, v0)); assertFalse(bfs.isBipartite(G, v0));
var expected = "{0:WH:0=[0:WH:0]}"; var expected = "{0:WHITE:0=[0:WHITE:0]}";
var actual = G.toString(); var actual = G.toString();
assertEquals(expected, actual); assertEquals(expected, actual);
...@@ -33,7 +33,7 @@ class ColoringTest_AdjList_2 { ...@@ -33,7 +33,7 @@ class ColoringTest_AdjList_2 {
@Test @Test
void itIgnoresCyclesFromSymmetricPaths() { void itIgnoresCyclesFromSymmetricPaths() {
var v1 = new Stub(1); var v1 = new Bfs2.Vertex(1);
// v0 -> v1 -> v0 // v0 -> v1 -> v0
G.addEdge(v0, v1); G.addEdge(v0, v1);
...@@ -41,7 +41,7 @@ class ColoringTest_AdjList_2 { ...@@ -41,7 +41,7 @@ class ColoringTest_AdjList_2 {
assertFalse(bfs.isBipartite(G, v0)); assertFalse(bfs.isBipartite(G, v0));
var expected = "{0:WH:0=[1:BK:1], 1:BK:1=[0:WH:0]}"; var expected = "{0:WHITE:0=[1:BLACK:1], 1:BLACK:1=[0:WHITE:0]}";
var actual = G.toString(); var actual = G.toString();
assertEquals(expected, actual); assertEquals(expected, actual);
...@@ -49,8 +49,8 @@ class ColoringTest_AdjList_2 { ...@@ -49,8 +49,8 @@ class ColoringTest_AdjList_2 {
@Test @Test
void itIgnoresCyclesFromAsymmetricPaths() { void itIgnoresCyclesFromAsymmetricPaths() {
var v1 = new Stub(1); var v1 = new Bfs2.Vertex(1);
var v2 = new Stub(2); var v2 = new Bfs2.Vertex(2);
// v0 -> v1 -> v2 -> v0 // v0 -> v1 -> v2 -> v0
G.addEdge(v0, v1); G.addEdge(v0, v1);
...@@ -59,22 +59,9 @@ class ColoringTest_AdjList_2 { ...@@ -59,22 +59,9 @@ class ColoringTest_AdjList_2 {
assertFalse(bfs.isBipartite(G, v0)); assertFalse(bfs.isBipartite(G, v0));
var expected = "{0:WH:0=[1:BK:1], 1:BK:1=[2:WH:2], 2:WH:2=[0:WH:0]}"; var expected = "{0:WHITE:0=[1:BLACK:1], 1:BLACK:1=[2:WHITE:2], 2:WHITE:2=[0:WHITE:0]}";
var actual = G.toString(); var actual = G.toString();
assertEquals(expected, actual); assertEquals(expected, actual);
} }
//<editor-fold desc="stubs">
class Stub extends Coloring.Vertex {
Stub(int key) {
super(key);
}
@Override
public String toString() {
return super.toString() + ":" + d;
}
}
//</editor-fold>
} }
...@@ -5,18 +5,18 @@ import org.junit.jupiter.api.Test; ...@@ -5,18 +5,18 @@ import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
class ColoringTest_AdjList_3 { class Bfs2Test_AdjList_3 {
@Test @Test
void itSearchesManyVerticesWithManyNeighbors() { void itSearchesManyVerticesWithManyNeighbors() {
var v0 = new Stub(0); var v0 = new Bfs2.Vertex(0);
var v1 = new Stub(1); var v1 = new Bfs2.Vertex(1);
var v2 = new Stub(2); var v2 = new Bfs2.Vertex(2);
var v3 = new Stub(3); var v3 = new Bfs2.Vertex(3);
var v4 = new Stub(4); var v4 = new Bfs2.Vertex(4);
var v5 = new Stub(5); var v5 = new Bfs2.Vertex(5);
var v6 = new Stub(6); var v6 = new Bfs2.Vertex(6);
var G = new AdjList<Coloring.Vertex>(); var G = new AdjList<Bfs2.Vertex>();
G.addEdge(v0, v1); G.addEdge(v0, v1);
G.addEdge(v0, v2); G.addEdge(v0, v2);
...@@ -33,18 +33,18 @@ class ColoringTest_AdjList_3 { ...@@ -33,18 +33,18 @@ class ColoringTest_AdjList_3 {
G.addEdge(v1, v0); // ignored symmetric path: v0 -> v1 -> v0 G.addEdge(v1, v0); // ignored symmetric path: v0 -> v1 -> v0
G.addEdge(v6, v0); // ignored asymmetric path: v0 -> v2 -> v6 -> v0 G.addEdge(v6, v0); // ignored asymmetric path: v0 -> v2 -> v6 -> v0
assertFalse(new Coloring().new Bfs().isBipartite(G, v0)); assertFalse(new Bfs2().isBipartite(G, v0));
//@formatter:off //@formatter:off
var expected = var expected =
"{" + "{" +
"0:WH:0=[1:BK:1, 2:BK:1, 4:BK:1, 0:WH:0], " + "0:WHITE:0=[1:BLACK:1, 2:BLACK:1, 4:BLACK:1, 0:WHITE:0], " +
"1:BK:1=[3:null:0, 5:null:0, 0:WH:0], " + "1:BLACK:1=[3:null:0, 5:null:0, 0:WHITE:0], " +
"2:BK:1=[6:null:0], " + "2:BLACK:1=[6:null:0], " +
"4:BK:1=[5:null:0], " + "4:BLACK:1=[5:null:0], " +
"3:null:0=[], " + "3:null:0=[], " +
"5:null:0=[], " + "5:null:0=[], " +
"6:null:0=[0:WH:0]" + "6:null:0=[0:WHITE:0]" +
"}"; "}";
//@formatter:on //@formatter:on
...@@ -53,16 +53,4 @@ class ColoringTest_AdjList_3 { ...@@ -53,16 +53,4 @@ class ColoringTest_AdjList_3 {
assertEquals(expected, actual); assertEquals(expected, actual);
} }
//<editor-fold desc="stubs">
class Stub extends Coloring.Vertex {
Stub(int key) {
super(key);
}
@Override
public String toString() {
return super.toString() + ":" + d;
}
}
//</editor-fold>
} }
...@@ -6,13 +6,13 @@ import org.junit.jupiter.api.Test; ...@@ -6,13 +6,13 @@ import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
class BfsTest_AdjList_1 { class BfsTest_AdjList_1 {
private Graph<Vertex> G; private Graph<Bfs.Vertex> G;
private Vertex v0; private Bfs.Vertex v0;
private Bfs bfs; private Bfs bfs;