From d8398caaa2d84a50e2ccbe2a4a0392742b9fd652 Mon Sep 17 00:00:00 2001 From: deep20jain Date: Mon, 4 Sep 2017 01:50:25 +0530 Subject: [PATCH] BAEL-1123 - Testing Linked List for Cycles - deep20jain@gmail.com (#2525) * Adding node and cycle detection by hashing * Adding implementation for all algorithms for cycle detection and removal * Applying formatting rules --- .../linkedlist/CycleDetectionBruteForce.java | 38 ++++++++++++ .../CycleDetectionByFastAndSlowIterators.java | 25 ++++++++ .../linkedlist/CycleDetectionByHashing.java | 27 ++++++++ .../linkedlist/CycleRemovalBruteForce.java | 61 +++++++++++++++++++ .../CycleRemovalByCountingLoopNodes.java | 55 +++++++++++++++++ .../CycleRemovalWithoutCountingLoopNodes.java | 38 ++++++++++++ .../baeldung/algorithms/linkedlist/Node.java | 38 ++++++++++++ .../CycleDetectionBruteForceTest.java | 20 ++++++ ...leDetectionByFastAndSlowIteratorsTest.java | 20 ++++++ .../CycleDetectionByHashingTest.java | 20 ++++++ .../linkedlist/CycleDetectionTestBase.java | 27 ++++++++ .../CycleRemovalBruteForceTest.java | 21 +++++++ .../CycleRemovalByCountingLoopNodesTest.java | 21 +++++++ ...leRemovalWithoutCountingLoopNodesTest.java | 21 +++++++ 14 files changed, 432 insertions(+) create mode 100644 algorithms/src/main/java/com/baeldung/algorithms/linkedlist/CycleDetectionBruteForce.java create mode 100644 algorithms/src/main/java/com/baeldung/algorithms/linkedlist/CycleDetectionByFastAndSlowIterators.java create mode 100644 algorithms/src/main/java/com/baeldung/algorithms/linkedlist/CycleDetectionByHashing.java create mode 100644 algorithms/src/main/java/com/baeldung/algorithms/linkedlist/CycleRemovalBruteForce.java create mode 100644 algorithms/src/main/java/com/baeldung/algorithms/linkedlist/CycleRemovalByCountingLoopNodes.java create mode 100644 algorithms/src/main/java/com/baeldung/algorithms/linkedlist/CycleRemovalWithoutCountingLoopNodes.java create mode 100644 algorithms/src/main/java/com/baeldung/algorithms/linkedlist/Node.java create mode 100644 algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleDetectionBruteForceTest.java create mode 100644 algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleDetectionByFastAndSlowIteratorsTest.java create mode 100644 algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleDetectionByHashingTest.java create mode 100644 algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleDetectionTestBase.java create mode 100644 algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleRemovalBruteForceTest.java create mode 100644 algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleRemovalByCountingLoopNodesTest.java create mode 100644 algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleRemovalWithoutCountingLoopNodesTest.java diff --git a/algorithms/src/main/java/com/baeldung/algorithms/linkedlist/CycleDetectionBruteForce.java b/algorithms/src/main/java/com/baeldung/algorithms/linkedlist/CycleDetectionBruteForce.java new file mode 100644 index 0000000000..532be70480 --- /dev/null +++ b/algorithms/src/main/java/com/baeldung/algorithms/linkedlist/CycleDetectionBruteForce.java @@ -0,0 +1,38 @@ +package com.baeldung.algorithms.linkedlist; + +public class CycleDetectionBruteForce { + + public static boolean detectCycle(Node head) { + if (head == null) { + return false; + } + + Node it1 = head; + int nodesTraversedByOuter = 0; + while (it1 != null && it1.next != null) { + it1 = it1.next; + nodesTraversedByOuter++; + + int x = nodesTraversedByOuter; + Node it2 = head; + int noOfTimesCurrentNodeVisited = 0; + + while (x > 0) { + it2 = it2.next; + + if (it2 == it1) { + noOfTimesCurrentNodeVisited++; + } + + if (noOfTimesCurrentNodeVisited == 2) { + return true; + } + + x--; + } + } + + return false; + } + +} diff --git a/algorithms/src/main/java/com/baeldung/algorithms/linkedlist/CycleDetectionByFastAndSlowIterators.java b/algorithms/src/main/java/com/baeldung/algorithms/linkedlist/CycleDetectionByFastAndSlowIterators.java new file mode 100644 index 0000000000..7c8e038cb2 --- /dev/null +++ b/algorithms/src/main/java/com/baeldung/algorithms/linkedlist/CycleDetectionByFastAndSlowIterators.java @@ -0,0 +1,25 @@ +package com.baeldung.algorithms.linkedlist; + +public class CycleDetectionByFastAndSlowIterators { + + public static boolean detectCycle(Node head) { + if (head == null) { + return false; + } + + Node slow = head; + Node fast = head; + + while (fast != null && fast.next != null) { + slow = slow.next; + fast = fast.next.next; + + if (slow == fast) { + return true; + } + } + + return false; + } + +} diff --git a/algorithms/src/main/java/com/baeldung/algorithms/linkedlist/CycleDetectionByHashing.java b/algorithms/src/main/java/com/baeldung/algorithms/linkedlist/CycleDetectionByHashing.java new file mode 100644 index 0000000000..0b3f91b97c --- /dev/null +++ b/algorithms/src/main/java/com/baeldung/algorithms/linkedlist/CycleDetectionByHashing.java @@ -0,0 +1,27 @@ +package com.baeldung.algorithms.linkedlist; + +import java.util.HashSet; +import java.util.Set; + +public class CycleDetectionByHashing { + + public static boolean detectCycle(Node head) { + if (head == null) { + return false; + } + + Set> set = new HashSet<>(); + Node node = head; + + while (node != null) { + if (set.contains(node)) { + return true; + } + set.add(node); + node = node.next; + } + + return false; + } + +} diff --git a/algorithms/src/main/java/com/baeldung/algorithms/linkedlist/CycleRemovalBruteForce.java b/algorithms/src/main/java/com/baeldung/algorithms/linkedlist/CycleRemovalBruteForce.java new file mode 100644 index 0000000000..cacfd44121 --- /dev/null +++ b/algorithms/src/main/java/com/baeldung/algorithms/linkedlist/CycleRemovalBruteForce.java @@ -0,0 +1,61 @@ +package com.baeldung.algorithms.linkedlist; + +public class CycleRemovalBruteForce { + + public static boolean detectAndRemoveCycle(Node head) { + if (head == null) { + return false; + } + + Node slow = head; + Node 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; + } + + private static void removeCycle(Node loopNodeParam, Node head) { + Node it = head; + + while (it != null) { + if (isNodeReachableFromLoopNode(it, loopNodeParam)) { + Node loopStart = it; + findEndNodeAndBreakCycle(loopStart); + break; + } + it = it.next; + } + } + + private static boolean isNodeReachableFromLoopNode(Node it, Node loopNodeParam) { + Node loopNode = loopNodeParam; + + while (loopNode.next != loopNodeParam) { + if (it == loopNode) { + return true; + } + loopNode = loopNode.next; + } + + return false; + } + + private static void findEndNodeAndBreakCycle(Node loopStartParam) { + Node loopStart = loopStartParam; + + while (loopStart.next != loopStartParam) { + loopStart = loopStart.next; + } + + loopStart.next = null; + } +} diff --git a/algorithms/src/main/java/com/baeldung/algorithms/linkedlist/CycleRemovalByCountingLoopNodes.java b/algorithms/src/main/java/com/baeldung/algorithms/linkedlist/CycleRemovalByCountingLoopNodes.java new file mode 100644 index 0000000000..afc525a8d3 --- /dev/null +++ b/algorithms/src/main/java/com/baeldung/algorithms/linkedlist/CycleRemovalByCountingLoopNodes.java @@ -0,0 +1,55 @@ +package com.baeldung.algorithms.linkedlist; + +public class CycleRemovalByCountingLoopNodes { + + public static boolean detectAndRemoveCycle(Node head) { + if (head == null) { + return false; + } + + Node slow = head; + Node 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; + } + + private static void removeCycle(Node head, int cycleLength) { + Node cycleLengthAdvancedIterator = head; + Node it = head; + + for (int i = 0; i < cycleLength; i++) { + cycleLengthAdvancedIterator = cycleLengthAdvancedIterator.next; + } + + while (it.next != cycleLengthAdvancedIterator.next) { + it = it.next; + cycleLengthAdvancedIterator = cycleLengthAdvancedIterator.next; + } + + cycleLengthAdvancedIterator.next = null; + } + + private static int calculateCycleLength(Node loopNodeParam) { + Node loopNode = loopNodeParam; + int length = 1; + + while (loopNode.next != loopNodeParam) { + length++; + loopNode = loopNode.next; + } + + return length; + } + +} diff --git a/algorithms/src/main/java/com/baeldung/algorithms/linkedlist/CycleRemovalWithoutCountingLoopNodes.java b/algorithms/src/main/java/com/baeldung/algorithms/linkedlist/CycleRemovalWithoutCountingLoopNodes.java new file mode 100644 index 0000000000..d5b8b1cc8f --- /dev/null +++ b/algorithms/src/main/java/com/baeldung/algorithms/linkedlist/CycleRemovalWithoutCountingLoopNodes.java @@ -0,0 +1,38 @@ +package com.baeldung.algorithms.linkedlist; + +public class CycleRemovalWithoutCountingLoopNodes { + + public static boolean detectAndRemoveCycle(Node head) { + if (head == null) { + return false; + } + + Node slow = head; + Node 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; + } + + private static void removeCycle(Node meetingPointParam, Node head) { + Node loopNode = meetingPointParam; + Node it = head; + + while (loopNode.next != it.next) { + it = it.next; + loopNode = loopNode.next; + } + + loopNode.next = null; + } + +} diff --git a/algorithms/src/main/java/com/baeldung/algorithms/linkedlist/Node.java b/algorithms/src/main/java/com/baeldung/algorithms/linkedlist/Node.java new file mode 100644 index 0000000000..4add22c77d --- /dev/null +++ b/algorithms/src/main/java/com/baeldung/algorithms/linkedlist/Node.java @@ -0,0 +1,38 @@ +package com.baeldung.algorithms.linkedlist; + +public class Node { + T data; + Node next; + + public static Node createNewNode(T data, Node next) { + Node node = new Node(); + node.data = data; + node.next = next; + return node; + } + + public static void traverseList(Node root) { + if (root == null) { + return; + } + + Node node = root; + while (node != null) { + System.out.println(node.data); + node = node.next; + } + } + + public static Node getTail(Node root) { + if (root == null) { + return null; + } + + Node node = root; + while (node.next != null) { + node = node.next; + } + return node; + } + +} \ No newline at end of file diff --git a/algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleDetectionBruteForceTest.java b/algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleDetectionBruteForceTest.java new file mode 100644 index 0000000000..09250a8a66 --- /dev/null +++ b/algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleDetectionBruteForceTest.java @@ -0,0 +1,20 @@ +package com.baeldung.algorithms.linkedlist; + +import org.junit.Assert; +import org.junit.Test; + +public class CycleDetectionBruteForceTest extends CycleDetectionTestBase { + + @Test + public void givenNormalList_dontDetectLoop() { + Node root = createList(); + Assert.assertFalse(CycleDetectionBruteForce.detectCycle(root)); + } + + @Test + public void givenCyclicList_detectLoop() { + Node root = createList(); + createLoop(root); + Assert.assertTrue(CycleDetectionBruteForce.detectCycle(root)); + } +} diff --git a/algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleDetectionByFastAndSlowIteratorsTest.java b/algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleDetectionByFastAndSlowIteratorsTest.java new file mode 100644 index 0000000000..cfe00aaefc --- /dev/null +++ b/algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleDetectionByFastAndSlowIteratorsTest.java @@ -0,0 +1,20 @@ +package com.baeldung.algorithms.linkedlist; + +import org.junit.Assert; +import org.junit.Test; + +public class CycleDetectionByFastAndSlowIteratorsTest extends CycleDetectionTestBase { + + @Test + public void givenNormalList_dontDetectLoop() { + Node root = createList(); + Assert.assertFalse(CycleDetectionByFastAndSlowIterators.detectCycle(root)); + } + + @Test + public void givenCyclicList_detectLoop() { + Node root = createList(); + createLoop(root); + Assert.assertTrue(CycleDetectionByFastAndSlowIterators.detectCycle(root)); + } +} diff --git a/algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleDetectionByHashingTest.java b/algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleDetectionByHashingTest.java new file mode 100644 index 0000000000..af9a8239ef --- /dev/null +++ b/algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleDetectionByHashingTest.java @@ -0,0 +1,20 @@ +package com.baeldung.algorithms.linkedlist; + +import org.junit.Assert; +import org.junit.Test; + +public class CycleDetectionByHashingTest extends CycleDetectionTestBase { + + @Test + public void givenNormalList_dontDetectLoop() { + Node root = createList(); + Assert.assertFalse(CycleDetectionByHashing.detectCycle(root)); + } + + @Test + public void givenCyclicList_detectLoop() { + Node root = createList(); + createLoop(root); + Assert.assertTrue(CycleDetectionByHashing.detectCycle(root)); + } +} diff --git a/algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleDetectionTestBase.java b/algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleDetectionTestBase.java new file mode 100644 index 0000000000..2abf5de1d4 --- /dev/null +++ b/algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleDetectionTestBase.java @@ -0,0 +1,27 @@ +package com.baeldung.algorithms.linkedlist; + +public class CycleDetectionTestBase { + + public static Node createList() { + Node root = Node.createNewNode(10, null); + + for (int i = 9; i >= 1; --i) { + Node current = Node.createNewNode(i, root); + root = current; + } + + return root; + } + + public static void createLoop(Node root) { + Node tail = Node.getTail(root); + + Node middle = root; + for (int i = 1; i <= 4; i++) { + middle = middle.next; + } + + tail.next = middle; + } + +} diff --git a/algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleRemovalBruteForceTest.java b/algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleRemovalBruteForceTest.java new file mode 100644 index 0000000000..b32f20fb75 --- /dev/null +++ b/algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleRemovalBruteForceTest.java @@ -0,0 +1,21 @@ +package com.baeldung.algorithms.linkedlist; + +import org.junit.Assert; +import org.junit.Test; + +public class CycleRemovalBruteForceTest extends CycleDetectionTestBase { + + @Test + public void givenNormalList_dontDetectLoop() { + Node root = createList(); + Assert.assertFalse(CycleRemovalBruteForce.detectAndRemoveCycle(root)); + } + + @Test + public void givenCyclicList_detectAndRemoveLoop() { + Node root = createList(); + createLoop(root); + Assert.assertTrue(CycleRemovalBruteForce.detectAndRemoveCycle(root)); + Assert.assertFalse(CycleDetectionByFastAndSlowIterators.detectCycle(root)); + } +} diff --git a/algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleRemovalByCountingLoopNodesTest.java b/algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleRemovalByCountingLoopNodesTest.java new file mode 100644 index 0000000000..dd938afae1 --- /dev/null +++ b/algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleRemovalByCountingLoopNodesTest.java @@ -0,0 +1,21 @@ +package com.baeldung.algorithms.linkedlist; + +import org.junit.Assert; +import org.junit.Test; + +public class CycleRemovalByCountingLoopNodesTest extends CycleDetectionTestBase { + + @Test + public void givenNormalList_dontDetectLoop() { + Node root = createList(); + Assert.assertFalse(CycleRemovalByCountingLoopNodes.detectAndRemoveCycle(root)); + } + + @Test + public void givenCyclicList_detectAndRemoveLoop() { + Node root = createList(); + createLoop(root); + Assert.assertTrue(CycleRemovalByCountingLoopNodes.detectAndRemoveCycle(root)); + Assert.assertFalse(CycleDetectionByFastAndSlowIterators.detectCycle(root)); + } +} diff --git a/algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleRemovalWithoutCountingLoopNodesTest.java b/algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleRemovalWithoutCountingLoopNodesTest.java new file mode 100644 index 0000000000..56a08a5ee4 --- /dev/null +++ b/algorithms/src/test/java/com/baeldung/algorithms/linkedlist/CycleRemovalWithoutCountingLoopNodesTest.java @@ -0,0 +1,21 @@ +package com.baeldung.algorithms.linkedlist; + +import org.junit.Assert; +import org.junit.Test; + +public class CycleRemovalWithoutCountingLoopNodesTest extends CycleDetectionTestBase { + + @Test + public void givenNormalList_dontDetectLoop() { + Node root = createList(); + Assert.assertFalse(CycleRemovalWithoutCountingLoopNodes.detectAndRemoveCycle(root)); + } + + @Test + public void givenCyclicList_detectAndRemoveLoop() { + Node root = createList(); + createLoop(root); + Assert.assertTrue(CycleRemovalWithoutCountingLoopNodes.detectAndRemoveCycle(root)); + Assert.assertFalse(CycleDetectionByFastAndSlowIterators.detectCycle(root)); + } +} \ No newline at end of file