0% found this document useful (0 votes)
87 views44 pages

Implementations of The List ADT Properties of Array and Linked Implementations Separate and Inner Node Classes Singly and Doubly Linked Lists

This document discusses implementations of the List abstract data type (ADT) using arrays and linked structures. It covers: 1. Implementing singly and doubly linked lists using separate node and list classes, with the node class containing data and next/previous pointer fields. 2. The characteristics of array and linked list implementations, where arrays allow random access but linked lists only sequential access. 3. Examples of implementing empty and non-empty singly linked lists by having the head pointer point directly to the first node or to dummy head/tail nodes. Operations like adding a node at the head or a specific index are demonstrated.

Uploaded by

ShengFeng
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
87 views44 pages

Implementations of The List ADT Properties of Array and Linked Implementations Separate and Inner Node Classes Singly and Doubly Linked Lists

This document discusses implementations of the List abstract data type (ADT) using arrays and linked structures. It covers: 1. Implementing singly and doubly linked lists using separate node and list classes, with the node class containing data and next/previous pointer fields. 2. The characteristics of array and linked list implementations, where arrays allow random access but linked lists only sequential access. 3. Examples of implementing empty and non-empty singly linked lists by having the head pointer point directly to the first node or to dummy head/tail nodes. Operations like adding a node at the head or a specific index are demonstrated.

Uploaded by

ShengFeng
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 44

CSE 12

Fundamental Data Structures: The Array and


Linked Structures

Implementations of the List ADT


Properties of array and linked implementations
Separate and inner node classes
Singly and doubly linked lists

05

Introduction
Any interface specification can be correctly
implemented in many ways
However, different correct implementations may have
different performance characteristics
It is important to know these characteristics, as they
affect the performance of the resulting software

Collection ADT's

An ADT specifies a range of values that instances of


the type can have, and operations on those values

A collection ADT can use various data structures to


implement its values

The data structure a collection ADT uses internally is


sometimes called its backing store

For the List ADT, the backing store is typically chosen


to be either an array, or a linked list

Array Characteristics
An array is a homogeneous data structure: all elements are
of the same type

The elements of an array are in adjacent memory locations


Because each cell has the same size, and the cells are
adjacent in memory, it is possible to quickly calculate the
address of any array cell, given the address of the first cell
so accessing any array cell is constant time: just
one multiplication, one addition, and one memory
access
an array is a random (direct) access structure

Linked Structure Characteristics


Nodes in a linked structure are allocated and deallocated
dynamically, as needed

The nodes of a linked structure are created at different


times, and are probably not adjacent in memory
Even if the address of the first node in a linked structure is
known, it is impossible to directly calculate the address of
any other node; instead each node stores the address of the
next node in the structure
so a node access requires visiting all previous
nodes in the structure in sequence
a linked structure is a sequential access structure

Array Versus Linked Structures

Implementing Linked Lists


In a singly linked list, each node X contains:

a pointer to the node immediately after X in the list; or


null, or pointer to a dummy node, if X is the last node
in the list

the data in X (or a pointer to data)

In a doubly linked list, each node X contains:

a pointer to the node immediately after X in the list; or


null, or pointer to a dummy node, if X is the last node
in the list

a pointer to the node immediately before X in the list;


or null, or pointer to a dummy node, if X is the first
node in the list

the data in X (or a pointer to data)

Implementing Linked Lists


Taking an object oriented approach, you will define (at
least) two classes:
A node class
instances of this class will be nodes in the list
A list class
an instance of this class will contain (one or more
pointers to) instances of the node class

For each, consider the properties and behavior required


We will first show an example of a separate node class,
and later see how to define an inner node class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

SLNode<E>
/**
*
The structure of a node in a singly linked list
*/
public class SLNode<E> {
private E
data;
// the data field
private SLNode<E> next;
// link to successor

The self-referential
(recursive) part of
the definition

/**
* Create an empty <tt>SLNode</tt> object.
*/
public SLNode() {
this.data = null;
this.next = null;
}

/**
* Create an <tt>SLNode</tt> that stores <tt>theElement</tt> and
* whose successor is <tt>theSuccessor</tt>.
* @param theElement
the element to store in this node
* @param theSuccessor this nodes successor
*/
public SLNode( E theElement, SLNode<E> theSuccessor ) {
this.data = theElement;
this.next = theSuccessor;
}

28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

SLNode<E>
/**
*
Successor accessor
*/
public SLNode<E> getSuccessor() {
return this.next;
}
/**
* Successor mutator
*/
public void setSuccessor(SLNode<E> n) {
this.next = n;
}
/**
*
Element accessor
*/
public E getData() {
return this.data;
}
/**
* Element mutator
*/
public void setData(E e) {
this.data = e;
}

SLNode instance
variables are private; so
we need to define public
accessors and mutators

SLNode construction
next
data

node2

SLNode<Integer> node2 = new SLNode<Integer>();


node2
node1

i1

next
data

next
data
15

Integer i1 = new Integer(15);


SLNode<Integer> node1 = new SLNode<Integer>(i1,node2);

Accessing the successor of a SLNode


node2
node1

i1

next
data

next
data
15

temp

SLNode<Integer> temp = node1.getSuccessor();

A Singly-linked List using SLNode


public class SinglyLinkedList<E> implements java.util.List<E>{
private SLNode<E> head; // used in options 1,2,3
private SLNode<E> tail; // used only in option 2
private int size;
// number of data elements in the list

One important issue: how to represent an empty list?


Need to be clear about this! It has implications for
correctly coding several of the List operations
We will consider some implementation options:
1. head pointer points directly to first node, or null if
none; no tail pointer
2. head, tail always point to 'dummy' nodes
3. head always points to 'dummy' node; no tail pointer

Option 1: direct head pointer

Create initially empty singly linked list

SinglyLinkedList<Integer> ls = new SinglyLinkedList<Integer>();

ls

head
size

Null head pointer means empty list, so size must be 0

Option 1: direct head pointer


After adding 4 data items to the list

ls

next
data

head
size

next
data

next
data

next
data

4
15

10

Using head directly as pointer to the


first node of the linked structure

40

77

Null next pointer


means end
of the list

Option 2: head and tail 'dummy'


nodes

Create initially empty singly linked list

SinglyLinkedList<Integer> ls = new SinglyLinkedList<Integer>();

ls

next
data

head
tail

next
data

size

Dummy head and tail nodes always exist.


Dummy head node next pointing to dummy tail node
means empty list, with size 0

Option 2: head and tail 'dummy'


nodes
After adding 4 data items to the list

ls

next
data

head
tail

next
data
15

next
data
size

next
data

next
data
10

next
data
40

77

Dummy head node next pointer points to first element of list


Last element of list next pointer points to same node as tail pointer

Inserting an element at the beginning


of the list, using option 1
pseudocode
void addAtHead(E theElement) {

1. Create the new SLNode


SLNode<E> newnode = new SLNode<E>(theElement, null);

2. Set the new nodes next field to point where head points
newnode.setSuccessor(head);

3. Set head to point to the new node


head = newnode;

4. Increment size by 1
size++;

Question: does this correctly handle cases both


of adding at the head of an empty list, and of a nonempty list?

Adding at the beginning of an empty list:


Step 1
head
size

newnode

next
data
77
element

1. Create the new SLNode


SLNode<E> newnode = new SLNode<E>( theElement, null );

Adding at the beginning of an empty list:


Step 2
head
size

newnode

next
data
77
element

2. Set the new nodes next field to point where head points
newnode.setSuccessor( head );

Adding at the beginning of an empty list:


Step 3
head
size

newnode

next
data
77
element

3. Set head to point to the new node, increment size


head = newnode;
size++;

Adding at the beginning of a nonempty


list: Step 1
next
data

head
size

next
data

next
data

next
data

4
15

newnode

10

40

77

next
data
77
element

1. Create the new SLNode


SLNode<E> newnode = new SLNode<E>( theElement, null );

Adding at the beginning of a nonempty


list: Step 2
next
data

head
size

next
data

next
data

4
15

newnode

next
data
10

40

77

next
data
77
element

2. Set the new nodes next field to point where head points
newnode.setSuccessor( head );

Adding at the beginning of a nonempty


list: Step 3
next
data

head
size

next
data

next
data

5
15

newnode

next
data
10

40

next
data
77
element

3. Set head to point to the new node, increment size


head = newnode;
size++;

77

Operations on a singly-linked list


Keep in mind the different options were considering
in implementing a singly linked list:
1. head pointer points directly to first node, or null if
none; no tail pointer
2. head, tail always point to 'dummy' nodes
3. head always points to 'dummy' node; no tail pointer

We have looked at adding a node at the beginning of a


linked list in the context of option 1
Next we will look at adding a node at an arbitrary index
in a linked list, using option 1

Inserting an element at an index in


the list (using option 1)
public void add(int index, E theElement) {

1. check index >=0 && index <= size


if(index < 0 || index > size) throw new IOBException();

2. advance cursor to point to node just before insertion point


SLNode<E> cursor = head;
while(--index > 0) cursor = cursor.getSuccessor();

4. Create new node


SLNode<E> newnode = new SLNode<E>(theElement, null);

5. Set the new nodes next to be same as cursor's next


newnode.setSuccessor(cursor.getSuccessor());

6. Set cursor's next to point to the new node


cursor.setSuccessor(newnode);

7. Increment size by 1
size++;
}

Adding at index 2 in a list: Step 4


cursor
next
data

head
size

next
data

next
data

next
data

4
15

newnode

10

40

77

next
data
77
element

4. Create the new SLNode


SLNode<E> newnode = new SLNode<E>( theElement, null );

Adding at index 2 in a list: Step 5


cursor
next
data

head
size

next
data

next
data

4
15

newnode

next
data
10

40

77

next
data
77
element

5. Set newnode's next to be same as cursor's next


newnode.setSuccessor(cursor.getSuccessor());

Adding at index 2 in a list: Step 6


cursor
next
data

head
size

next
data

next
data

next
data

5
15

10

newnode

40

77

next
data
77
element

6. Set cursor's next to point to the new node, increment size


cursor.setSuccessor(newnode);
size++;

Adding at an index: cases to test

Does that add method work in all cases:

index == 0 ?

index == size ?

index == 0 when list is empty (so index==size


also)?

0 < index < size ?


If not, how can you fix it so it does work?
Would option 2 (using dummy head and tail nodes)
make the implementation easier?

Inserting an element at the beginning


of the list (using option 2)
void addAtHead(E theElement) {

1. Create the new SLNode


SLNode<E> newnode = new SLNode<E>(theElement, null);

2. Set the new nodes next to be same as dummy head node's next
newnode.setSuccessor(head.getSuccessor());

3. Set dummy head's next to point to the new node


head.setSuccessor(newnode);

4. Increment size by 1
size++;

Question: does this correctly handle the special


case of adding at the head of an empty list?

Deleting a node at an index:


pseudocode
1 public E remove ( int p ) {

1. Verify that p is within the linked list bounds


2. Move cursor to node with index p 1
3. Let target = cursor's successor: the node to remove
6

SLNode<E> target = cursor.getSuccessor();

4. Save the targets data, to return it


7

E element = target.getData();

5. Make cursor's next point to target's successor node.


(This removes target node from the list)
8

cursor.setSuccessor( target.getSuccessor() );

6. Decrement size
9

size--;

7. Return the element stored in the target node


10
11 }

return element;

Removing at index 2 in a list: Step 3


target
cursor
next
data

head
size

next
data

next
data

next
data

4
15

10

40

77

3. Let target = cursor's successor: the node to remove


SLNode<E> target = cursor.getSuccessor();

Removing at index 2 in a list: Step 5


target

cursor
next
data

head
size

next
data

next
data

next
data

3
15

10

40

77

5. Make cursor's next point to target's successor node.


cursor.setSuccessor( target.getSuccessor() );
size--;

Deleting a Node: Cleanup

Splicing around the node to be deleted, as shown,


removes it from the linked list

a traversal from the head of the list will no longer


encounter the node at all
Furthermore, consider the pointer variable target

It is a local variable in the remove method

It will be destroyed when the method returns

Then the program has no 'live' pointers to the


deleted node, and Java's garbage collector can
reclaim it
Nothing more needs to be done...!

Using an inner class for the node class

The previous example used a separate, top-level public


class SLNode<E> to define a singly-linked node type,
with accessor and mutator methods to manipulate the
node

Another approach is to use a private static inner class


to define a node, and access instance variables of
node objects directly, instead of using accessor/mutator
methods
This is a good example of making implementation
details (in this case, the entire definition of the
lists node class) private!

Using an inner class for the node class


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

import java.util.*;
/**
*
Outer class: List<E>
*/
public class SinglyLinkedList<E> implements java.util.List<E> {
private Node<E> head;
// pointer to first element (not dummy)
private int size;
// number of nodes in this List
/**
* private static inner class: Node<T>
*/
private static class Node<T> {
private T data; // the data field
private Node<T> next; // link to successor
private Node(T data) { // inner class constructor
this.data = data;
}
}
public SinglyLinkedList () {
// outer class constructor
head = null; // null head means empty list
size = 0;
}

Inserting an element at the


beginning, using Option 1 with an
inner node class
void addAtHead(E theElement) {

1. Create the new Node


Node<E> newnode = new Node<E>(theElement);

2. Set the new nodes next field to point where head is pointing
newnode.next = head;

3. Set head to point to the new node


head = newnode;

4. Increment size by 1
size++;

Question: does this correctly handle the special


case of adding to an empty list?

Doubly-Linked Lists
The singly-linked list is unidirectional
At the expense of an additional link (and the
consequent code complexity) we can have a
bidirectional list

We need to add a
second link
attribute to the
node definition

Circular Doubly-Linked Lists


We considered a singly-linked list implementation
using dummy head and tail nodes
Because of the bidirectional nature of a doubly-linked
list, it is possible to use just one dummy node, which
is both a head and a tail node!
The first node in the list has its predecessor pointer
pointing to the dummy node, and the last node in the
list has its successor pointer pointing to the dummy
node
Thus the list is circular, in that following successor
(or predecessor) pointers will eventually get you back
to where you started

A Circular Doubly-Linked List


This circular doubly linked list has size 3
From first to last, the data elements are: A, B, C

Time Complexity for Array Operations


Time Complexity for the Array Backing Store (n = # of occupied cells)
Operation

Cost

read at an index, worst case

(1)

add/remove (at the end of the array)

(1)

add/remove (in the interior of the array)

(n)

resize

(length of new array)

find by index

(1)

find by target

(n)

Time Complexity for Linked Operations


Time Complexity for the Linked List Backing Store
Operation

Cost
Singly Linked

Doubly Linked

read at an index, worst case

(n)

(n)

add/remove (at the head)

(1)

(1)

add/remove (at the tail)

(n)

(1)

add/remove (in the interior of the list)

(n)

(n)

resize

N/A

N/A

find by index

(n)

(n)

find by target

(n)

(n)

Next time

Algorithm analysis vs. measurement


Timing an algorithm
Average and standard deviation
Improving measurement accuracy

Reading: Gray, Ch 2

You might also like