Linked Lists
Data Structures and Algorithms I
Dr. Tim Margush University of Akron
The Concept
Sequentially related information can be stored in non-sequential storage locations
List Nodes are the containers that hold the information
Links are stored with the information to determine the location of the next item
Links are usually array subscripts or pointers
The position of the first item is stored separately
Where are the Nodes
Stored in an Array
Array subscripts represent the location of a node Links are simply ints (subscript values)
Dynamically Allocated (from heap)
Node locations are actual addresses Links are pointers to nodes (Node *)
Linked Lists in an Array
An array of Nodes (Node store[MAX];) provides storage for the linked list
class Node contains members data and
next
In this example, the first node in the list is at 3 first 3 List contents: 1092, 9873, 198, 983,
40932, 76
sub 0 1 2 3 4 5
data 198 40932 983 1092 76 9873
next 2 4 1 5 End -1 of list 0
Dynamic Linked Lists
A pointer to a Node Node * head; (Node * head;) provides access to the 4357 first item in the list
class Node contains members data and 76 10956
9822
NULL pointer
next
126
Head
List contents: 10956, 76, 4357, 9822, 126
Self-Referential Structures
A struct or class that contains a member that refers to another object of the same type
Note: class X cannot have a member of type X, but it can have a member of type X* or X& class Node{ ITEM_TYPE data; Node * next; }
Each Node object contains a pointer that can contain the address of another Node object
Array or Dynamic?
Array Implementation User manages free store (in the array)
but more code Array size limits storage
Dynamic Implementation System manages free store (via new and delete) More efficient (faster),
Easy to use Storage is limited only by heap size
Array takes storage Storage used even if not completely proportional to used number of nodes
A Typical (Dynamic) Node Class
#define E_TYPE Fraction class Node{ public: Node(const E_TYPE &, Node * = NULL); E_TYPE & getData(); Node * getNext() const; void setNext(Node *); private: E_TYPE data; Node * next; };
For efficiency, Node may make class List a friend
Omit access functions
E_TYPE is any type that has a copy constructor
We must initialize data in the constructor's member initializer list
A Simple Linked List
//Create empty list Node * head = NULL; //Add first node 1 4 2 x 3 Fraction x(1,2); head = new Node(x, head); //Add node to position 1 x.setFraction(3,4); head = new Node(x, head);
head
1 2
3 4
Insert at Beginning
This is the easiest case
It is a special case in a more general insert routine //x to the front of the list! Node * temp; temp = new Node(x); temp->next = head; head = temp; or more simply: head=new Node(x, head);
The new node must point to the address found in head pointer head must then point to the new node
Repeat 10 Times: Insert at End
If new items are to be added at the end of a linked list, keep a pointer to the last item for efficiency
Node *head=NULL, *last=NULL; //First node is special: last = head = new Node(x); //Then for each new x: last = last->next = new Node(x); Note that each Node's next member is set to NULL by default
This is the correct value for the last Node in the list
A Distinguished List Element
Many linked list operations are applied to a "current element" in the list
insert before/after current, remove current
It is customary to have another pointer (besides head) to specify the location of the current list item
Surprisingly, it usually does not point directly to the current item!
Current Pointer
3 5 2 7 1 3 4 1
curr points to the node before the curr current node
Fraction 1/3 is "current"
curr==NULL means first node is current
This idea reduces the complexity of many linked list algorithms Next we look at how you would insert a new node at the current position
Insert At (Before) Current
3 5
curr
2 7
1 3
4 1
//Insert 11/4 at current Node *temp; temp Fraction x(11,4); temp = new Node(x,curr->next); curr->next = temp;
11 4
x 11 4
Illegal Dereferencing
Just remember two things: 1. Never dereference a NULL pointer 2. Never dereference a NULL pointer if (curr == NULL){ temp = new Node(x,head); head = temp; } else { temp = new Node(x,curr->next);
The beginning of the list is usually a special case in code
curr->next = temp;
}
Removing Current
3 5
curr
2 7
1 3
4 1
// 1/3 is to be removed Node *temp = curr->next; curr->next = temp->next; delete temp;
temp
Did we dereference a NULL pointer? Removing the first item is a special case!
Could temp be NULL?
List Disposal
Node *temp; //delete all nodes while(head){ temp=head; head=head->next; delete temp; } //head is now NULL
Each Node must be deleted individually A temporary pointer is required for this operation
temp holds the address of the head node while head is set to the next (if any)
List ADT - Linked Implementation (LI)
class List{ public: //No changes here! private: Node *head; Node *current; };
Only the implementation changes
head points to first item (if any) current points to node before current item
List clients will never suspect things are different
List ADT (LI) Con/Destructor & next()
List::List(){ void List::next(){ current=head=NULL; if (current==NULL) } current=head; List::~List(){ else if (current->next !=NULL) Node *temp; current=current->next; while(temp=head){ } head=head->next; Note: When current points at the last Node, the position at delete temp; the end of the list is current } (not the last element) }
List ADT (LI) length()
int List::length(){ int n = 0; Node *temp=head; while(temp){ n++; temp=temp->next; } return n; }
Since we do not explicitly store the number of items in the list, it is expensive to determine the current list length!
You could improve efficiency by storing the list size
List ADT (LI) setPos()
void List::setPos(int n){ if (n<1) return; if (n==1) { current=NULL; return;} Node * temp=head; while(--n>1 && temp) temp=temp->next; if (temp) current=temp; }
current changes only in n is legal
If n is too big, temp will become NULL For example:
if length is 3 L.setPos(4); //OK L.setPos(5); //illegal
head
1st
2nd
temp
3rd
List ADT (LI) prev()
void List::prev(){ if (current==head){ current=NULL; return;} Node * t=head; while(t->next!=current) t=t->next; current=t; }
Going backwards in a linked list is costly!
t searches for a Node that points to the same address current points to
Could t ever become NULL in this code?
List ADT (LI) insert()
int List::insert(type & elt){ if (current == NULL) head = new Node(x,head); else current->next = new Node(x,curr->next); }
There is no limit to the number of items we can insert
We may run out of memory
Inserting at first location is a special case This is easier than the array implementation!
List ADT (LI) remove()
int List::remove(){ Node * temp=head; if (current) temp=current->next; if (temp == NULL) return 0; if (current == NULL) head = temp->next; else current->next=temp->next; delete temp; return 1; }
temp points to the Node to be deleted
If current is at end of list, temp will be NULL remove fails in this case
Deleting the first item is a special case
current cannot be dereferenced (see underlines)
Advantages of Linked Implementation
No limit to number of items (up to memory limits) Insert and remove operations are very efficient
There is no shuffling of the remaining list items
Storage required is proportional to the number of elements currently in the list
An empty list is very small
Access via pointer is faster than accessing an array component
No computation
Disadvantages of Linked Implementation
SetPos, prev, and length are very inefficient
length could be easily made efficient by adding one data member SetPos is inherently inefficient since a linked list is not a random access structure
Linked lists are designed to be processed in forward order
prev cannot be improved in a simple linked list
Each Node contains a pointer which requires additional storage