diff --git a/src/main/java/com/thealgorithms/datastructures/lists/SelfOrganizingLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/SelfOrganizingLinkedList.java new file mode 100644 index 000000000000..d2e3c636bdd8 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/lists/SelfOrganizingLinkedList.java @@ -0,0 +1,98 @@ +package com.thealgorithms.datastructures.lists; + +import java.util.Objects; + +/** + * Node structure for the generic linked list. + * + * @param the type of element held in this node + */ +class LinkedList { + E value; + LinkedList next; + + LinkedList(E value) { + this.value = value; + this.next = null; + } +} + +/** + * A Self-Organizing Linked List implementation using the Move-To-Front (MTF) strategy. + * When an element is searched, it is automatically moved to the head of the list + * to optimize subsequent lookups. + * + * @param the type of elements held in this list + */ +public class SelfOrganizingLinkedList { + private LinkedList head; + private int size; + + public SelfOrganizingLinkedList() { + this.size = 0; + this.head = null; + } + + /** Inserts a new value at the end of the list. */ + public void insert(E value) { + LinkedList newNode = new LinkedList<>(value); + if (head == null) { + head = newNode; + } else { + LinkedList temp = head; + while (temp.next != null) { + temp = temp.next; + } + temp.next = newNode; + } + size++; + } + + /** + * Searches for a value in the list. + * If found, moves the node to the front (head) of the list. + * + * @param key the value to search for + * @return true if the element is present, false otherwise + */ + public boolean search(E key) { + if (head == null) { + return false; + } + if (Objects.equals(head.value, key)) { + return true; + } + + LinkedList prev = head; + LinkedList curr = head.next; + + while (curr != null && !Objects.equals(curr.value, key)) { + prev = curr; + curr = curr.next; + } + + if (curr == null) { + return false; + } + + prev.next = curr.next; + curr.next = head; + head = curr; + return true; + } + + /** Gets the current head of the list. */ + public E getHeadValue() { + return head != null ? head.value : null; + } + + /** Returns the size of the list. */ + public int getSize() { + return size; + } + + /** Returns true if the list contains no elements. */ + public boolean isEmpty() { + return head == null; + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/lists/SelfOrganizingLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/SelfOrganizingLinkedListTest.java new file mode 100644 index 000000000000..c3a0b5354718 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/lists/SelfOrganizingLinkedListTest.java @@ -0,0 +1,85 @@ +package com.thealgorithms.datastructures.lists; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class SelfOrganizingLinkedListTest { + private SelfOrganizingLinkedList list; + + @BeforeEach + void setUp() { + list = new SelfOrganizingLinkedList<>(); + } + + @Test + void testSearchOnEmptyList() { + assertFalse(list.search(10)); + assertNull(list.getHeadValue()); + } + + @Test + void testSearchElementAtHeadValueDoesNotChangeStructure() { + list.insert(10); + list.insert(20); + list.insert(30); + + assertTrue(list.search(10)); + assertEquals(10, list.getHeadValue()); + } + + @Test + void testMoveMiddleElementToFront() { + list.insert(10); + list.insert(20); + list.insert(30); + list.insert(40); + + // Search middle element '30' + assertTrue(list.search(30)); + + // '30' should now be the new head + assertEquals(30, list.getHeadValue()); + } + + @Test + void testMoveLastElementToFront() { + list.insert(10); + list.insert(20); + list.insert(30); + + // Search last element '30' + assertTrue(list.search(30)); + + assertEquals(30, list.getHeadValue()); + } + + @Test + void testSearchNonExistentElement() { + list.insert(10); + list.insert(20); + + assertFalse(list.search(99)); + assertEquals(10, list.getHeadValue()); // Head remains unchanged + } + + @Test + void testMultipleSearchesSequentialMoveToFront() { + list.insert(1); + list.insert(2); + list.insert(3); + + list.search(2); // Head becomes 2 + assertEquals(2, list.getHeadValue()); + + list.search(3); // Head becomes 3 + assertEquals(3, list.getHeadValue()); + + list.search(1); // Head becomes 1 + assertEquals(1, list.getHeadValue()); + } +}