Chapter 22 Implementing lists: linked implementations

Preview:

Citation preview

Chapter 22Implementing lists: linked implementations

This chapter discusses

Linked lists lists built through object

references. dynamic lists. linked implementations of

structures more complex than a simple sequence.

A linked list implementation If the list is not empty

there is a first element there is a last element every element (except the last)

has a successor every element (except the first)

has a predecessor

A linked list implementation (cont.) An array structure is built by creating a

reference to the collection of list elements which are stored sequentially in memory.

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.)

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:getpublic 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.

At loop termination pos == i p references the Node we are

looking for.

LinkedList methods:append First, find the last element of list

LinkedList methods:append (cont.) Create a new Node containing the element to

be appended Set the old last Node’s 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 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. 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.

Deleting the first element and inserting a new first element, are constant time operations.

We must be particularly careful of “boundary” cases: cases involving the empty list a list with one element 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.nextpos = pos + 1;

} p.next = p.next.next; if (i == size-1) //last element removed

last = p;}size = size - 1;

}

Header nodes

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.)

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 In a circular list, the last node references

the first. A circular list may or may not have a

header. We can traverse the entire list starting

from any node. Care must be taken to avoid infinite

iterations or recursions.

Doubly-linked lists

Each node contains references to the preceding as well as to the following node.

Doubly-linked lists (cont.) Three components:

the list elements 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.) 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 Set previous of the new node to

reference the old last node. Set next of the new node to reference

the header. Set previous of the header to reference

the new node. 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

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 automatic allocation: memory space for

automatic variables is allocated when the method is invoked, and reclaimed (“deallocated”) when the method completes.

Memory space for array elements is allocated when the array is created.

For a linked implementation, space required for a node is allocated when the node is created.

Garbage collection

Dynamically allocated space that can no longer be accessed is termed garbage.

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.) The Java run-time system or interpreter

continuously looks for garbage and reclaims the space (garbage collection).

Many programming languages do not include garbage collection as part of their run-time systems.

They require programs to deallocate explicitly dynamically allocated space.

In an object-oriented environment, it is often difficult to know when an object no longer is accessible.

Garbage collection (cont.) Dangling references are references to

space that has been reclaimed mistakenly.

Such references result in errors that often are extremely difficult to track down.

We’ve covered

Linked structures nodes headers

Doubly-linked lists Circular lists Time complexities Dynamic storage allocation

garbage collection

Glossary

Glossary (cont.)

Recommended