* Adding node and cycle detection by hashing

* Adding implementation for all algorithms for cycle detection and removal

* Applying formatting rules

* Refactoring methods and adding more tests

* Fixing infinite loop corner case
This commit is contained in:
deep20jain 2017-09-08 22:53:55 +05:30 committed by Zeger Hendrikse
parent 35ac22c947
commit cccd6a3eab
14 changed files with 153 additions and 115 deletions

View File

@ -2,9 +2,9 @@ package com.baeldung.algorithms.linkedlist;
public class CycleDetectionBruteForce {
public static <T> boolean detectCycle(Node<T> head) {
public static <T> CycleDetectionResult<T> detectCycle(Node<T> head) {
if (head == null) {
return false;
return new CycleDetectionResult<>(false, null);
}
Node<T> it1 = head;
@ -25,14 +25,14 @@ public class CycleDetectionBruteForce {
}
if (noOfTimesCurrentNodeVisited == 2) {
return true;
return new CycleDetectionResult<>(true, it1);
}
x--;
}
}
return false;
return new CycleDetectionResult<>(false, null);
}
}

View File

@ -2,9 +2,9 @@ package com.baeldung.algorithms.linkedlist;
public class CycleDetectionByFastAndSlowIterators {
public static <T> boolean detectCycle(Node<T> head) {
public static <T> CycleDetectionResult<T> detectCycle(Node<T> head) {
if (head == null) {
return false;
return new CycleDetectionResult<>(false, null);
}
Node<T> slow = head;
@ -15,11 +15,11 @@ public class CycleDetectionByFastAndSlowIterators {
fast = fast.next.next;
if (slow == fast) {
return true;
return new CycleDetectionResult<>(true, fast);
}
}
return false;
return new CycleDetectionResult<>(false, null);
}
}

View File

@ -5,9 +5,9 @@ import java.util.Set;
public class CycleDetectionByHashing {
public static <T> boolean detectCycle(Node<T> head) {
public static <T> CycleDetectionResult<T> detectCycle(Node<T> head) {
if (head == null) {
return false;
return new CycleDetectionResult<>(false, null);
}
Set<Node<T>> set = new HashSet<>();
@ -15,13 +15,13 @@ public class CycleDetectionByHashing {
while (node != null) {
if (set.contains(node)) {
return true;
return new CycleDetectionResult<>(true, node);
}
set.add(node);
node = node.next;
}
return false;
return new CycleDetectionResult<>(false, null);
}
}

View File

@ -0,0 +1,12 @@
package com.baeldung.algorithms.linkedlist;
public class CycleDetectionResult<T> {
boolean cycleExists;
Node<T> node;
public CycleDetectionResult(boolean cycleExists, Node<T> node) {
super();
this.cycleExists = cycleExists;
this.node = node;
}
}

View File

@ -3,26 +3,21 @@ package com.baeldung.algorithms.linkedlist;
public class CycleRemovalBruteForce {
public static <T> boolean detectAndRemoveCycle(Node<T> head) {
if (head == null) {
return false;
CycleDetectionResult<T> result = CycleDetectionByFastAndSlowIterators.detectCycle(head);
if (result.cycleExists) {
removeCycle(result.node, head);
}
Node<T> slow = head;
Node<T> fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
removeCycle(slow, head);
return true;
}
}
return false;
return result.cycleExists;
}
/**
* @param loopNodeParam - reference to the node where Flyods cycle
* finding algorithm ends, i.e. the fast and the slow iterators
* meet.
* @param head - reference to the head of the list
*/
private static <T> void removeCycle(Node<T> loopNodeParam, Node<T> head) {
Node<T> it = head;
@ -39,12 +34,12 @@ public class CycleRemovalBruteForce {
private static <T> boolean isNodeReachableFromLoopNode(Node<T> it, Node<T> loopNodeParam) {
Node<T> loopNode = loopNodeParam;
while (loopNode.next != loopNodeParam) {
do {
if (it == loopNode) {
return true;
}
loopNode = loopNode.next;
}
} while (loopNode.next != loopNodeParam);
return false;
}

View File

@ -3,28 +3,17 @@ package com.baeldung.algorithms.linkedlist;
public class CycleRemovalByCountingLoopNodes {
public static <T> boolean detectAndRemoveCycle(Node<T> head) {
if (head == null) {
return false;
CycleDetectionResult<T> result = CycleDetectionByFastAndSlowIterators.detectCycle(head);
if (result.cycleExists) {
removeCycle(result.node, head);
}
Node<T> slow = head;
Node<T> fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
int cycleLength = calculateCycleLength(slow);
removeCycle(head, cycleLength);
return true;
}
}
return false;
return result.cycleExists;
}
private static <T> void removeCycle(Node<T> head, int cycleLength) {
private static <T> void removeCycle(Node<T> loopNodeParam, Node<T> head) {
int cycleLength = calculateCycleLength(loopNodeParam);
Node<T> cycleLengthAdvancedIterator = head;
Node<T> it = head;

View File

@ -3,24 +3,13 @@ package com.baeldung.algorithms.linkedlist;
public class CycleRemovalWithoutCountingLoopNodes {
public static <T> boolean detectAndRemoveCycle(Node<T> head) {
if (head == null) {
return false;
CycleDetectionResult<T> result = CycleDetectionByFastAndSlowIterators.detectCycle(head);
if (result.cycleExists) {
removeCycle(result.node, head);
}
Node<T> slow = head;
Node<T> fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
removeCycle(slow, head);
return true;
}
}
return false;
return result.cycleExists;
}
private static <T> void removeCycle(Node<T> meetingPointParam, Node<T> head) {

View File

@ -2,19 +2,22 @@ package com.baeldung.algorithms.linkedlist;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(value = Parameterized.class)
public class CycleDetectionBruteForceTest extends CycleDetectionTestBase {
boolean cycleExists;
Node<Integer> head;
@Test
public void givenNormalList_dontDetectLoop() {
Node<Integer> root = createList();
Assert.assertFalse(CycleDetectionBruteForce.detectCycle(root));
public CycleDetectionBruteForceTest(Node<Integer> head, boolean cycleExists) {
super();
this.cycleExists = cycleExists;
this.head = head;
}
@Test
public void givenCyclicList_detectLoop() {
Node<Integer> root = createList();
createLoop(root);
Assert.assertTrue(CycleDetectionBruteForce.detectCycle(root));
public void givenList_detectLoop() {
Assert.assertEquals(cycleExists, CycleDetectionBruteForce.detectCycle(head).cycleExists);
}
}

View File

@ -2,19 +2,22 @@ package com.baeldung.algorithms.linkedlist;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(value = Parameterized.class)
public class CycleDetectionByFastAndSlowIteratorsTest extends CycleDetectionTestBase {
boolean cycleExists;
Node<Integer> head;
@Test
public void givenNormalList_dontDetectLoop() {
Node<Integer> root = createList();
Assert.assertFalse(CycleDetectionByFastAndSlowIterators.detectCycle(root));
public CycleDetectionByFastAndSlowIteratorsTest(Node<Integer> head, boolean cycleExists) {
super();
this.cycleExists = cycleExists;
this.head = head;
}
@Test
public void givenCyclicList_detectLoop() {
Node<Integer> root = createList();
createLoop(root);
Assert.assertTrue(CycleDetectionByFastAndSlowIterators.detectCycle(root));
public void givenList_detectLoop() {
Assert.assertEquals(cycleExists, CycleDetectionByFastAndSlowIterators.detectCycle(head).cycleExists);
}
}

View File

@ -2,19 +2,22 @@ package com.baeldung.algorithms.linkedlist;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(value = Parameterized.class)
public class CycleDetectionByHashingTest extends CycleDetectionTestBase {
boolean cycleExists;
Node<Integer> head;
@Test
public void givenNormalList_dontDetectLoop() {
Node<Integer> root = createList();
Assert.assertFalse(CycleDetectionByHashing.detectCycle(root));
public CycleDetectionByHashingTest(Node<Integer> head, boolean cycleExists) {
super();
this.cycleExists = cycleExists;
this.head = head;
}
@Test
public void givenCyclicList_detectLoop() {
Node<Integer> root = createList();
createLoop(root);
Assert.assertTrue(CycleDetectionByHashing.detectCycle(root));
public void givenList_detectLoop() {
Assert.assertEquals(cycleExists, CycleDetectionByHashing.detectCycle(head).cycleExists);
}
}

View File

@ -1,7 +1,22 @@
package com.baeldung.algorithms.linkedlist;
import java.util.Arrays;
import java.util.Collection;
import org.junit.runners.Parameterized.Parameters;
public class CycleDetectionTestBase {
@Parameters
public static Collection<Object[]> getLists() {
return Arrays.asList(new Object[][] {
{ createList(), false },
{ createListWithLoop(), true },
{ createListWithFullCycle(), true },
{ createListWithSingleNodeInCycle(), true }
});
}
public static Node<Integer> createList() {
Node<Integer> root = Node.createNewNode(10, null);
@ -13,6 +28,26 @@ public class CycleDetectionTestBase {
return root;
}
public static Node<Integer> createListWithLoop() {
Node<Integer> node = createList();
createLoop(node);
return node;
}
public static Node<Integer> createListWithFullCycle() {
Node<Integer> head = createList();
Node<Integer> tail = Node.getTail(head);
tail.next = head;
return head;
}
public static Node<Integer> createListWithSingleNodeInCycle() {
Node<Integer> head = createList();
Node<Integer> tail = Node.getTail(head);
tail.next = tail;
return head;
}
public static void createLoop(Node<Integer> root) {
Node<Integer> tail = Node.getTail(root);

View File

@ -2,20 +2,23 @@ package com.baeldung.algorithms.linkedlist;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(value = Parameterized.class)
public class CycleRemovalBruteForceTest extends CycleDetectionTestBase {
boolean cycleExists;
Node<Integer> head;
@Test
public void givenNormalList_dontDetectLoop() {
Node<Integer> root = createList();
Assert.assertFalse(CycleRemovalBruteForce.detectAndRemoveCycle(root));
public CycleRemovalBruteForceTest(Node<Integer> head, boolean cycleExists) {
super();
this.cycleExists = cycleExists;
this.head = head;
}
@Test
public void givenCyclicList_detectAndRemoveLoop() {
Node<Integer> root = createList();
createLoop(root);
Assert.assertTrue(CycleRemovalBruteForce.detectAndRemoveCycle(root));
Assert.assertFalse(CycleDetectionByFastAndSlowIterators.detectCycle(root));
public void givenList_ifLoopExists_thenDetectAndRemoveLoop() {
Assert.assertEquals(cycleExists, CycleRemovalBruteForce.detectAndRemoveCycle(head));
Assert.assertFalse(CycleDetectionByFastAndSlowIterators.detectCycle(head).cycleExists);
}
}

View File

@ -2,20 +2,23 @@ package com.baeldung.algorithms.linkedlist;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(value = Parameterized.class)
public class CycleRemovalByCountingLoopNodesTest extends CycleDetectionTestBase {
boolean cycleExists;
Node<Integer> head;
@Test
public void givenNormalList_dontDetectLoop() {
Node<Integer> root = createList();
Assert.assertFalse(CycleRemovalByCountingLoopNodes.detectAndRemoveCycle(root));
public CycleRemovalByCountingLoopNodesTest(Node<Integer> head, boolean cycleExists) {
super();
this.cycleExists = cycleExists;
this.head = head;
}
@Test
public void givenCyclicList_detectAndRemoveLoop() {
Node<Integer> root = createList();
createLoop(root);
Assert.assertTrue(CycleRemovalByCountingLoopNodes.detectAndRemoveCycle(root));
Assert.assertFalse(CycleDetectionByFastAndSlowIterators.detectCycle(root));
public void givenList_ifLoopExists_thenDetectAndRemoveLoop() {
Assert.assertEquals(cycleExists, CycleRemovalByCountingLoopNodes.detectAndRemoveCycle(head));
Assert.assertFalse(CycleDetectionByFastAndSlowIterators.detectCycle(head).cycleExists);
}
}

View File

@ -2,20 +2,23 @@ package com.baeldung.algorithms.linkedlist;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(value = Parameterized.class)
public class CycleRemovalWithoutCountingLoopNodesTest extends CycleDetectionTestBase {
boolean cycleExists;
Node<Integer> head;
@Test
public void givenNormalList_dontDetectLoop() {
Node<Integer> root = createList();
Assert.assertFalse(CycleRemovalWithoutCountingLoopNodes.detectAndRemoveCycle(root));
public CycleRemovalWithoutCountingLoopNodesTest(Node<Integer> head, boolean cycleExists) {
super();
this.cycleExists = cycleExists;
this.head = head;
}
@Test
public void givenCyclicList_detectAndRemoveLoop() {
Node<Integer> root = createList();
createLoop(root);
Assert.assertTrue(CycleRemovalWithoutCountingLoopNodes.detectAndRemoveCycle(root));
Assert.assertFalse(CycleDetectionByFastAndSlowIterators.detectCycle(root));
public void givenList_ifLoopExists_thenDetectAndRemoveLoop() {
Assert.assertEquals(cycleExists, CycleRemovalWithoutCountingLoopNodes.detectAndRemoveCycle(head));
Assert.assertFalse(CycleDetectionByFastAndSlowIterators.detectCycle(head).cycleExists);
}
}