You are on page 1of 9

Lecture 11:

Linked Lists
PIC 10B
Todd Wittman

Data Structures



For the rest of this course, we'll study different ways to


store and organize data.
Data structures
 Vectors

 Queues

 Dynamic

 Binary

Arrays
 Linked Lists
 Stacks



Search Trees
 Hash Tables
 Heaps

We learn different data structures, because each has its


advantages and disadvantages.
We'll template our data structures, so they can hold
different types of data.

Erasing in a Vector
Dynamic arrays and vectors allocate sequential memory
blocks.
This is good for indexing. If the 10 block below is at
address 1201, then the 40 block is at address 1204.
But this is bad for inserting and erasing.
If we want to erase the 20 block in the picture below, we
have to shift all the elements over to the right.






10

20

30

40

10

30

40

Inserting and erasing in a vector is an O(N) operation.

Sec 12.1: Linked Lists




Instead of allocating sequential blocks of memory, we can


allow the blocks to be anywhere in the memory heap as
long as we have pointers from one block to the next.
20
10
30
40
NULL






This data structure is called a linked list.


Each block is called a node. The pointers are called links.
A node has two parts: the data and the link to the next node.
The last node points to NULL, so we know where the list ends.

Why Should We Use Linked Lists?




Suppose we want to erase the block indicated by the


arrow. A pointer that marks our position in the list is
called an iterator.
iterator
20

20

10

10
30

30
40

40
NULL




NULL

All we have to do is rearrange the pointers. Then we


can safely delete the 20 block, freeing up that block for
future use.
Erasing is an O(1) operation!
Inserting is just as easy!

Why Shouldn't We Use Linked Lists?







In arrays and vectors, the fact that the blocks are


sequential allows us to look up the Nth block very quickly.
We just add N to the address of the first block.
So look-ups in an array/vector are O(1).
In a linked list, to get to the Nth block we have to start at
the first block and trace a path through N blocks.
20
10

20

30

40

10
30
40
NULL

So look-ups in a linked list are O(N)!

LinkedList vs. Vector


Vector / Array: Look-ups are fast, but inserting and
deleting is slow.
LinkedList: Look-ups are slow, but inserting and
deleting is fast.




Vector

Linked List

Data access

O(1)

O(N)

Insert / Delete

O(N)

O(1)

So we need to ask which operations were going to be


performing more often: accessing or changing the data.

Types of Linked Lists




(Singly) Linked List


10

30

40

NULL

30

40

NULL

Doubly Linked List

NULL

20

10

20

Circularly Linked List


10

20

30

40

Today well start building a templated doubly linked list...

The 3 Classes



Our linked list will be built on 3 inter-dependent classes.


Node -- Stores one block of data.
Private: T data, Node<T>* prev, Node<T>* next
prev

data

next

LinkedList -- A list of nodes.


Private: Node<T>* first,
Node<T>* last
first
NULL

10

last
20

30

NULL

position


Iterator -- Marks our current position in the list.


Private: Node<T>* position

Sec 12.2: The Node Class





To create a list of nodes, we first need to build a Node class.


Nodes in a doubly linked list have two pointers: prev and next.
template <typename T>
class Node {
public:
prev
next
Node(T newData); //Constructor
data
template <typename T>
friend class LinkedList;
template <typename T>
friend class Iterator;
private:
T data;
Node* prev; //Pointer to previous node in list.
Node* next; //Pointer to next node in list.
};
But how will our LinkedList class access the Nodes data?

The Node Constructor








The Node constructor just needs to set the data to the


passed value.
Well initialize both pointers to NULL.
template <typename T>
Node<T>::Node(T newData) {
data = newData;
prev = NULL;
next = NULL;
NULL
newData
NULL
}
Now our LinkedList class can create a Node.
Or just a pointer to a Node using the new command.
Node<T>* nodePtr = new Node<T>(value);

The Iterator Class




We will use the Iterator class to move through our list:


iter = myList.begin(); iter.forward(); iter.backward();
first
NULL

10

last
20

30

NULL

position



We will also use the Iterator to mark the position of a node we want to
erase or insert.
When we erase a node, we will move the iterator to next position in list.
myList.erase(iter);
When we insert, we will insert the given value before the iterator position.
myList.insert(iter, 25);

What results when we apply these 2 operations to the list


above?

The Iterator Class


The LinkedList class needs to know the Iterator position for its insert
and erase functions.
template <typename T>
class Iterator {
position
public:
Iterator();
T get() const;
//Returns data value we are pointing to.
void forward();
//Moves position forward one node.
void backward();
//Moves position backward one node.
bool isNull();
//Returns true if iterator points to NULL.
template <typename T>
friend class LinkedList;
private:
Node<T>* position;
};


The -> Operator








But if we create a Node pointer, how do we get at the data


fields?
If you have a pointer to an object, you can access member
functions or private variables with the -> operator.
Node<int> *p = new Node(42);
p->data = 25;
//Same as saying: (*p).data = 25;
Well use the -> arrow a lot, because we need to move the
Node pointers around.
Just for fun, what would these commands do to a list?
p->next = NULL;
p->next = p->prev;
(p->next)->prev = p->next;

The Iterator Class


template <typename T>
Iterator<T>::Iterator() {
position = NULL;
}
template <typename T>
T Iterator<T>::get() const {
return position->data;
}
template <typename T>
bool Iterator<T>::isNull {
return (position==NULL);
}

template <typename T>


void Iterator<T>::forward() {
position = position->next;
}
template <typename T>
void Iterator<T>::backward()
{
position = position->prev;
}

Sec 16.2: The LinkedList Class


Were using dynamically allocated memory, so we need to
define the Big 4.
template <typename T>
first
last
class LinkedList {
NULL
10
20
30
NULL
public:
LinkedList();
~LinkedList();
***Well add more functions: insert, erase, operators***
private:
Node<T>* first; //Pointer to first node in list.
Node<T>* last; //Pointer to last node in list.
};


LinkedList Constructor
The default constructor just sets up an empty list.
template <typename T>
LinkedList<T>::LinkedList( ) {
first = NULL;
last = NULL;
}
 The template allows us to store lists of ints, strings, etc.
LinkedList<string> words;
LinkedList<double> numbers;
LinkedList<Matrix<double>> markovChains;
 Well discuss the destructor ~LinkedList() later, because it
will use our erase() function.


Setting Begin and End


template <typename T>
Iterator<T> LinkedList::begin() const {
Iterator<T> iter;
iter.position = first;
return iter;
}
 Now we can say:
Iterator<int> starting = myList.begin();
 The end() function would be similar.

You might also like