Chapter 22 Implementing lists: linked implementations
This chapter discusses n Linked lists u lists built through object references. u dynamic lists. u linked implementations of structures more complex than a simple sequence.
A linked list implementation n If the list is not empty u there is a first element u there is a last element u every element (except the last) has a successor u every element (except the first) has a predecessor
A linked list implementation (cont.) n An array structure is built by creating a reference to the collection of list elements which are stored sequentially in memory. n A linked list is built by creating a reference to a node of the list that, in turn contains a reference to another node of the list, etc.
LinkedList class public abstract class LinkedList implements Cloneable { … private class Node { /** * Create a Node containing specified element. * / public Node (Object element) { this.element = element; this.next = null; } Object element; Node next; }
LinkedList class (cont.) public abstract class LinkedList implements Cloneable { /** * Create a Node containing specified element. * / protected LinkedList () { size = 0; first = null; } … private int size; private Node first; }
LinkedList class (cont.)
The component next of the last Node always has a value of null. For the empty list, the LinkedList component first is null.
LinkedList methods: get public Object get (int i) { Node p = first; int pos = 0; while (pos < i) { p = p.next; pos = pos + 1; } return p.element; }
LinkedList methods: get (cont.) The variable p is initialized with a reference to the 0-th Node of the list.
LinkedList methods: get (cont.) Each iteration of the loop assigns p a reference to the next element of the list and increments pos.
LinkedList methods: get (Cont). Loop invariant: p references the Node containing the element with index pos. n At loop termination pos == i p references the Node we are looking for.
LinkedList methods: append n First, find the last element of list
LinkedList methods: append (cont.) n Create a new Node containing the element to be appended n Set the old last Nodes next component to reference the new Node. public void append (Object obj) { if (this.isEmpty()) first = new Node(obj); else { Node p = first; while (p.next != null); p = p.next; p.next = new Node(obj); } size = size + 1; }
LinkedList methods: remove n First, find the Node in front of the one we want to delete.
LinkedList methods: remove (cont.) Take next of the Node before the one to be deleted and reference it the same as the next field of the Node to be deleted.
LinkedList methods: remove (cont.) Since p.next is the Node to be deleted, p.next cannot be null. n Removing the first element of the list is a special case.
LinkedList methods: remove (cont.) public void remove (int i) { if (i == 0) { first = first.next; else { Node p = first; int pos = 0; while (pos < i-1) { p = p.next; pos = pos + 1; } p.next = p.next.next; } size = size - 1; }
LinkedList methods: add
LinkedList methods: add (cont.) public add (int i, Object obj) { Node newElement = new Node(obj); if (i == 0) { newElement.next = first; first = newElement; } else { Node p = first; int pos = 0; while (pos < i-1) { p = p.next; pos = pos + 1; } newElement.next = p.next; p.next = newElement; } size = size + 1; }
LinkedList methods: get, append, remove, and add all require linear time on average. n Deleting the first element and inserting a new first element, are constant time operations. n We must be particularly careful of boundary cases: cases involving u the empty list u a list with one element u the first or last element of a list These may well require explicit handling.
LinkedList variations A simple change will make append a constant time operation. We keep a reference to both the first and last elements in the list.
LinkedList variations (cont.) public append (Object obj) { Node newElement = new Node(obj); if (this.isEmpty()) first = newElement; else last.next = newElement; last = newElement; size = size + 1; }
LinkedList variations (cont.) remove must now check explicitly for the case in which the last element is deleted. public void remove (int i) { if (size == 1) { // remove the only element first = null; last = null; } else if (i == 0) { // remove the first element first = first.next; }
LinkedList variations (cont.) else { Node p = first; int pos = 0; while (pos < i-1) { p = p.next pos = pos + 1; } p.next = p.next.next; if (i == size-1) //last element removed last = p; } size = size - 1; }
Header nodes n One way to eliminate special cases is to employ a header node. It contains no element, but is always present at the front of the list. i.e. it is referenced by first.
Header nodes (cont.) private class Header extends Node { public Header () { this.element = null; this.next = null; } The LinkedList constructor creates the header. protected LinkedList () { size = 0; first = new Header(); last = first; }
Header nodes (cont.) n The method append, need not check explicitly for the empty list. public append (Object obj) { Node newElement = new Node(obj); last.next = newElement; last = newElement; size = size + 1; }
Circular lists n In a circular list, the last node references the first. n A circular list may or may not have a header. n We can traverse the entire list starting from any node. n Care must be taken to avoid infinite iterations or recursions.
Doubly-linked lists n Each node contains references to the preceding as well as to the following node.
Doubly-linked lists (cont.) n Three components: u the list elements u references to its two neighboring nodes. public abstract class DoublyLinkedList implements Cloneable {… private int size; private Node header; private class Node { public Node (Object element) { this.element = element; this.next = null; this.previous = null; } Object element; Node next; Node previous; }
Doubly-linked lists (cont.) protected DoublyLinkedList () { size = 0; header = new Header(); header.next = header; header.previous = header; }
Doubly-linked lists (cont.) n Operations are a bit more complicated since we have two references in each node, but the combination of a circular structure and a header eliminates the need for handling most boundary cases explicitly.
DoublyLinkedList : append n Set previous of the new node to reference the old last node. n Set next of the new node to reference the header. n Set previous of the header to reference the new node. n Set next component of the old last node to reference the new node.
DoublyLinkedList : append (cont.) public void append (Object obj) { Node newElement = new Node(obj); Node last = header.previous; newElement.next = header; newElement.previous = last; last.next = newElement; header.previous = newElement; size = size + 1; }
Linked list limitations n Accessing elements by index is a linear time operation. The get operation is linear. Therefore, any operation using the get method will be slower than in an array-based implementation. public boolean contains (List list, Object obj) { int n = list.size(); int i = 0; while (i < n && !obj.equals(list.get(i)) i = i + 1; } return i < n; }
Linked list limitations (cont.) This method can be implemented without get. public boolean contains (LinkedList list, Object obj) { Node p = list.first; while (p != null && !obj.equals(p.element)) p = p.next; } return p != null; }
Dynamic storage allocation n automatic allocation: memory space for automatic variables is allocated when the method is invoked, and reclaimed (deallocated) when the method completes. n Memory space for array elements is allocated when the array is created. n For a linked implementation, space required for a node is allocated when the node is created.
Garbage collection n Dynamically allocated space that can no longer be accessed is termed garbage. n If we create an object and then loose all references to the object, the memory space occupied by the object becomes garbage.
Garbage collection (cont.) n The Java run-time system or interpreter continuously looks for garbage and reclaims the space (garbage collection). n Many programming languages do not include garbage collection as part of their run-time systems. n They require programs to deallocate explicitly dynamically allocated space. n In an object-oriented environment, it is often difficult to know when an object no longer is accessible.
Garbage collection (cont.) n Dangling references are references to space that has been reclaimed mistakenly. n Such references result in errors that often are extremely difficult to track down.
Weve covered n Linked structures u nodes u headers n Doubly-linked lists n Circular lists n Time complexities n Dynamic storage allocation u garbage collection
Glossary
Glossary (cont.)