Chapter 2
Linked Lists
1
What is a Data Structure?
A data structure is a collection of data organized in
some fashion. A data structure not only stores data,
but also supports the operations for manipulating
data in the structure. For example, an array is a data
structure that holds a collection of data in sequential
order. You can find the size of the array, store,
retrieve, and modify data in the array.
Array is simple and easy to use, but it has two
limitations:
2
Limitations of arrays
Once an array is created, its size cannot
be altered.
Array provides inadequate support for
inserting, deleting, sorting, and searching
operations.
3
Object-Oriented Data Structure
In object-oriented thinking, a data structure is an object that stores
other objects, referred to as data or elements.
So some people refer a data structure as a container object or a
collection object. To define a data structure is essentially to declare a
class.
The class for a data structure should use data fields to store data and
provide methods to support operations such as insertion and deletion. To
create a data structure is therefore to create an instance from the class.
You can then apply the methods on the instance to manipulate the data
structure such as inserting an element to the data structure or deleting an
element from the data structure.
4
Four Classic Data Structures
Four classic dynamic data structures to be introduced in this
chapter are lists, stacks, queues, and priority queues.
– A list is a collection of data stored sequentially. It supports insertion
and deletion anywhere in the list.
– A stack can be perceived as a special type of the list where
insertions and deletions take place only at the one end, referred to as
the top of a stack.
– A queue represents a waiting list, where insertions take place at the
back (also referred to as the tail of) of a queue and deletions take
place from the front (also referred to as the head of) of a queue.
– In a priority queue, elements are assigned with priorities. When
accessing elements, the element with the highest priority is removed
first.
5
Lists
A list is a popular data structure to store data in sequential
order. For example, a list of students, a list of available
rooms, a list of cities, and a list of books, etc. can be stored
using lists. The common operations on a list are usually the
following:
· Retrieve an element from this list.
· Insert a new element to this list.
· Delete an element from this list.
· Find how many elements are in this list.
· Find if an element is in this list.
· Find if this list is empty.
6
Linked List
Likearrays, Linked List is a linear data structure.
Unlike arrays, linked list elements are not stored at
a contiguous location; the elements are linked
using pointers.
7
Why Linked List?
Arrays can be used to store linear data of similar types, but arrays have
the following limitations.
1) The size of the arrays is fixed: So we must know the upper limit on the number of
elements in advance. Also, generally, the allocated memory is equal to the upper limit
irrespective of the usage.
2) Inserting a new element in an array of elements is expensive because the room has to
be created for the new elements and to create room existing elements have to be
shifted.
For example, in a system, if we maintain a sorted list of IDs in an array id[].
id[] = [1000, 1010, 1050, 2000, 2040].
And if we want to insert a new ID 1005, then to maintain the sorted order, we have to
move all the elements after 1000 (excluding 1000).
Deletion is also expensive with arrays until unless some special techniques are used.
For example, to delete 1010 in id[], everything after 1010 has to be moved.
Advantages over arrays 8
Draw backs: Linked List
1) Random access is not allowed. We have to access
elements sequentially starting from the first node.
So we cannot do binary search with linked lists
efficiently with its default implementation.
2) Extra memory space for a pointer is required with
each element of the list.
3) Not cache friendly. Since array elements are
contiguous locations, there is locality of reference
which is not there in case of linked lists.
9
Representation: : Linked List
A linked list is represented by a pointer to the first node of the linked
list. The first node is called the head. If the linked list is empty, then
the value of the head is NULL.
Each node in a list consists of at least two parts:
1)data
2)Pointer (Or Reference) to the next node
Note
In C, we can represent a node using structures. Below is an example
of a linked list node with integer data.
In Java or C#, LinkedList can be represented as a class and a Node as
a separate class. The LinkedList class contains a reference of Node
class type.
10
Nodes in Linked Lists
A linked list consists of nodes. Each node contains an element,
and each node is linked to its next neighbor. Thus a node can be
defined as a class, as follows:
tail
Node 1 Node 2 Node n
head …
element 1 element 2 element n
next next null
class Node:
def __init__(self, element):
self.elmenet = element
self.next = None
11
Adding Three Nodes
The variable head refers to the first node in the list, and the
variable tail refers to the last node in the list. If the list is
empty, both are None. For example, you can create three
nodes to store three strings in a list, as follows:
Step 1: Declare head and tail:
head = None The list is empty now
tail = None
12
Adding Three Nodes, cont.
Step 2: Create the first node and insert it to the list:
head = Node("Chicago") After the first node is inserted
last = head inserted
head "Chicago"
tail
next: None
13
Adding Three Nodes, cont.
Step 3: Create the second node and insert it to the
list:
tail
head "Chicago" "Denver"
tail.next = Node("Denver")
next next: None
tail
tail = tail.next head "Chicago" "Denver"
next next: None
14
Adding Three Nodes, cont.
Step 4: Create the third node and insert it to the list:
tail.next = Node("Dallas") tail
head "Chicago" "Denver" "Dallas"
next next next: None
tail
tail = tail.next
head "Chicago" "Denver" "Dallas"
next next next: None
15
Traversing All Elements in the List
Each node contains the element and a data field
named next that points to the next element. If the
node is the last in the list, its pointer data field next
contains the value None. You can use this property
to detect the last node. For example, you may write
the following loop to traverse all the nodes in the list.
current = head
while current != None:
print(current.element)
current = current.next
16
LinkedList
m 1
Node LinkedList
element: object -head: Node
next: Node -tail: Node
-size: int
1
LinkedList() Creates an empty linked list.
Link addFirst(e: object): None Adds a new element to the head of the list.
addLast(e: object): None Adds a new element to the tail of the list.
getFirst(): object Returns the first element in the list.
getLast(): object Returns the last element in the list.
removeFirst(): object Removes the first element from the list.
removeLast(): object Removes the last element from the list.
add(e: object) : None Same as addLast(e).
insert(index: int, e: object) : None Adds a new element at the specified index.
clear(): None Removes all the elements from this list.
contains(e: object): bool Returns true if this list contains the element.
get(index: int) : object Returns the element from at the specified index.
indexOf(e: object) : int Returns the index of the first matching element.
isEmpty(): bool Returns true if this list contains no elements.
lastIndexOf(e: object) : int Returns the index of the last matching element.
getSize(): int Returns the number of elements in this list.
remove(e: object): bool Removes the element from this list.
removeAt(index: int) : object Removes the element at the specified index and
returns the removed element.
set(index: int, e: object) : object Sets the element at the specified index and returns the
element you are replacing.
__iter__() : Iterator Returns an iterator for this linked list.
LinkedList TestLinkedList Run
17
Implementing addFirst(e)
def addFirst(self, e):
newNode = Node(e) # Create a new node
newNode.next = self.__head # link the new
node with the head
self.__head = newNode # head points to the
new node
self.__size += 1 # Increase list size
if self.__tail == None: # the new node is the
only node in list
self.__tail = self.__head head tail
e0 … ei ei+1 … ek
next next next None
A new node
to be inserted
here e
next (a) Before a new node is inserted.
tail
e0 … ei ei+1 … ek
next next next None
head
This is e
the new
node next (b) After a new node is inserted.
18
Implementing addLast(e)
def addLast(self, e):
newNode = Node(e) # Create a new node for e
if self.__tail == None:
self.__head = self.__tail = newNode # The
only node in list
else:
self.__tail.next = newNode # Link the new
with the last node
self.__tail = self.__tail.next # tail now points
to the last node
head tail
self.__size += 1 # Increase size A new node
e0 … ei ei+1 … to be inserted
ek
here
next next next None
e
(a) Before a new node is inserted. None
head
e0 … ei ei+1 … A new node
ek
tail is appended
next next next next in the list
e
(b) After a new node is inserted. None
19
Implementing add(index, e)
def insert(self, index, e):
if index == 0:
self.addFirst(e) # Insert first head current temp tail
elif index >= size:
…
self.addLast(e) # Insert last e0 ei ei+1 … ek
next next next
else: # Insert in the middle None
current = head A new node
to be inserted
for i in range(1, index): here e
(a) Before a new node is inserted.
current = current.next None
temp = current.next
current.next = Node(e) head current temp tail
(current.next).next = temp
self.__size += 1 e0 … ei ei+1 … ek
next next next None
A new node
is inserted in e
the list (b) After a new node is inserted.
None
20
Implementing removeFirst()
def removeFirst(self): head tail
if self.__size == 0:
e0 e1 … ei ei+1 … ek
return None
next next next next None
else:
temp = self.__head
self.__head = Delete this node (a) Before the node is deleted.
self.__head.next self.__size -= 1
if self.__head == None:
self.__tail = None return temp.element
head tail
e0 e1 … ei ei+1 … ek
next next next next None
This node is deleted (b) After the node is deleted.
21
def removeLast(self):
if self.__size == 0: Implementing
return None
elif self.__size == 1: removeLast()
temp = self.__head
self.__head = self.__tail = None head current tail
self.__size = 0
return temp.element e0 e1 … ek-2 ek-1 ek
else:
next next next next None
current = self.__head
for i in range(self.__size - 2): (a) Before the node is deleted. Delete this node
current = current.next
head tail
temp = self.__tail
self.__tail = current
e0 e1 … ek-2 ek-1 ek
self.__tail.next = None
self.__size -= 1 next next next None None
return temp.element
(b) After the node is deleted. This node is deleted
this node
22
Implementing removeAt(index)
def removeAt(self, index):
if index < 0 or index >= self.__size:
return None # Out of range
elif index == 0:
return self.removeFirst() # Remove first
elif index == self.__size - 1:
return self.removeLast() # Remove last
else: head previous current current.next tail
previous = self.__head
e0 … Ek-1 ek ek-1 … ek
for i in range(1, index): next next next next None
previous = previous.next
Delete this node (a) Before the node is deleted.
current = previous.next
previous.next = current.next head previous current.next tail
self.__size -= 1
e0 … ek-1 ek-1 … ek
return current.element
next next next None
(b) After the node is deleted.
23
Time Complexity for list and LinkedList
Methods for list/Complexity Methods for LinkedList/Complexity
append(e: E) O (1) add(e: E) O (1)
insert(index: int, e: E) O (n ) insert(index: int, e: E) O (n )
N/A clear() O(1)
e in myList O (n ) contains(e: E) O (n )
list[index] O(1) get(index: int) O (n )
index(e: E) O (n ) indexOf(e: E) O (n )
len(x) == 0? O (1) isEmpty() O(1)
N/A lastIndexOf(e: E) O (n )
remove(e: E) O (n ) remove(e: E) O (n )
len(x) O (1) getSize() O (1)
del x[index] O(n) removeAt(index: int) O (n )
x[index] = e O(n) set(index: int, e: E) O (n )
insert(0, e) O(n) addFirst(e: E) O(1)
del x[0] O(n) removeFirst() O(1)
24
Comparing list with LinkedList
LinkedListPerformance Run
25
Circular Linked Lists
A circular, singly linked list is like a singly
linked list, except that the pointer of the last
node points back to the first node.
Node 1 Node 2 Node n
head … tail
element element element
next next next
26
Doubly Linked Lists
A doubly linked list contains the nodes with two
pointers. One points to the next node and the other
points to the previous node. These two pointers are
conveniently called a forward pointer and a backward
pointer. So, a doubly linked list can be traversed
forward and backward.
Node 1 Node 2 Node n
head … tail
element element element
next next null
null previous previous
27
Circular Doubly Linked Lists
A circular, doubly linked list is doubly linked
list, except that the forward pointer of the last
node points to the first node and the backward
pointer of the first pointer points to the last node.
Node 1 Node 2 Node n
head … tail
element element element
next next next
previous previous previous
28
Iterators
An iterator is an object that provides a uniformed way for
traversing the elements in a container object. Recall that you
can use a for loop to traverse the elements in a list, a tuple, a
set, a dictionary, and a string. For example, the following code
displays all the elements in set1 that are greater than 3.
set1 = {4, 5, 1, 9}
for e in set1:
if e > 3:
print(e, end = ' ')
29
__iter__ Method
Can you use a for loop to traverse the elements in a linked list?
To enable the traversal using a for loop in a container object,
the container class must implement the __iter__() method that
returns an iterator as shown in lines 112-114 in Listing 18.2,
LinkedList.py.
def __iter__(self):
return LinkedListIterator(self.__head)
30
__next__ Method
An iterator class must contains the __next__()
method that returns the next element in the container
object as shown in lines in lines 122-132 in Listing
18.2, LinkedList.py.
LinkedList TestIterator Run
FibonacciNumberIterator Run
31
Generators
Generators are special Python functions for
generating iterators. They are written like regular
functions but use the yield statement to return data.
To see how generators work, we rewrite Listing
18.5 FibnacciNumberIterator.py using a generator
in Listing 18.6.
FibonacciNumberGenerator Run
32
Stacks
A stack can be viewed as a special type of list,
where the elements are accessed, inserted, and
deleted only from the end, called the top, of the
stack.
Data1 Data2 Data3
Data3
Data2 Data2
Data1 Data1 Data1
Data3 Data2 Data1
Data2
Data1 Data1
33
Stack Animation
www.cs.armstrong.edu/liang/animation/StackAnima
tion.html
34
Stack
Stack
-elements: list A list to store the elements in the stack.
Stack() Constructs an empty stack.
isEmpty(): bool Returns true if the stack is empty.
peek(): object Returns the element at the top of the stack without
removing it from the stack.
push(value: object): None Stores an element into the top of the stack.
pop():object Removes the element at the top of the stack and returns it.
getSize(): int Returns the number of elements in the stack.
Stack TestStack Run
35
Queues
A queue represents a waiting list. A queue can be
viewed as a special type of list, where the
elements are inserted into the end (tail) of the
queue, and are accessed and deleted from the
beginning (head) of the queue.
Data1 Data2 Data3
Data3
Data2 Data2
Data1 Data1 Data1
Data3 Data3
Data2
Data1 Data2 Data3
36
Queue Animation
www.cs.armstrong.edu/liang/animation/QueueAnim
ation.html
37
Queue
Queue
-elements LinkedList Stores queus elements in a list.
Queue() Creates an empty queue.
enqueue(e: object): None Adds an element to this queue.
dequeue(): object Removes an element from this queue.
getSize(): int Returns the number of elements from this queue.
isEmpty(): bool Returns true if the queue is empty.
__str__(): str Returns a string representation of the queue.
Queue TestQueue Run
38
Priority Queue
A regular queue is a first-in and first-out data structure. Elements are
appended to the end of the queue and are removed from the
beginning of the queue. In a priority queue, elements are assigned
with priorities. When accessing elements, the element with the
highest priority is removed first. A priority queue has a largest-in,
first-out behavior. For example, the emergency room in a hospital
assigns patients with priority numbers; the patient with the highest
priority is treated first.
PriorityQueue
-heap: Heap Elements are stored in a heap.
enqueue(element: object): None Adds an element to this queue.
dequeue(): objecct Removes an element from this queue.
getSize(): int Returns the number of elements from this queue.
PriorityQueue TestPriorityQueue Run
39