Skip to content

Commit c457a83

Browse files
Merge branch 'master' into feature/add-compression-algorithms
2 parents 852e832 + b50d1d0 commit c457a83

File tree

8 files changed

+640
-2
lines changed

8 files changed

+640
-2
lines changed

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
<plugin>
8383
<groupId>org.jacoco</groupId>
8484
<artifactId>jacoco-maven-plugin</artifactId>
85-
<version>0.8.13</version>
85+
<version>0.8.14</version>
8686
<executions>
8787
<execution>
8888
<goals>
@@ -112,7 +112,7 @@
112112
<dependency>
113113
<groupId>com.puppycrawl.tools</groupId>
114114
<artifactId>checkstyle</artifactId>
115-
<version>12.0.0</version>
115+
<version>12.0.1</version>
116116
</dependency>
117117
</dependencies>
118118
</plugin>
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package com.thealgorithms.datastructures.lists;
2+
/**
3+
* Implements an algorithm to flatten a multilevel linked list.
4+
*
5+
* In this specific problem structure, each node has a `next` pointer (to the
6+
* next node at the same level) and a `child` pointer (which points to the head
7+
* of another sorted linked list). The goal is to merge all these lists into a
8+
* single, vertically sorted linked list using the `child` pointer.
9+
*
10+
* The approach is a recursive one that leverages a merge utility, similar to
11+
* the merge step in Merge Sort. It recursively flattens the list starting from
12+
* the rightmost node and merges each node's child list with the already
13+
* flattened list to its right.
14+
* @see <a href="https://www.geeksforgeeks.org/flattening-a-linked-list/">GeeksforGeeks: Flattening a Linked List</a>
15+
*/
16+
public final class FlattenMultilevelLinkedList {
17+
/**
18+
* Private constructor to prevent instantiation of this utility class.
19+
*/
20+
private FlattenMultilevelLinkedList() {
21+
}
22+
/**
23+
* Node represents an element in the multilevel linked list. It contains the
24+
* integer data, a reference to the next node at the same level, and a
25+
* reference to the head of a child list.
26+
*/
27+
static class Node {
28+
int data;
29+
Node next;
30+
Node child;
31+
32+
Node(int data) {
33+
this.data = data;
34+
this.next = null;
35+
this.child = null;
36+
}
37+
}
38+
39+
/**
40+
* Merges two sorted linked lists (connected via the `child` pointer).
41+
* This is a helper function for the main flatten algorithm.
42+
*
43+
* @param a The head of the first sorted list.
44+
* @param b The head of the second sorted list.
45+
* @return The head of the merged sorted list.
46+
*/
47+
private static Node merge(Node a, Node b) {
48+
// If one of the lists is empty, return the other.
49+
if (a == null) {
50+
return b;
51+
}
52+
if (b == null) {
53+
return a;
54+
}
55+
56+
Node result;
57+
58+
// Choose the smaller value as the new head.
59+
if (a.data < b.data) {
60+
result = a;
61+
result.child = merge(a.child, b);
62+
} else {
63+
result = b;
64+
result.child = merge(a, b.child);
65+
}
66+
result.next = null; // Ensure the merged list has no `next` pointers.
67+
return result;
68+
}
69+
70+
/**
71+
* Flattens a multilevel linked list into a single sorted list.
72+
* The flattened list is connected using the `child` pointers.
73+
*
74+
* @param head The head of the top-level list (connected via `next` pointers).
75+
* @return The head of the fully flattened and sorted list.
76+
*/
77+
public static Node flatten(Node head) {
78+
// Base case: if the list is empty or has only one node, it's already flattened.
79+
if (head == null || head.next == null) {
80+
return head;
81+
}
82+
83+
// Recursively flatten the list starting from the next node.
84+
head.next = flatten(head.next);
85+
86+
// Now, merge the current list (head's child list) with the flattened rest of the list.
87+
head = merge(head, head.next);
88+
89+
// Return the head of the fully merged list.
90+
return head;
91+
}
92+
}

src/main/java/com/thealgorithms/datastructures/lists/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,4 @@ The `next` variable points to the next node in the data structure and value stor
3030
6. `MergeKSortedLinkedlist.java` : Merges K sorted linked list with mergesort (mergesort is also the most efficient sorting algorithm for linked list).
3131
7. `RandomNode.java` : Selects a random node from given linked list and diplays it.
3232
8. `SkipList.java` : Data Structure used for storing a sorted list of elements with help of a Linked list hierarchy that connects to subsequences of elements.
33+
9. `TortoiseHareAlgo.java` : Finds the middle element of a linked list using the fast and slow pointer (Tortoise-Hare) algorithm.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.thealgorithms.datastructures.lists;
2+
3+
public class TortoiseHareAlgo<E> {
4+
static final class Node<E> {
5+
Node<E> next;
6+
E value;
7+
8+
private Node(E value, Node<E> next) {
9+
this.value = value;
10+
this.next = next;
11+
}
12+
}
13+
14+
private Node<E> head = null;
15+
16+
public TortoiseHareAlgo() {
17+
head = null;
18+
}
19+
20+
public void append(E value) {
21+
Node<E> newNode = new Node<>(value, null);
22+
if (head == null) {
23+
head = newNode;
24+
return;
25+
}
26+
Node<E> current = head;
27+
while (current.next != null) {
28+
current = current.next;
29+
}
30+
current.next = newNode;
31+
}
32+
33+
public E getMiddle() {
34+
if (head == null) {
35+
return null;
36+
}
37+
38+
Node<E> slow = head;
39+
Node<E> fast = head;
40+
41+
while (fast != null && fast.next != null) {
42+
slow = slow.next;
43+
fast = fast.next.next;
44+
}
45+
46+
return slow.value;
47+
}
48+
49+
@Override
50+
public String toString() {
51+
StringBuilder sb = new StringBuilder("[");
52+
Node<E> current = head;
53+
while (current != null) {
54+
sb.append(current.value);
55+
if (current.next != null) {
56+
sb.append(", ");
57+
}
58+
current = current.next;
59+
}
60+
sb.append("]");
61+
return sb.toString();
62+
}
63+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package com.thealgorithms.searches;
2+
3+
import com.thealgorithms.devutils.searches.SearchAlgorithm;
4+
5+
/**
6+
* Sentinel Linear Search is a variation of linear search that eliminates the
7+
* need to check the array bounds in each iteration by placing the search key
8+
* at the end of the array as a sentinel value.
9+
*
10+
* <p>
11+
* The algorithm works by:
12+
* 1. Storing the last element of the array
13+
* 2. Placing the search key at the last position (sentinel)
14+
* 3. Searching from the beginning without bound checking
15+
* 4. If found before the last position, return the index
16+
* 5. If found at the last position, check if it was originally there
17+
*
18+
* <p>
19+
* Time Complexity:
20+
* - Best case: O(1) - when the element is at the first position
21+
* - Average case: O(n) - when the element is in the middle
22+
* - Worst case: O(n) - when the element is not present
23+
*
24+
* <p>
25+
* Space Complexity: O(1) - only uses constant extra space
26+
*
27+
* <p>
28+
* Advantages over regular linear search:
29+
* - Reduces the number of comparisons by eliminating bound checking
30+
* - Slightly more efficient in practice due to fewer conditional checks
31+
*
32+
* @author TheAlgorithms Contributors
33+
* @see LinearSearch
34+
* @see SearchAlgorithm
35+
*/
36+
public class SentinelLinearSearch implements SearchAlgorithm {
37+
/**
38+
* Performs sentinel linear search on the given array.
39+
*
40+
* @param array the array to search in
41+
* @param key the element to search for
42+
* @param <T> the type of elements in the array, must be Comparable
43+
* @return the index of the first occurrence of the key, or -1 if not found
44+
* @throws IllegalArgumentException if the array is null
45+
*/
46+
@Override
47+
public <T extends Comparable<T>> int find(T[] array, T key) {
48+
if (array == null) {
49+
throw new IllegalArgumentException("Array cannot be null");
50+
}
51+
52+
if (array.length == 0) {
53+
return -1;
54+
}
55+
56+
if (key == null) {
57+
return findNull(array);
58+
}
59+
60+
// Store the last element
61+
T lastElement = array[array.length - 1];
62+
63+
// Place the sentinel (search key) at the end
64+
array[array.length - 1] = key;
65+
66+
int i = 0;
67+
// Search without bound checking since sentinel guarantees we'll find the key
68+
while (array[i].compareTo(key) != 0) {
69+
i++;
70+
}
71+
72+
// Restore the original last element
73+
array[array.length - 1] = lastElement;
74+
75+
// Check if we found the key before the sentinel position
76+
// or if the original last element was the key we were looking for
77+
if (i < array.length - 1 || (lastElement != null && lastElement.compareTo(key) == 0)) {
78+
return i;
79+
}
80+
81+
return -1; // Key not found
82+
}
83+
84+
/**
85+
* Helper method to find null values in the array.
86+
*
87+
* @param array the array to search in
88+
* @param <T> the type of elements in the array
89+
* @return the index of the first null element, or -1 if not found
90+
*/
91+
private <T extends Comparable<T>> int findNull(T[] array) {
92+
for (int i = 0; i < array.length; i++) {
93+
if (array[i] == null) {
94+
return i;
95+
}
96+
}
97+
return -1;
98+
}
99+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package com.thealgorithms.datastructures.lists;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNull;
5+
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
import org.junit.jupiter.api.DisplayName;
9+
import org.junit.jupiter.api.Test;
10+
/**
11+
* Unit tests for the FlattenMultilevelLinkedList class.
12+
* This class tests the flattening logic with various list structures,
13+
* including null lists, simple lists, and complex multilevel lists.
14+
*/
15+
final class FlattenMultilevelLinkedListTest {
16+
17+
// A helper function to convert a flattened list (connected by child pointers)
18+
// into a standard Java List for easy comparison.
19+
private List<Integer> toList(FlattenMultilevelLinkedList.Node head) {
20+
List<Integer> list = new ArrayList<>();
21+
FlattenMultilevelLinkedList.Node current = head;
22+
while (current != null) {
23+
list.add(current.data);
24+
current = current.child;
25+
}
26+
return list;
27+
}
28+
29+
@Test
30+
@DisplayName("Test with a null list")
31+
void testFlattenNullList() {
32+
assertNull(FlattenMultilevelLinkedList.flatten(null));
33+
}
34+
35+
@Test
36+
@DisplayName("Test with a simple, single-level list")
37+
void testFlattenSingleLevelList() {
38+
// Create a simple list: 1 -> 2 -> 3
39+
FlattenMultilevelLinkedList.Node head = new FlattenMultilevelLinkedList.Node(1);
40+
head.next = new FlattenMultilevelLinkedList.Node(2);
41+
head.next.next = new FlattenMultilevelLinkedList.Node(3);
42+
43+
// Flatten the list
44+
FlattenMultilevelLinkedList.Node flattenedHead = FlattenMultilevelLinkedList.flatten(head);
45+
46+
// Expected output: 1 -> 2 -> 3 (vertically)
47+
List<Integer> expected = List.of(1, 2, 3);
48+
assertEquals(expected, toList(flattenedHead));
49+
}
50+
51+
@Test
52+
@DisplayName("Test with a complex multilevel list")
53+
void testFlattenComplexMultilevelList() {
54+
// Create the multilevel structure from the problem description
55+
// 5 -> 10 -> 19 -> 28
56+
// | | | |
57+
// 7 20 22 35
58+
// | | |
59+
// 8 50 40
60+
// | |
61+
// 30 45
62+
FlattenMultilevelLinkedList.Node head = new FlattenMultilevelLinkedList.Node(5);
63+
head.child = new FlattenMultilevelLinkedList.Node(7);
64+
head.child.child = new FlattenMultilevelLinkedList.Node(8);
65+
head.child.child.child = new FlattenMultilevelLinkedList.Node(30);
66+
67+
head.next = new FlattenMultilevelLinkedList.Node(10);
68+
head.next.child = new FlattenMultilevelLinkedList.Node(20);
69+
70+
head.next.next = new FlattenMultilevelLinkedList.Node(19);
71+
head.next.next.child = new FlattenMultilevelLinkedList.Node(22);
72+
head.next.next.child.child = new FlattenMultilevelLinkedList.Node(50);
73+
74+
head.next.next.next = new FlattenMultilevelLinkedList.Node(28);
75+
head.next.next.next.child = new FlattenMultilevelLinkedList.Node(35);
76+
head.next.next.next.child.child = new FlattenMultilevelLinkedList.Node(40);
77+
head.next.next.next.child.child.child = new FlattenMultilevelLinkedList.Node(45);
78+
79+
// Flatten the list
80+
FlattenMultilevelLinkedList.Node flattenedHead = FlattenMultilevelLinkedList.flatten(head);
81+
82+
// Expected sorted output
83+
List<Integer> expected = List.of(5, 7, 8, 10, 19, 20, 22, 28, 30, 35, 40, 45, 50);
84+
assertEquals(expected, toList(flattenedHead));
85+
}
86+
87+
@Test
88+
@DisplayName("Test with some empty child lists")
89+
void testFlattenWithEmptyChildLists() {
90+
// Create a list: 5 -> 10 -> 12
91+
// | |
92+
// 7 11
93+
// |
94+
// 9
95+
FlattenMultilevelLinkedList.Node head = new FlattenMultilevelLinkedList.Node(5);
96+
head.child = new FlattenMultilevelLinkedList.Node(7);
97+
head.child.child = new FlattenMultilevelLinkedList.Node(9);
98+
99+
head.next = new FlattenMultilevelLinkedList.Node(10); // No child list
100+
head.next.child = null;
101+
102+
head.next.next = new FlattenMultilevelLinkedList.Node(12);
103+
head.next.next.child = new FlattenMultilevelLinkedList.Node(16);
104+
105+
// Flatten the list
106+
FlattenMultilevelLinkedList.Node flattenedHead = FlattenMultilevelLinkedList.flatten(head);
107+
108+
// Expected sorted output
109+
List<Integer> expected = List.of(5, 7, 9, 10, 12, 16);
110+
assertEquals(expected, toList(flattenedHead));
111+
}
112+
}

0 commit comments

Comments
 (0)