LinkedLists

By Jeong Hyun Lim for CMSC132


Introduction

A linked list is a data structure where one object refers to the next one in a sequence by storing its address. Each object is referred to as a node. In addition to a reference to the next object in the sequence, each object carries some data (e.g., integer value, reference to string, etc.). The following diagram illustrates a typical linked lists:

The following class defines a node of a linked list where the data is an integer.

    public class Node {
        int data;
        Node next;  // reference to next object in the sequence
    }
  		    

The following class defines a node of a linked list where data is a string reference.

    public class Node {
        String data;
        Node next;  // reference to next object in the sequence
    }
  		    

A reference variable of node type called head has the address of the first node of the list. A empty list is represented by setting the head to null.

A LinkedList Class

The following is a typical LinkedList class definition where the node class has been defined as an inner class. Although we are defining a linked list of strings, the example is representative of any linked list.

    public class LinkedList {
        private class Node {
            private String data;
            private Node next;

            private Node(String data) {
                this.data = data;
                this.next = null;  // unnecessary, but to illustrate initial value
            }
        }

        private Node head;
    }
        

Operations

Recursion

Implementing recursive solutions to linked lists usually requires an auxiliary that will take a head reference as a parameter. For example, printing a list can define as follows:

    public void printList() {
        printList(head);
    }

    public void printList(Node curr) {
        if (curr != null) {
            System.out.println(curr.data); // S1
            printList(curr.next); // S2 // processes the tail
        }
    }
            

Reversing statements S1 and S2 will print the list in reverse order. Another example where a value is returned is represented by the size() method.

    public int size() {
        return size(head);
    }

    public int size(Node curr) {
        if (curr == null) {
            return 0;
        } else {
            return 1 + size(curr.next); // 1 + size of the tail
        }
    }
            

An example where a data structure is created and passed as part of the recursive calls is the following:

    public ArrayList<String> getData() {
        ArrayList<String> answer = new ArrayList<String>();

        getData(head, answer);
        return answer;
    }

    public void getData(Node curr, ArrayList<String> answer) {
        if (curr != null) {
            answer.add(curr.data);
            getData(curr.next, answer);
        }
    }
            

A modification of the list using recursion is represented by the following example:

    /* Sets the head to the result of remove recursive call. */
    public void remove(String target) {
        head = remove(target, head);
    }

    public Node remove(String target, Node curr) {
        /* If current reaches the end (null), target was not found. */
        if (curr == null) {
            return null;
        }

        /* Conditional to check if target is found. */
        if (curr.data.equals(target)) {
            /* remove target node by returning next. */
            return curr.next;
        }
        /* Target is not found, so set next to recursive call. */
        else {
            curr.next = remove(target, curr.next);
        }

        return curr;
    }
            

Miscellaneous

Full Example

    public class MyLinkedList<T extends Comparable<T>> { /* Notice the parameter */
        private class Node {
            private T data;
            private Node next;

            private Node(T data) {
                this.data = data;
                next = null; /* do we really need to do this? */
            }
        }

        /* List head pointer */
        private Node head;

        /* We don't actually need it */
        public MyLinkedList() {
            head = null;
        }

        /* Adding at the front of the list */
        public MyLinkedList<T> add(T data) {
            Node newNode = new Node(data);
            newNode.next = head;
            head = newNode;

            return this;
        }

        public String toString() {
            String result = "\" ";
            Node curr = head;

            while (curr != null) {
                result += curr.data + " ";

                curr = curr.next;
            }

            return result + "\"";
        }

        public boolean find(T target) {
            Node curr = head;

            while (curr != null) {
                if (curr.data.compareTo(target) == 0) {
                    return true;
                }

                curr = curr.next;
            }

            return false;
        }

        public T getLastElement() {
            if (head == null) {
                return null;
            }

            Node curr = head;
            while (curr.next != null) {
                curr = curr.next;
            }

            return curr.data;
        }

        /* No insertion will take place if list empty or targetElement not found */
        public void insertElementAfter(T targetElement, T toInsert) {
            Node curr = head;

            while (curr != null) {
                if (curr.data.compareTo(targetElement) == 0) {
                    Node newNode = new Node(toInsert);
                    newNode.next = curr.next;
                    curr.next = newNode;
                    return; /* what would happen if we don't return? */
                }

                curr = curr.next;
            }
        }

        /* No insertion will take place if list empty or targetElement not found */
        public void insertElementBefore(T targetElement, T toInsert) {
            Node prev = null, curr = head;

            while (curr != null) {
                if (curr.data.compareTo(targetElement) == 0) {
                    Node newNode = new Node(toInsert);
                    if (curr == head) {
                        newNode.next = head;
                        head = newNode;
                    } else {
                        prev.next = newNode;
                        newNode.next = curr;
                    }
                    return;
                } else {
                    prev = curr;
                    curr = curr.next;
                }
            }
        }

        public void delete(T targetElement) {
            Node prev = null, curr = head;

            while (curr != null) {
                if (curr.data.compareTo(targetElement) == 0) {
                    if (curr == head) {
                        head = head.next;
                    } else {
                        prev.next = curr.next;
                    }
                    return;
                } else {
                    prev = curr;
                    curr = curr.next;
                }
            }
        }

        public MyLinkedList<T> getListWithDataInBetween(T start, T end) {
            MyLinkedList<T> newList = new MyLinkedList<T>();

        if (head != null) {
            Node curr = head, last = null;

            while (curr != null) {
                if (curr.data.compareTo(start) >= 0 && curr.data.compareTo(end) <= 0) {
                    Node newNode = new Node(curr.data);
                    if (newList.head == null) {
                        newList.head = newNode;
                    } else {
                        last.next = newNode;
                    }
                    last = newNode;
                }
                curr = curr.next;
            }
        }

        return newList;
        }
    }