0% found this document useful (0 votes)
9 views

LinkedList - revised

Uploaded by

littlen991
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views

LinkedList - revised

Uploaded by

littlen991
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 22

Pros and cons for storing a list in an array

- Support direct (random) access to elements in the list by index.


- Support binary search if the list is sorted.
- Insertion and deletion operation to an ordered list requires O(n) time.
- Physical size of the array is fixed once it is created. Programmer needs to
estimate the required size of the array.
- Handling of array overflow is costly (create a new array and copy the data
objects from old array to new array).

Representation of a list as linked list


- Length of a linked list is elastic.
- A linked list can be expanded dynamically.
- A linked list only supports sequential access to elements in the list.
- Insertion or deletion can be done in constant time (provided the point of
insertion or deletion is determined).

1
Linked List

• A linked list is a collection of components called nodes. Nodes are dynamically


created or destroyed based on computation requirements.
• A node has two parts, the data (or info) and the link (or next). The link is a
pointer pointing to the next node.

data link

• The address of the first node in the list is maintained in a pointer variable usually
called head, first, or list.

• The null pointer (nullptr or NULL, physical value 0 in C/C++) is used to denote
the end of the list, or not a valid address.

Example: a linked list of 4 integers.

head 45 65 34 76
NULL

In typical C++ implementations, we define the node using struct and design the
functions using template such that the programs can be reused for different data
types.

template<class Type>
struct node
{
Type info;
node<Type>* link; // another commonly used var name is “next”
};

node<Type>* head; // point to the first node in the list

// Other commonly used variable names : list, front, first, etc.

2
Example program codes that operate on a linked list

// Function to find the length of the linked list

template<class Type>
int length(const node<Type>* list) // getter function
{
int len = 0;
const node<Type>* p = list; // traverse the list using p

while (p != nullptr) // p points to a valid node


{
len++;
p = p->link; // move to the next node
}
return len;
}

3
// Insert a new element x at the front of the list.
// Assume Type = int in the following examples.

step 1:

head 45 65 34 76
NULL

(step 1)
p x

step 2:

head 45 65 34 76
NULL
(step 2)

p x

template<class Type>
void insertFront(node<Type>*& list, const Type& x)
{
// setter function, list is passed by reference

node<Type>* p = new node<Type>; // create new node


p->info = x;

p->link = list; // step 1


list = p; // step 2, update pointer to the 1st node
}

4
// Remove the element x (1st instance) from the linked list

// To remove a node from the linked list, we need to


// know the reference to its predecessor (the previous node).

// Program organization:
// 1. search for the node holding x with a while-loop
// 2. if x is found, remove the node
// (need to know its predecessor)
template<class Type>
bool remove(node<Type>*& list, const Type& x) // setter function
{
node<Type>* cur = list;
node<Type>* prev = nullptr;
// prev points to the predecessor of cur

while (cur != nullptr && cur->info != x) // sequential search


{
prev = cur;
cur = cur->link; // maintain relationships of cur & prev
}

// if cur == nullptr, x is not found in the linked list

if (cur != nullptr) // cur->info == x


{
if (prev != nullptr) // cur is not the first node
prev->link = cur->link;
else
list = cur->link;

delete cur; // free the storage of the removed node


return true; // node holding x is removed
}
return false; // x is not found, no node is removed
}

5
Diagrams showing the deletion operation:

1. Locate the node storing the value x, e.g. x = 34

prev cur

head 45 65 34 76
NULL

2. Update the links

prev cur

head 45 65 34 76
NULL

3. Physically delete the node

prev cur

head 45 65 34 76
NULL

Structure of the list after removing the element 34

head 45 65 76
NULL

6
Function to find the min data value in an unordered linked list

//Precondition: list is not empty


template<class Type>
Type getMin(const node<Type> *list) // getter function
{
Type min = list->info;
const node<Type> *p = list->link;

while (p != nullptr)
{
if (p->info < min)
min = p->info;

p = p->link;
}
return min;
}

Function to find the node holding the min data value in an unordered linked list

template<class Type>
node<Type>* getMinNode(node<Type>* list) // getter function
{
if (list == nullptr) // return nullptr if the list is empty
return nullptr;

node<Type>* min = list;


node<Type>* p = list->link;

while (p != nullptr)
{
if (p->info < min->info)
min = p;

p = p->link;
}
return min;
}

7
Function to remove the node with the min data value in an unordered linked list

//Precondition: list is not empty


template<class Type>
Type removeMin(node<Type>*& list) // setter function
{
// list is passed by reference (may be modified)
node<Type>* min = list;
node<Type>* prev = nullptr; // prev is predecessor of min

// Search for the min node and its predecessor

node<Type> *p = list->link; //comparison starts from 2nd node


node<Type> *q = list; //q is predecessor of p

while (p != nullptr)
{
if (p->info < min->info)
{
prev = q;
min = p;
}

q = p;
p = p->link;
}

// remove the node min

Type minValue = min->info;

if (prev != nullptr)
prev->link = min->link;
else // min is the 1st node
list = min->link; // list is updated, that’s why
// list is passed by reference
delete min;
return minValue;
}

8
Function to insert a node to an ordered linked list

template<class Type>
void insert(node<Type>*& list, node<Type>* p) // setter function
{
node<Type> *prev = nullptr;
node<Type> *cur = list;

// Determine the point of insertion (i.e. after prev)


while (cur != nullptr && p->info > cur->info)
{
prev = cur;
cur = cur->link;
}

// Perform the insertion


if (prev == nullptr) // insert p at front
{
p->link = list;
list = p;
}
else // insert p after prev
{
p->link = prev->link;
prev->link = p;
}
}

Remark:
In this example, comparison of data objects is done using the operator>()
function of the given data type (i.e. based on natural order of the data type).

9
Sort a linked list using the insert function
template<class Type>
void insertionSort(node<Type>*& list)
{
// Sorting is based on natural order of the data type

node<Type>* sortedList = nullptr;

node<Type>* p = list;
while (p != nullptr)
{
node<Type>* r = p->link;
insert(sortedList, p);
p = r;
}
list = sortedList;
}

Question: Is the above implementation of insertion sort a stable sorting algorithm ?

Remark:
• Null-pointer exception is a common error in programs that manipulate linked list.
• A pointer must be properly initialized or tested for not equal to nullptr before you
can use it to access a data member or the next node.

10
Function to merge two ordered linked lists
template<class Type>
node<Type>* clone(const node<Type>* p)
{
node<Type>* r = new node<Type>;
r->info = p->info;
r->link = nullptr;
return r;
}

template<class Type>
node<Type>* mergeList(const node<Type > *A, const node<Type> *B)
{
const node<Type> *p = A; // traverse list A with p
const node<Type> *q = B; // traverse list B with q

node<Type> *r, *header;


r = header = new node<Type>; // r is last node in result list
// header is a dummy node to simplify the codes in the loop
while (p != nullptr && q != nullptr)
{
if (p->info <= q->info)
{ r->link = clone(p); p = p->link; }
else
{ r->link = clone(q); q = q->link; }
r = r->link;
}
while (p != nullptr)
{
r->link = clone(p);
r = r->link;
p = p->link;
}
while (q != nullptr)
{
r->link = clone(q);
r = r->link;
q = q->link;
}
r = header->link; // r points to 1st data node
delete header; // release memory space of header node
return r;
}
11
Other variants of linked list

1. Linked list with header node:

header node

head 45 65 76
NULL

The data field of the header node is not used to store valid data. Some metadata may
be stored in the header node (if appropriate).

List does not exist head


NULL
(or not yet created):

header node

List is empty: head


NULL

2. Circular list:
- The link of the last node points back to the first node.

c_list

first node last node


45 65 34 76

- The pointer variable c_list points to the last node in the list.
- We can access the first node in one step:

node<Type> *first = c_list->link;

12
3. Circular list with header:

head
first node last node

65 34 76

4. Doubly-linked list:

back info next

template<class Type>
struct DNode
{
Type info;
DNode<Type>* next; //points to successor node
DNode<Type>* back; //points to predecessor node
}

With a doubly-linked list, we can traverse the list in the forward or backward
direction.

It is more convenience to perform insertion and deletion to a doubly-linked list.


You don’t need to maintain 2 pointer variables.

Remark: The list container in the C++ standard template library (STL), and the
LinkedList class in the Java JDK are implemented as doubly-linked list.

13
first last

45 65 76
NULL NULL

//Assume the doubly-linked list is referenced by 2 pointer


//variables first and last

//Insert element x after the current node


DNode<Type>* p = new DNode<Type>;
p->info = x;
p->next = cur->next; // given cur != nullptr
p->back = cur;

if (cur->next != nullptr) // cur has a successor


cur->next->back = p;
else
last = p; // p is the last node after insertion

cur->next = p;

//Remove node p in a doubly-linked list

if (p->next != nullptr)
p->next->back = p->back;
else // p is the last node
{
last = p->back;
if (last != nullptr)
last->next = nullptr;
}

if (p->back != nullptr)
p->back->next = p->next;
else // p is the first node
{
first = p->next;
if (first != nullptr)
first->back = nullptr;
}
delete p;
14
5. Doubly-linked list with header node

head
first node last node
NULL 45 65 76
NULL
header node

6. Circular doubly-linked list

45 65 76

first

7. Circular doubly-linked list with header

first node last node


45 65 76

head

Circular doubly-linked list with header is used in some operating system for
dynamic memory allocation and de-allocation.
The algorithm is called boundary-tag method.

15
The searching problem

Given a set of key-value pairs (where the keys are distinct), we want to find the
associated value for a given key.

Time complexity of the search and update operations (insertion or deletion of keys
to the data set):
Time complexity
Search algorithm search insert/delete
Sequential search (unordered list) O(N) O(1)
Binary search O(log N) O(N)
(ordered list maintained in an array)
Binary search tree (balanced) O(log N) O(log N)
Hash table, Map or Dictionary O(1) O(1)
(average case and best case)

16
General principle of Hash table

(K, V) is a key-value pair.

 = { (K1, V1), (K2, V2), …, (Kn, Vn) } // data set with n records

h(key) is the hash function, and output value of h(key) is between 0 and M-1.
The hash table is an array of size M where M ≥ n.

Example: assume the keys are unsigned fixed-size integer, and M = 11 is a prime
number.

h(key) = key % M
 = { (55, V1), (41, V2), (73, V3), (20, V4), (35, V5) }

Hash Table
index bucket (key, value)
0 (55, V1) 55 % 11 = 0
1
2 (35, V5) 35 % 11 = 2
3
4
5
6
7 (73, V3) 73 % 11 = 7
8 (41, V2) 41 % 11 = 8
9 (20, V4) 20 % 11 = 9
10

The load factor is defined as  = n/M, where n is the number of elements, and M is
the size of the hash table.

17
Let U = domain of the keys.
M = size of the hash table.
The hash function h is a mapping h: U → {0, 1, 2, …, M−1}, where |U| >> M.

To search for a given key x, we use the hash function to compute the home address
home = h(x).
• If the home bucket is empty, then x does not exist in the set.
• The record stored in the home bucket is compared with x. If x matches the key
of the record, then the associated value is returned.
• If x does not match the key stored in the home bucket, then some additional
buckets may be probed depending on the collision resolution strategy used.

Collision
• A collision is said to have occurred when two distinct keys K1 and K2 are
hashed into the same bucket, i.e. h(K1) = h(K2).

Perfect hash function


• A function h is said to be a perfect hash function with respect to a given set of
keys  such that h(K1)  h(K2) for any two distinct keys K1, K2 .

18
Some simple hash functions

Bit-Extraction:
• Merely extract a few scattered bits from an element, putting those bits
together to form an address.
• A weakness of extraction is that the resulting location depends only on a small
subset of the bits of an element.
• A first principle in the design of hash functions is that the hash location should
be a function of every bit of the element.

Compression or folding:
• The key is divided into fixed-length segments, and then add them up as binary
numbers or take their exclusive-or.
• For example, keys are character strings, key = “THE”,
h(“THE”) = 0101 0100 xor 0100 1000 xor 0100 0101 = 0101 1001 = 89 d
• In general, if the keys are character string, then breaking the key into
individual chars is not a good idea!
Permutation of the same set of characters will have the same hash value, e.g.
h(“THE”) = h(“HET”)

Division:
• h(x) = x % M, where M is a prime number.

Multiplication:
• h(x) = M × fraction( × x)
• where  = (5 – 1) / 2, or  = 1 − (5 – 1) / 2

19
Collision resolution using separate chaining

• The hash table T is organized as an array of linear linked lists, one linked list
for each hash bucket.
• To lookup a key, we simply do a sequential search of the given linked list.

Example:
Suppose the hash function h(x) = x % 11.
Let  = {17, 33, 28, 20, 45, 64, 56}

After inserting all the keys, the hash table looks as follows:

hashTable
0 33 NULL
1 45 56 NULL
2 NULL
3 NULL
4 NULL
5 NULL
6 17 28 NULL
7 NULL
8 NULL
9 20 64 NULL
10 NULL

Remarks:
- The hash table class in the Java JDK is implemented using separate chaining.
- Performance of the hash table depends on the load factor. Usually, the load
factor is maintained to be no more than 0.75 (threshold chosen by the Java
designer).

20
Orthogonal list: Linked representation of sparse matrix

Non-zero elements in a row (or column) are connected in a circular linked list with
header node.

// In this example, struct node is NOT defined as template


struct node { int row, col;
double value;
node *down, *right;
};

node *M; //reference pointer to the sparse matrix

𝟓𝟗 𝟎 𝟎 𝟎
Example: 𝑀 = [𝟕𝟏 𝟎 𝟗 𝟎]
𝟎 𝟎 𝟎 𝟎
𝟐 𝟎 𝟏 𝟔

4 4 -1 0 -1 2 -1 3

0 -1 0 0 59

1 -1 1 0 71 1 2 9

3 -1 3 0 2 3 2 1 3 3 6

21
• In this example, values of nRow and nCol of the sparse matrix are stored in the
dummy header node pointed at by M.
• The header of each circular list stores the respective row/column number.

//Return the value of matrix[i][j]

double getValue(node *M, int i, int j)


{
node *p, *q;
int foundRow = 0;

p = M->down;
while (p != M && !foundRow)
{
if (p->row < i)
p = p->down;
else if (p->row == i) //found the row
foundRow = 1;
else
p = M; //terminate the loop
}

if (foundRow) //p points to header of row i


{
q = p->right;
while (q != p)
{
if (q->col < j)
q = q->right;
else if (q->col == j)
return q->value; //value of matrix[i][j]
else
q = p; //terminate the loop
}
}

return 0; //matrix[i][j] == 0
}

22

You might also like