Chapter 4
Chapter 4: Arrays
4.1 Definition
4.2 Linear Arrays
4.3 Arrays as Abstract Data Type (ADT)
4.4 Representation of Linear Arrays in Memory
4.5 Traversing Linear Arrays
4.6 Inserting and Deleting in Arrays
4.7 Multi-Dimensional Arrays
4.8 Matrices and Sparse Matrices
4.9 Searching and Sorting Techniques using Arrays
4.1 Definition of Arrays
An array is a collection of elements that share the same data type and are stored in
contiguous memory locations. Arrays are used to store multiple values in a single
variable, making it easier to manage large sets of data.
Characteristics of Arrays:
1. Homogeneous Elements: All elements in an array must be of the same data type.
2. Fixed Size: The size of an array is defined at the time of declaration and
cannot be changed dynamically.
3. Indexing: Each element in an array is accessed using an index (or subscript),
usually starting from 0 in C and C++.
4. Contiguous Memory Allocation: Elements are stored sequentially in memory,
which allows efficient access.
Representation of Arrays:
An array A of size n can be represented as:
● A[0], A[1], A[2], ..., A[n-1] in zero-based indexing (common in C and C++)
● A[1], A[2], ..., A[n] in one-based indexing (common in mathematical notation)
Types of Arrays:
1. One-Dimensional Array (Linear Array): A simple list of elements accessed
using a single index.
2. Multi-Dimensional Arrays:
○ Two-Dimensional Arrays (Matrix): Accessed using two indices, commonly
used for tables and matrices.
○ Three-Dimensional and Higher-Dimensional Arrays: Used for more
complex data structures.
Declaration and Initialization in C:
// Declaration of an integer array with 5 elements
int A[5];
// Initialization during declaration
int B[5] = {1, 2, 3, 4, 5};
// Accessing elements
printf("%d", B[2]); // Output: 3
Advantages of Arrays:
● Fast access: Any element can be accessed in constant time O(1).
● Memory efficiency: Data is stored in contiguous locations, reducing overhead.
● Simple implementation: Easy to declare and use.
Limitations of Arrays:
● Fixed size: Cannot grow dynamically, leading to wasted memory if not used
efficiently.
● Insertion and Deletion Overhead: Inserting or deleting elements requires
shifting elements, which can be inefficient.
4.2 Linear Arrays
A linear array is a list of a finite number n of homogeneous data elements (i.e.,
elements of the same type) that satisfy the following conditions:
1. Indexing: The elements are referenced using an index set consisting of n
consecutive numbers.
2. Memory Allocation: The elements of the array are stored in successive memory
locations.
The number n of elements in the array is called its length or size. If not explicitly
mentioned, it is assumed that the index set consists of integers from 1 to n. In
general, the length of an array can be calculated using the formula:
Length = Upper Bound (UB) - Lower Bound (LB) + 1
If the lower bound is 1, then the length of the array is simply equal to the upper bound.
Representation of Linear Arrays
The elements of an array A may be represented using different notations based on the
programming language:
● Subscript Notation: A1, A2, A3, ..., An
● Parentheses Notation (used in FORTRAN, PL/1, and BASIC): A(1), A(2), ..., A(N)
● Bracket Notation (used in Pascal, C, and C++): A[1], A[2], A[3], ..., A[N]
We commonly use subscript or bracket notation in algorithms and examples.
Example of a Linear Array
Consider a 6-element integer array DATA:
DATA = [247, 56, 429, 135, 87, 156]
Each element is stored in consecutive memory locations and can be accessed using an
index.
Memory Address Calculation
If an array is stored in contiguous memory locations, the address of an element can be
found using the formula:
LOC(A[K]) = Base(A) + w * (K - LB)
where:
● Base(A) = starting memory address of the array.
● w = size (in bytes) of each element.
● K = index of the element.
● LB = lower bound of the array index.
Example Calculation
Suppose an array AAA(5:50) has:
● Base(AAA) = 300
● w = 4 (words per memory cell)
● Lower bound (LB) = 5
Finding addresses:
LOC(AAA[15]) = 300 + 4 * (15 - 5) = 340
LOC(AAA[35]) = 300 + 4 * (35 - 5) = 420
If we try to find AAA[55], it would be invalid as UB = 50, meaning this index is out of
bounds.
Key Properties of Linear Arrays
1. Fast access: Direct access to elements using an index.
2. Efficient traversal: Each element can be accessed sequentially in O(1) time.
3. Fixed size: Memory is allocated at declaration and cannot be resized
dynamically.
4. Insertion & Deletion Complexity: Insertion and deletion operations require
shifting elements, making them O(n) operations.
Linear arrays provide a simple yet powerful data structure for storing and accessing
homogeneous elements efficiently.
4.3 Arrays as Abstract Data Type (ADT)
Definition of an Abstract Data Type (ADT)
An Abstract Data Type (ADT) is a mathematical model for a data structure,
specifying:
1. The data elements it can contain.
2. The operations that can be performed on the data.
3. The behavior of these operations.
An ADT does not specify how the data is stored or implemented internally but rather
focuses on what operations can be performed.
Arrays as an ADT
An array ADT defines a homogeneous collection of elements stored in contiguous
memory locations, which can be accessed using an index.
Characteristics of Array as an ADT
● Fixed Size: The size of an array is predefined and cannot be changed
dynamically.
● Direct Access: Elements can be accessed in constant time O(1) using an index.
● Homogeneous Data: All elements in an array belong to the same data type.
● Operations Defined for an Array:
1. Access (Retrieve an element using index)
2. Insertion (Adding an element at a specific position)
3. Deletion (Removing an element from a position)
4. Traversal (Processing all elements sequentially)
5. Searching (Finding an element within the array)
6. Sorting (Arranging elements in a particular order)
Example of Array ADT in C
typedef struct {
int data[100]; // Array with fixed size
int size; // Number of elements currently stored
} ArrayADT;
// Access function
int get(ArrayADT *arr, int index) {
if (index >= 0 && index < arr->size)
return arr->data[index];
return -1; // Invalid index
}
// Insert function
void insert(ArrayADT *arr, int index, int value) {
if (arr->size < 100 && index >= 0 && index <= arr->size) {
for (int i = arr->size; i > index; i--)
arr->data[i] = arr->data[i - 1];
arr->data[index] = value;
arr->size++;
}
}
// Delete function
void delete(ArrayADT *arr, int index) {
if (index >= 0 && index < arr->size) {
for (int i = index; i < arr->size - 1; i++)
arr->data[i] = arr->data[i + 1];
arr->size--;
}
}
Advantages of Array ADT
● Efficient access: O(1) time complexity for retrieving an element using an index.
● Simple structure: Easy to use and implement.
● Memory efficiency: Stored in contiguous memory locations, reducing access
overhead.
Limitations of Array ADT
● Fixed size: Cannot dynamically grow or shrink.
● Insertion and deletion inefficiencies: Requires shifting elements, making these
operations O(n).
The array ADT forms the foundation for more advanced data structures like linked
lists, stacks, and queues.
4.3 Representation of Linear Arrays in Memory
A linear array is stored in the computer’s memory as a sequence of contiguous memory
locations. Each element of the array is stored at a unique address, and the computer
does not keep track of the address of every element. Instead, it stores only the base
address, which is the memory location of the first element of the array.
Memory Representation of Linear Arrays
The elements of an array LA are stored in successive memory cells, and the address of
each element is computed using a formula rather than storing each address separately.
If Base(LA) is the starting address of the array and w is the number of words per
memory cell, the address of any element LA[K] can be calculated using the formula:
LOC(LA[K]) = Base(LA) + w * (K - LB)
where:
● Base(LA) is the starting memory address of the array.
● w is the number of words per memory cell.
● K is the index of the element.
● LB is the lower bound of the array index.
Example Calculation
Consider an array AUTO, which stores the number of automobiles sold from 1932 to
1984. Suppose:
● Base(AUTO) = 200
● w = 4 words per memory cell.
● Lower Bound (LB) = 1932.
To find the address of AUTO[1965]:
LOC(AUTO[1965]) = 200 + 4 * (1965 - 1932) = 200 + 132 = 332
This calculation shows that any element can be accessed directly in constant time O(1)
without scanning other elements.
Properties of Array Memory Representation
1. Contiguous Storage: Elements are stored sequentially, improving access speed.
2. Index-Based Access: The memory location can be computed using an index,
enabling quick lookup.
3. Fixed Size: The array size is allocated at declaration and remains constant.
4. Efficient Data Retrieval: Since addresses are computed, retrieval is
independent of the element’s position.
Linear arrays allow fast access to elements using their indices and are widely used for
storing structured data in memory.
4.4 Traversing Linear Arrays
Definition:
Traversing a linear array refers to the process of accessing and processing (visiting)
each element of the array exactly once. It is commonly used for printing elements,
counting occurrences of certain values, or performing computations on the array
elements.
Algorithm for Traversing a Linear Array
The following algorithm traverses a linear array LA with a lower bound (LB) and upper
bound (UB):
1. Initialize counter: Set K = LB.
2. Loop through the array: Repeat steps 3 and 4 while K ≤ UB:
○ Visit element: Apply a processing operation to LA[K].
○ Increase counter: Set K = K + 1.
3. Exit the loop.
Alternative Representation Using a For-Loop
A simpler version using a for-loop:
for (K = LB; K <= UB; K++) {
Process(LA[K]);
This structure is commonly used in programming languages such as C, C++, Java, and
Python.
Example: Counting Specific Values in an Array
Consider an array AUTO, which stores the number of automobiles sold each year from
1932 to 1984. Suppose we want to find the number of years during which more than
300 automobiles were sold.
Algorithm:
1. Initialize NUM = 0.
2. Loop from 1932 to 1984:
○ If AUTO[K] > 300, increment NUM by 1.
3. Return NUM.
C Implementation:
int countYears(int AUTO[], int LB, int UB) {
int NUM = 0;
for (int K = LB; K <= UB; K++) {
if (AUTO[K] > 300) {
NUM++;
return NUM;
Key Points About Traversing Arrays
● Time Complexity: O(n), where n is the number of elements.
● Space Complexity: O(1), as no additional space is used.
● Sequential Access: Each element is accessed in order.
● Initialization: Some operations may require initializing variables before
processing begins.
Traversing is one of the fundamental operations performed on arrays, making it an
essential concept in data structures and programming.
4.5 Inserting and Deleting in Arrays
Insertion in an Array
Insertion refers to adding a new element to an array. If the insertion occurs at the
end of the array and there is available memory, it can be done easily. However,
inserting an element in the middle of an array requires shifting elements to create
space for the new element.
Algorithm for Inserting an Element at Position K
1. Start with an array LA of size N and an index K where the new element ITEM
should be inserted.
2. Ensure there is enough memory to accommodate the new element.
3. Shift elements one position downward starting from the last element up to
position K to create space for ITEM.
4. Insert ITEM at K.
5. Increase the array size by 1.
C Implementation of Insertion
void insert(int LA[], int *N, int K, int ITEM) {
if (K > *N || K < 0) {
printf("Invalid Position");
return;
}
for (int J = *N; J >= K; J--) {
LA[J] = LA[J - 1];
}
LA[K] = ITEM;
(*N)++;
}
If an array NAME has elements sorted alphabetically and a new element must be
inserted while maintaining order, existing elements need to be shifted accordingly. This
is an expensive operation if there are many elements.
Deletion in an Array
Deletion refers to removing an element from the array. If the element is at the end, it
can be removed easily. However, removing an element from the middle requires shifting
subsequent elements upward to fill the empty space.
Algorithm for Deleting an Element at Position K
1. Identify the element at index K to be deleted.
2. Shift all elements after K one position upward.
3. Reduce the array size by 1.
C Implementation of Deletion
void delete(int LA[], int *N, int K) {
if (K >= *N || K < 0) {
printf("Invalid Position");
return;
}
for (int J = K; J < *N - 1; J++) {
LA[J] = LA[J + 1];
}
(*N)--;
}
Complexity Analysis
● Insertion at the End: O(1) (if space is available).
● Insertion in the Middle: O(n) (requires shifting).
● Deletion at the End: O(1).
● Deletion in the Middle: O(n) (requires shifting).
These operations highlight why arrays are not ideal for frequent insertions and
deletions. In such cases, linked lists may be a better choice.
4.6 Multi-Dimensional Arrays
A multi-dimensional array is an extension of a one-dimensional (linear) array where
each element is referenced by multiple indices. The most common types of
multi-dimensional arrays are two-dimensional (2D) arrays, also known as matrices, and
three-dimensional (3D) arrays.
Two-Dimensional Arrays
A two-dimensional m × n array A consists of m × n elements, where each element is
identified using two subscripts (J, K).
● The element at row J and column K is denoted as A[J, K].
● It can be visualized as a table or matrix where:
○ Rows are horizontal collections of elements.
○ Columns are vertical collections of elements.
Example of a 2D Array
Consider a 3 × 4 matrix A:
A[1,1] A[1,2] A[1,3] A[1,4]
A[2,1] A[2,2] A[2,3] A[2,4]
A[3,1] A[3,2] A[3,3] A[3,4]
Each element is accessed using its row index and column index.
Example: Storing Test Scores
If a class of 25 students takes 4 tests, the scores can be stored in a 25 × 4 matrix
named SCORE:
● SCORE[K, L] represents the K-th student’s score on the L-th test.
● The 2nd row represents the test scores of the second student.
Memory Representation of 2D Arrays
In memory, a 2D array is stored as a linear block of contiguous memory. The two
common ways of storage are:
1. Row-Major Order: Elements are stored row-wise (left to right, then top to
bottom).
2. Column-Major Order: Elements are stored column-wise (top to bottom, then
left to right).
The address of an element A[J, K] in a row-major order storage can be computed
using:
LOC(A[J, K]) = Base(A) + w * [(J - 1) * n + (K - 1)]
where:
● Base(A) is the starting memory address.
● w is the size of each element in bytes.
● m is the number of rows, and n is the number of columns.
● J and K are row and column indices, respectively.
For column-major order, the formula is:
LOC(A[J, K]) = Base(A) + w * [(K - 1) * m + (J - 1)]
where columns are stored first instead of rows.
Example Calculation
Given a 3 × 4 matrix A stored in row-major order with Base(A) = 200 and w = 4
bytes per element, find LOC(A[2,3]):
LOC(A[2,3]) = 200 + 4 * [(2 - 1) * 4 + (3 - 1)]
= 200 + 4 * [1 * 4 + 2]
= 200 + 4 * 6
= 224
Thus, A[2,3] is stored at memory address 224.
Three-Dimensional Arrays
A three-dimensional array consists of multiple 2D arrays stacked together, referred
to as pages or layers.
● Each element is indexed by three subscripts (J, K, L).
● Example: A 2 × 4 × 3 array B represents a data structure with 2 layers, 4
rows, and 3 columns.
Memory storage follows the same row-major or column-major order, extended to 3
dimensions.
General Multi-Dimensional Arrays
In programming, multi-dimensional arrays can have more than 3 dimensions, though
they are rarely used beyond 3D due to complexity.
In C, a 2D array is declared as:
int A[3][4]; // 3 rows, 4 columns
A 3D array is declared as:
int B[2][4][3]; // 2 layers, 4 rows, 3 columns
Key Points about Multi-Dimensional Arrays
● Efficient storage: They provide structured data storage for tables, matrices,
and 3D data structures.
● Accessing elements: Indexing requires multiple subscripts (row, column, page,
etc.).
● Memory calculations: Addresses can be computed using row-major or
column-major order formulas.
● Application: Used in scientific computing, graphics processing, and databases.
Multi-dimensional arrays are essential in programming for storing and manipulating
complex data structures efficiently.
4.7 Matrices and Sparse Matrices
Definition of Matrices
A matrix is a rectangular arrangement of elements (numbers or symbols) in rows and
columns. If a matrix has m rows and n columns, it is called an m × n matrix.
A square matrix is an n × n matrix, where the number of rows and columns is the
same. The main diagonal of a square matrix consists of elements where the row index
equals the column index.
Basic Matrix Operations
1. Matrix Addition: If A and B are m × n matrices, their sum C = A + B is
obtained by adding corresponding elements.
2. Scalar Multiplication: If A is an m × n matrix and k is a scalar (constant), then k
× A is obtained by multiplying each element of A by k.
3. Matrix Multiplication: If A is an m × p matrix and B is a p × n matrix, their
product C = A × B results in an m × n matrix, Each element of C is obtained by
taking the dot product of the corresponding row of A and column of B.
Example of Matrix Multiplication
Consider:
The product C = A × B is:
Sparse Matrices
A sparse matrix is a matrix in which most of the elements are zero. These matrices
often appear in scientific computing, engineering, and databases.
Types of Sparse Matrices
1. Triangular Matrix: Nonzero elements appear only on or below the main diagonal.
2. Tridiagonal Matrix: Nonzero elements appear only on the main diagonal and
adjacent diagonals.
3. Diagonal Matrix: Only the diagonal elements are nonzero.
Efficient Storage of Sparse Matrices
Since storing zero values wastes memory, we can store only the nonzero elements in a
linear array.
For example, a lower triangular matrix can be stored in row-major or column-major
order in a one-dimensional array to save space.
Example of Sparse Matrix Storage
If we store only the nonzero elements, we can use a 1D array:
Instead of storing a full n × n array, we only store relevant elements, reducing
memory usage significantly.
Key Takeaways
● Matrices are used in scientific computing, graphics, and data processing.
● Matrix multiplication is fundamental but computationally expensive (O(n³)
complexity).
● Sparse matrices help save memory by storing only nonzero elements.
● Efficient storage techniques include compressed storage formats for fast
retrieval.
Matrices and sparse matrices play a vital role in computational mathematics and data
structures.
4.8 Searching and Sorting Techniques Using Arrays
Searching in Arrays
Searching refers to the process of locating a specific ITEM in an array DATA. The
search is successful if the item is found; otherwise, it is unsuccessful. Searching
algorithms are broadly classified into:
1. Linear Search (Sequential Search)
2. Binary Search (Efficient for Sorted Arrays)
Linear Search
Linear search is the simplest method, where each element of the array is compared
sequentially with the desired item.
● Algorithm:
1. Start at the first element.
2. Compare it with the target item.
3. If a match is found, return the position.
4. If not, move to the next element.
5. If the array is exhausted, return "Not Found".
● Time Complexity: O(n)
● Space Complexity: O(1)
● Advantage: Works on both sorted and unsorted arrays.
● Disadvantage: Inefficient for large datasets.
Binary Search
Binary search is an efficient algorithm that applies only to sorted arrays. It repeatedly
divides the search interval in half:
● Algorithm:
1. Set BEG = LB and END = UB.
2. Find MID = (BEG + END)/2.
3. If DATA[MID] == ITEM, return MID.
4. If ITEM < DATA[MID], update END = MID - 1.
5. If ITEM > DATA[MID], update BEG = MID + 1.
6. Repeat until BEG > END (unsuccessful search).
● Time Complexity: O(log n)
● Space Complexity: O(1)
● Advantage: Much faster than linear search.
● Disadvantage: Only works for sorted arrays.
Sorting in Arrays
Sorting refers to arranging elements in ascending or descending order. Several
algorithms exist for sorting arrays, with different performance characteristics.
Common Sorting Algorithms
1. Bubble Sort
○ Repeatedly swaps adjacent elements if they are in the wrong order.
○ Time Complexity: O(n²) (inefficient for large lists).
2. Insertion Sort
○ Builds the sorted list by inserting each element at the correct position.
○ Time Complexity: O(n²) (efficient for small or nearly sorted data).
3. Selection Sort
○ Finds the smallest element and swaps it with the first unsorted element.
○ Time Complexity: O(n²) (not efficient for large lists).
4. Merge Sort
○ Divides the list into halves, sorts each recursively, and merges them.
○ Time Complexity: O(n log n) (good for large datasets).
5. Quick Sort
○ Selects a pivot, partitions the array, and sorts recursively.
○ Time Complexity: O(n log n) (fast in practice).
Example: Bubble Sort Algorithm
void bubbleSort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
Key Takeaways
● Searching helps locate elements in an array.
● Sorting arranges elements to optimize search and retrieval.
● Binary Search is the fastest for sorted arrays.
● Quick Sort and Merge Sort are the best general-purpose sorting algorithms.
Chapter 5
Unit 5: Linked Lists - Concepts
5.1 Definition
5.2 Representation of Singly Linked List in Memory
5.3 Traversing a Singly Linked List
5.4 Searching in a Singly Linked List
5.5 Memory Allocation
5.6 Garbage Collection
5.7 Insertion into a Singly Linked List
5.8 Deletion from a Singly Linked List
5.9 Doubly Linked List
5.10 Header Linked List
5.11 Circular Linked List
5.1 Definition
A list is a collection of data items arranged in a linear order. In everyday use, lists
allow adding and removing elements, such as in a shopping list where new items can be
added and existing ones removed.
In data processing, lists are commonly used to store and manipulate data. One way to
store a list is by using arrays, but arrays have some limitations:
● Inserting and deleting elements in an array is expensive.
● Arrays occupy a fixed block of memory, making resizing difficult.
To overcome these problems, a linked list is used.
● In a linked list, each element contains:
1. Data – Stores the actual information.
2. Pointer (Link field) – Stores the address of the next element in the
sequence.
● Unlike arrays, elements in a linked list do not need to be stored in
consecutive memory locations.
● This structure makes insertion and deletion operations more efficient
compared to arrays.
A linked list is a type of dynamic data structure that allows easy modification, making
it useful for applications like word processing where frequent editing is required.
5.2 Representation of Linked Lists in Memory
A linked list is stored in memory using two linear arrays:
1. INFO Array – Stores the data of each node.
2. LINK Array – Stores the pointer (next address) of each node.
Additionally, two special variables are used:
● START – Stores the location of the first node in the list.
● NULL – A special value (usually 0) indicating the end of the list.
Unlike arrays, linked list nodes do not need to be stored in consecutive memory
locations. Multiple linked lists can be stored using the same INFO and LINK arrays,
with separate pointer variables for each list.
Example : Linked List of Characters
Here is how a linked list storing the character string "BU BCA" can be represented
using INFO and LINK arrays clearly:
Assume the starting point is index 5.
The traversal steps are as follows:
● START = 5 → INFO[5] = B (First character)
● LINK[5] = 2 → INFO[2] = U
● LINK[2] = 8 → INFO[8] = (blank)
● LINK[8] = 3 → INFO[3] = B
● LINK[3] = 7 → INFO[7] = C
● LINK[7] = 1 → INFO[1] = A
● LINK[1] = 0 → End of list (NULL)
This clearly represents the linked list containing the characters in the correct
sequence:
B → U → (blank) → B → C → A → NULL
Conclusion
● A linked list can be stored using INFO and LINK arrays with separate
pointers.
● This structure is useful for managing dynamic datasets such as brokerage
clients, test scores, or employee records.
5.3 Traversing a Linked List
A linked list is stored in memory using INFO and LINK arrays, with START pointing
to the first node and NULL marking the end. Traversing a linked list means visiting
each node exactly once to process its data.
Algorithm for Traversing a Linked List
A pointer variable (PTR) is used to keep track of the current node. The LINK field
helps move from one node to the next.
Steps to Traverse a Linked List:
1. Initialize PTR = START (begin at the first node).
2. Repeat until PTR = NULL:
○ Process INFO[PTR] (perform the required operation).
○ Update PTR to LINK[PTR] (move to the next node).
3. End traversal when PTR reaches NULL.
● Diagram to be drawn: A linked list diagram showing PTR moving from one node
to another using LINK[PTR].
Example: Printing All Elements of a Linked List
The PRINT procedure prints all elements in the linked list by traversing it.
Steps:
1. Set PTR = START.
2. While PTR ≠ NULL:
○ Print INFO[PTR].
○ Update PTR to LINK[PTR].
3. End when PTR = NULL.
This follows the traversal algorithm, replacing the processing step with printing
INFO[PTR].
Code:
#include <stdio.h>
#include <stdlib.h>
typedef struct node {
char INFO;
struct node *LINK;
} NODE;
int main() {
NODE *START = (NODE *)malloc(sizeof(NODE));
NODE *second = (NODE *)malloc(sizeof(NODE));
NODE *third = (NODE *)malloc(sizeof(NODE));
NODE *fourth = (NODE *)malloc(sizeof(NODE));
NODE *fifth = (NODE *)malloc(sizeof(NODE));
NODE *sixth = (NODE *)malloc(sizeof(NODE));
START->INFO = 'B';
START->LINK = second;
second->INFO = 'U';
second->LINK = third;
third->INFO = ' ';
third->LINK = fourth;
fourth->INFO = 'B';
fourth->LINK = fifth;
fifth->INFO = 'C';
fifth->LINK = sixth;
sixth->INFO = 'A';
sixth->LINK = NULL;
NODE *PTR = START;
while (PTR != NULL) {
printf("%c", PTR->INFO);
PTR = PTR->LINK;
}
free(START);
free(second);
free(third);
free(fourth);
free(fifth);
free(sixth);
return 0;
}
Example : Counting the Number of Nodes in a Linked List
The COUNT procedure determines the total number of elements in a linked list.
Steps:
1. Initialize counter NUM = 0.
2. Initialize pointer PTR = START.
3. While PTR ≠ NULL, repeat:
○ Increment NUM = NUM + 1.
○ Move PTR to LINK[PTR].
4. End traversal when PTR = NULL.
This procedure traverses the list like the previous algorithm, but instead of
processing data, it increments a counter to count nodes.
Conclusion
● Traversing a linked list allows processing each element once.
● The PRINT procedure outputs all elements.
● The COUNT procedure calculates the total number of nodes.
● Most list processing procedures follow the same structure with different
processing steps.
5.4 Searching a Singly Linked List
A linked list stored in memory can be searched to find a specific ITEM. The search
operation returns the location (LOC) of the node where the ITEM is first found. There
are two approaches for searching:
1. Searching an Unsorted Linked List
2. Searching a Sorted Linked List
Searching an Unsorted Linked List
If the linked list is not sorted, the search is performed sequentially. The algorithm:
1. Uses a pointer variable (PTR) to traverse the list.
2. Compares INFO[PTR] with the ITEM.
3. If found, LOC = PTR, and the search stops.
4. If the end of the list (NULL) is reached, LOC = NULL (search unsuccessful).
Steps of the Algorithm (Algorithm - SEARCH)
1. Set PTR = START (initialize pointer).
2. While PTR ≠ NULL, do:
○ If INFO[PTR] = ITEM, then set LOC = PTR and exit.
○ Otherwise, update PTR = LINK[PTR] (move to the next node).
3. If not found, set LOC = NULL.
Code:
#include <stdio.h>
#include <stdlib.h>
// Define structure for a node
struct Node {
int data;
struct Node* next;
};
// Function to create a new node
struct Node* createNode(int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// Function to search for an item in the linked list
struct Node* search(struct Node* start, int item) {
struct Node* ptr = start; // Initialize pointer PTR to START
while (ptr != NULL) { // Traverse the list while PTR is not
NULL
if (ptr->data == item) {
return ptr; // If found, return the node
}
ptr = ptr->next; // Move to the next node
}
return NULL; // If not found, return NULL
}
// Function to display the linked list
void displayList(struct Node* head) {
struct Node* temp = head;
while (temp != NULL) {
printf("%d -> ", temp->data);
temp = temp->next;
}
printf("NULL\n");
}
int main() {
// Creating a sample linked list: 10 -> 20 -> 30 -> 40 -> NULL
struct Node* start = createNode(10);
start->next = createNode(20);
start->next->next = createNode(30);
start->next->next->next = createNode(40);
printf("Linked List: ");
displayList(start);
int item;
printf("Enter item to search: ");
scanf("%d", &item);
struct Node* result = search(start, item);
if (result != NULL) {
printf("Item %d found at address %p\n", item, result);
} else {
printf("Item %d not found in the list.\n", item);
}
return 0;
}
Searching a Sorted Linked List
If the linked list is sorted, the search can be optimized by stopping early when ITEM
is found to be greater than the current node's data.
Steps of the Algorithm (SRCHSL)
1. Set PTR = START.
2. While PTR ≠ NULL:
○ If ITEM < INFO[PTR], move to the next node (PTR = LINK[PTR]).
○ If ITEM = INFO[PTR], set LOC = PTR and exit.
○ If ITEM > INFO[PTR], set LOC = NULL and exit (search unsuccessful).
3. If not found, set LOC = NULL.
Even though the list is sorted, the worst-case time complexity remains O(n) because
binary search is not possible in a linked list.
Conclusion
● Unsorted lists require a linear search that checks each element sequentially.
● Sorted lists allow early termination, but the time complexity remains O(n).
● Unlike arrays, binary search cannot be used in linked lists since random
access is not possible.
5.5 Memory Allocation
Introduction to Memory Allocation
Memory allocation in C refers to the process of reserving memory space for program
execution. C provides two types of memory allocation:
1. Static Memory Allocation – Memory size is fixed at compile time.
2. Dynamic Memory Allocation – Memory is allocated at runtime as needed.
Dynamic memory allocation allows efficient memory usage and is particularly useful for
linked lists, trees, graphs, and dynamic arrays.
Types of Memory Allocation in C
1. Static Memory Allocation
● Done at compile time.
● The size of variables is fixed.
● Memory is allocated in the stack.
Example:
int arr[10]; // Allocates memory for 10 integers at compile time
Limitation: The size of arr cannot be changed during runtime.
2. Dynamic Memory Allocation
● Done at runtime using functions from stdlib.h.
● Memory is allocated in the heap.
● Provides flexibility to allocate and free memory as needed.
Dynamic Memory Allocation Functions
Function Description
malloc() Allocates a block of memory but does not initialize it.
calloc() Allocates a block of memory and initializes it to zero.
realloc() Resizes an existing allocated memory block.
free() Frees dynamically allocated memory.
Using malloc()
● Allocates memory but does not initialize it.
● Returns NULL if memory allocation fails.
Example:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr;
ptr = (int *)malloc(5 * sizeof(int)); // Allocate memory for 5
integers
if (ptr == NULL) {
printf("Memory allocation failed\n");
return 1;
}
for (int i = 0; i < 5; i++)
ptr[i] = i * 10; // Assign values
printf("Allocated Memory Values: ");
for (int i = 0; i < 5; i++)
printf("%d ", ptr[i]);
free(ptr); // Free memory
return 0;
}
Using calloc()
● Allocates memory and initializes all elements to zero.
Example:
int *ptr;
ptr = (int *)calloc(5, sizeof(int)); // Allocates memory for 5 integers
and initializes them to zero
Using realloc()
● Used to resize previously allocated memory.
Example:
ptr = (int *)realloc(ptr, 10 * sizeof(int));
// Resizes memory to hold 10 integers
Using free()
● Deallocates memory and prevents memory leaks.
Example:
free(ptr);
ptr = NULL; // Avoid dangling pointer issues
5.5.2 AVAIL List (Free-Storage List)
The AVAIL list is a mechanism used in dynamic memory allocation to manage free
memory efficiently. It is a linked list of all free memory blocks that can be reused
for future allocations.
Purpose of AVAIL List
● Keeps track of memory locations that have been freed.
● Helps reduce memory fragmentation.
● Improves memory management by reusing freed nodes.
Working of AVAIL List
1. When a new node is needed:
○ A node is taken from the AVAIL list.
○ The AVAIL pointer is updated to point to the next available node.
2. When a node is deleted:
○ The deleted node is returned to the AVAIL list.
○ This allows memory to be reused instead of requesting new memory.
Best Practices for Memory Allocation
1. Always check if malloc() or calloc() returns NULL.
2. Use free() to release unused memory.
3. Set pointers to NULL after freeing memory.
4. Avoid memory leaks by ensuring every allocation is freed.
5. Use tools like Valgrind to detect memory leaks.
By properly managing memory allocation and implementing an AVAIL list, programs run
efficiently without unnecessary memory consumption.
5.6 Garbage Collection
5.6.1 Introduction to Garbage Collection
● When nodes are deleted, their memory must be reclaimed for future use.
● Instead of immediately adding deleted nodes to the AVAIL list, garbage
collection is used.
● Garbage collection is a process that automatically finds and frees unused
memory.
5.6.2 Methods of Garbage Collection
1. Immediate Reinsertion
● When a node is deleted, it is immediately added back to the AVAIL list.
● This ensures that free space is always available for new insertions.
● However, this method can be time-consuming if frequent insertions and
deletions occur.
2. System-Level Garbage Collection
● The operating system periodically collects unused memory and adds it to the
AVAIL list.
● The process involves two steps:
○ The system tags all active (in-use) nodes.
○ It collects all untagged (unused) nodes and moves them to the AVAIL
list.
● Garbage collection may occur when:
○ The AVAIL list is nearly empty.
○ The CPU is idle, allowing time for memory cleanup.
● Diagram to be drawn: A memory scan identifying and reclaiming unused
memory cells.
5.6.3 Overflow and Underflow
1. Overflow
● Occurs when no free memory is available for insertion (AVAIL = NULL).
● The system may print "OVERFLOW" to indicate a memory shortage.
● The solution is to allocate additional memory or handle errors gracefully.
2. Underflow
● Occurs when trying to delete from an empty list (START = NULL).
● The system may print "UNDERFLOW" to indicate that no nodes are available for
deletion.
● The solution is to check if START is NULL before attempting deletion.
5.6.4 Comparison of Memory Management Approaches
Method Description Advantages Disadvantages
Immediate Adds deleted nodes to Ensures free Can slow down
Reinsertion AVAIL immediately space is available execution
System Garbage Periodically scans More efficient in Requires additional
Collection memory for unused large systems processing
nodes
5.6.5 Conclusion
● Memory allocation is managed using the AVAIL list, which tracks free space.
● Deleted nodes are returned to AVAIL or reclaimed later by garbage
collection.
● Garbage collection reclaims unused memory automatically when necessary.
● Overflow happens when AVAIL = NULL, meaning no free space is available.
● Underflow happens when START = NULL, meaning the list is already empty.
5.7 Insertion into a Linked List
Insertion in a linked list involves adding a new node at a specific location. This process
requires:
1. Allocating memory for the new node from the AVAIL list.
2. Updating pointers to ensure correct linking between nodes.
There are three main types of insertion:
1. Inserting at the Beginning
2. Inserting after a Given Node
3. Inserting into a Sorted Linked List
5.7.1 Memory Allocation for Insertion
● A new node is taken from the AVAIL list, which tracks free memory locations.
● If AVAIL = NULL, the list is full, and insertion is not possible (OVERFLOW
condition).
● The first available node is removed from AVAIL, and its memory is assigned to
the new node.
Steps for Memory Allocation:
1. Check if AVAIL = NULL (if true, print "OVERFLOW").
2. Remove the first node from the AVAIL list (NEW := AVAIL, AVAIL :=
LINK[AVAIL]).
3. Store the new data (ITEM) into INFO[NEW].
● Diagram to be drawn: Pointer manipulation when assigning a new node from
AVAIL.
5.7.2 Inserting at the Beginning of a List
● If there is no specific order in the list, inserting at the beginning is the
simplest method.
● The new node becomes the first node, and its pointer links to the previous first
node.
Algorithm 5.4: INSFIRST (Insert at the Beginning)
1. If AVAIL = NULL, print "OVERFLOW" and exit.
2. Remove a node from AVAIL (NEW := AVAIL, AVAIL := LINK[AVAIL]).
3. Store ITEM in INFO[NEW].
4. Set LINK[NEW] := START (new node points to old first node).
5. Update START := NEW (new node becomes the first node).
6. Exit.
Code:
#include <stdio.h>
#include <stdlib.h>
// Structure for a linked list node
typedef struct Node {
int data;
struct Node* next;
} Node;
Node* START = NULL; // Head of the main linked list
Node* AVAIL = NULL; // Head of the available list
(free list)
// Function to get a new node from AVAIL
void insertAtBeginning(int ITEM) {
if (AVAIL == NULL) {
printf("OVERFLOW\n");
return;
}
// Remove a node from AVAIL
Node* NEW = AVAIL;
AVAIL = AVAIL->next; // Update AVAIL to next
available node
// Store ITEM in new node
NEW->data = ITEM;
// Set LINK[NEW] := START (new node points to old
first node)
NEW->next = START;
// Update START := NEW (new node becomes the
first node)
START = NEW;
}
// Function to initialize AVAIL with N free nodes
void initializeAvailList(int N) {
for (int i = 0; i < N; i++) {
Node* temp = (Node*)malloc(sizeof(Node));
temp->next = AVAIL;
AVAIL = temp;
}
}
// Function to display the linked list
void displayList() {
Node* temp = START;
printf("List: ");
while (temp) {
printf("%d -> ", temp->data);
temp = temp->next;
}
printf("NULL\n");
}
int main() {
initializeAvailList(5); // Create 5 available
nodes
insertAtBeginning(10);
insertAtBeginning(20);
insertAtBeginning(30);
displayList(); // Expected output: 30 -> 20 -> 10
-> NULL
return 0;
}
5.7.3 Inserting After a Given Node
● A new node can be inserted after a specific node (A) so that it appears before
the next node (B).
● If LOC = NULL, the node is inserted at the beginning, otherwise, it is inserted
after node LOC.
Algorithm : INSLOC (Insert After a Given Node)
1. If AVAIL = NULL, print "OVERFLOW" and exit.
2. Remove a node from AVAIL (NEW := AVAIL, AVAIL := LINK[AVAIL]).
3. Store ITEM in INFO[NEW].
4. If LOC = NULL:
○ Insert at the beginning (LINK[NEW] := START, START := NEW).
5. Otherwise:
○ Insert after node LOC (LINK[NEW] := LINK[LOC], LINK[LOC] :=
NEW).
6. Exit.
Code:
#include <stdio.h>
#include <stdlib.h>
// Structure for a linked list node
typedef struct Node {
int data;
struct Node* next;
} Node;
Node* START = NULL; // Head of the main linked list
Node* AVAIL = NULL; // Head of the available list (free list)
// Function to get a new node from AVAIL and insert it after LOC
void INSLOC(int ITEM, Node* LOC) {
if (AVAIL == NULL) {
printf("OVERFLOW\n");
return;
}
// Remove a node from AVAIL
Node* NEW = AVAIL;
AVAIL = AVAIL->next; // Update AVAIL to next available node
// Store ITEM in new node
NEW->data = ITEM;
if (LOC == NULL) { // Insert at the beginning
NEW->next = START;
START = NEW;
} else { // Insert after LOC
NEW->next = LOC->next;
LOC->next = NEW;
}
}
// Function to initialize AVAIL with N free nodes
void initializeAvailList(int N) {
for (int i = 0; i < N; i++) {
Node* temp = (Node*)malloc(sizeof(Node));
temp->next = AVAIL;
AVAIL = temp;
}
}
// Function to display the linked list
void displayList() {
Node* temp = START;
printf("List: ");
while (temp) {
printf("%d -> ", temp->data);
temp = temp->next;
}
printf("NULL\n");
}
// Function to find a node by value
Node* findNode(int value) {
Node* temp = START;
while (temp && temp->data != value) {
temp = temp->next;
}
return temp; // Returns NULL if not found
}
int main() {
initializeAvailList(5); // Create 5 available nodes
INSLOC(10, NULL); // Insert 10 at the beginning
INSLOC(20, NULL); // Insert 20 at the beginning
INSLOC(30, NULL); // Insert 30 at the beginning
displayList(); // Expected: 30 -> 20 -> 10 -> NULL
Node* loc = findNode(20);
INSLOC(25, loc); // Insert 25 after 20
displayList(); // Expected: 30 -> 20 -> 25 -> 10 -> NULL
return 0;
}
5.7.4 Inserting into a Sorted Linked List
● If the list is sorted, the new node must be placed in the correct position to
maintain order.
● Two steps are needed:
1. Find the correct location to insert the new node.
2. Insert the node after the identified location.
Step 1: Finding the Location (Algorithm - FINDA)
● The list is traversed, and the pointer variable SAVE tracks the last node
before ITEM.
● If the list is empty or ITEM is smaller than the first node, insert at the
beginning.
Procedure: FINDA(INFO, LINK, START, ITEM, LOC)
1. If START = NULL, set LOC = NULL and return.
2. If ITEM < INFO[START], set LOC = NULL and return.
3. Set SAVE := START and PTR := LINK[START].
4. While PTR ≠ NULL:
○ If ITEM < INFO[PTR], set LOC := SAVE and return.
○ Otherwise, update SAVE := PTR, PTR := LINK[PTR].
5. Set LOC := SAVE and return.
Step 2: Insert the Node in the Sorted List (Algorithm - INSERT)
1. Use FINDA to find the correct location (LOC).
2. Use INSLOC to insert ITEM after LOC.
Algorithm 5.7: INSERT(INFO, LINK, START, AVAIL, ITEM)
1. Call FINDA(INFO, LINK, START, ITEM, LOC).
2. Call INSLOC(INFO, LINK, START, AVAIL, LOC, ITEM).
3. Exit.
5.7.6 Copying a Linked List
● Sometimes, a new list needs to be created by copying an existing list.
● The copying process follows insertion algorithms to add elements one by one.
● A null list is first created (NEW_LIST = NULL), and then elements are added in
sequence.
5.7.7 Conclusion
● Insertion in a linked list requires memory allocation and pointer updates.
● Three insertion methods exist:
1. At the beginning (simplest method).
2. After a given node (specific placement).
3. In a sorted list (maintaining order).
● Algorithms ensure proper insertion while handling overflow conditions.
● Linked lists provide flexibility, but pointer manipulation is crucial for
correctness.
5.8 Deletion from a Linked List
Deleting a node from a linked list involves:
1. Updating pointers to remove the node from the list.
2. Returning the deleted node’s memory to the AVAIL list for reuse.
To delete a node N, we need to update the pointer of the previous node (A) so that it
now points to the node after N (B).
5.8.1 Deletion Process
● If N is deleted, the previous node (A) must point to the next node (B).
● The deleted node (N) is added to the AVAIL list so that its memory can be
reused.
● Special cases:
○ If N is the first node, START is updated to point to B.
○ If N is the last node, A must contain NULL.
5.8.3 Deletion Algorithms
There are two types of deletion algorithms:
1. Deleting a node after a given node
2. Deleting a node with a specific ITEM
For both cases, the deleted node is returned to the AVAIL list for future use.
5.8.4 Deleting the Node After a Given Node
Given:
● LOC → Location of the node to be deleted.
● LOCP → Location of the node before LOC.
Algorithm: DEL (Delete a Node at a Given Location)
1. If LOCP = NULL, update START := LINK[START] (delete first node).
2. Else, update LINK[LOCP] := LINK[LOC] (skip the node being deleted).
3. Return the deleted node to the AVAIL list:
○ LINK[LOC] := AVAIL
○ AVAIL := LOC
4. Exit.
5.8.5 Deleting a Node with a Given ITEM
Before deletion, we must search for the node containing ITEM and its preceding node
(LOCP).
Step 1: Finding the Node (Algorithm 5.9 - FINDB)
● Traverse the linked list using a pointer (PTR).
● Keep track of the previous node using SAVE.
● Stop when INFO[PTR] = ITEM.
Procedure : FINDB (Find a Node to Delete)
1. If START = NULL, set LOC = NULL and return.
2. If INFO[START] = ITEM, set LOC = START, LOCP = NULL, and return.
3. Set SAVE = START, PTR = LINK[START].
4. While PTR ≠ NULL, repeat:
○ If INFO[PTR] = ITEM, set LOC = PTR, LOCP = SAVE, and return.
○ Else, update SAVE = PTR, PTR = LINK[PTR].
5. If ITEM not found, set LOC = NULL.
Step 2: Deleting the Found Node (Algorithm - DELETE)
1. Call FINDB(INFO, LINK, START, ITEM, LOC, LOCP).
2. If LOC = NULL, print "ITEM not in list" and exit.
3. If LOCP = NULL, update START := LINK[START] (delete first node).
4. Else, update LINK[LOCP] := LINK[LOC].
5. Return the deleted node to AVAIL:
○ LINK[LOC] := AVAIL
○ AVAIL := LOC
6. Exit.
5.8.7 Conclusion
● Deleting a node requires updating pointers and returning memory to the AVAIL
list.
● If the first node is deleted, START is updated.
● If the last node is deleted, the previous node points to NULL.
● Two deletion algorithms exist:
1. Deleting after a given node (DEL)
2. Deleting a node with a given ITEM (DELETE)
● Efficient pointer management ensures correct deletion without breaking the
linked list structure.
5.9 Doubly Linked List
A doubly linked list (DLL) is an advanced version of a singly linked list, where each
node contains two pointers:
1. Next pointer (Forward link) – Points to the next node in the sequence.
2. Previous pointer (Backward link) – Points to the previous node in the sequence.
This bidirectional linking allows traversal in both forward and backward directions,
making insertions and deletions more efficient than in a singly linked list.
5.9.1 Structure of a Doubly Linked List
Each node in a doubly linked list contains three fields:
1. INFO – Stores the actual data.
2. PREV – Pointer to the previous node in the list.
3. NEXT – Pointer to the next node in the list.
Representation of a Doubly Linked List Node in C:
struct Node {
int data;
struct Node* prev;
struct Node* next;
};
5.9.2 Advantages of a Doubly Linked List
✅ Bidirectional Traversal – Can be traversed in both forward and backward
directions.
✅ Efficient Deletions – Deleting a node is faster, as the previous node is directly
accessible.
✅ Efficient Insertions – Insertions in both directions are easier compared to a singly
linked list.
✅ More Flexible than Singly Linked Lists – Can be used in applications requiring
two-way navigation.
5.9.3 Disadvantages of a Doubly Linked List
❌ Extra Memory Overhead – Each node requires an extra pointer (PREV).
❌ Slightly More Complex – Managing two pointers per node requires careful pointer
updates during insertion and deletion.
5.9.4 Types of Doubly Linked Lists
1. Regular Doubly Linked List – Both head and tail are explicitly stored.
2. Circular Doubly Linked List – The last node points to the first node, making
the list circular.
5.9.5 Operations on a Doubly Linked List
1. Traversing a Doubly Linked List
● Can be done forward (using NEXT pointers) or backward (using PREV pointers).
Algorithm for Forward Traversal:
1. Start from the head node (START).
2. While PTR != NULL:
○ Print INFO[PTR].
○ Move to NEXT[PTR].
3. Exit.
Algorithm for Backward Traversal:
1. Start from the last node (TAIL).
2. While PTR != NULL:
○ Print INFO[PTR].
○ Move to PREV[PTR].
3. Exit.
2. Insertion in a Doubly Linked List
Insertion can occur:
1. At the beginning
2. At the end
3. At a specific position
Algorithm for Inserting at the Beginning:
1. Allocate memory for a new node.
2. Set NEW->INFO = ITEM.
3. Set NEW->NEXT = START.
4. Set NEW->PREV = NULL.
5. If START != NULL, update START->PREV = NEW.
6. Update START = NEW.
3. Deletion in a Doubly Linked List
Deletion can occur:
1. At the beginning
2. At the end
3. At a specific position
Algorithm for Deleting a Node:
1. Locate the node to be deleted (LOC).
2. Update PREV[LOC]->NEXT = NEXT[LOC].
3. Update NEXT[LOC]->PREV = PREV[LOC].
4. Free the node (LOC).
5.9.6 Applications of Doubly Linked Lists
📌 Navigation Systems – Used in web browsers for forward and backward navigation.
📌 Music/Video Players – Allows next and previous track switching.
📌 Undo/Redo Operations – Used in text editors and photo editors.
📌 Memory Management – Helps in efficient allocation and deallocation of memory.
5.9.7 Conclusion
● Doubly linked lists offer better flexibility than singly linked lists but require
extra memory.
● They allow efficient insertions and deletions at both ends.
● Useful in applications requiring bidirectional traversal.
● Circular DLLs further enhance flexibility for continuous data processing.
5.10 Header Linked List
A header linked list is a special type of linked list where the first node (header
node) does not contain actual data. Instead, it is used to store list-specific
information such as:
● The number of nodes in the list.
● A pointer to the first actual node in the list.
This header node helps in easier management of the linked list, especially in dynamic
operations like insertion and deletion.
5.10.1 Types of Header Linked Lists
1. Grounded Header Linked List
○ The last node points to NULL (like a regular linked list).
○ Used for simple linear lists with a header node.
2. Circular Header Linked List
○ The last node points to the header node, making the list circular.
○ Useful in applications that require continuous looping.
● Diagram to be drawn: Grounded and Circular Header Linked Lists.
5.10.2 Structure of a Header Linked List
Each node in a header linked list contains:
1. INFO – Stores data (except in the header node).
2. LINK – Points to the next node in the list.
3. HEADER – The first node that stores metadata (like node count).
Example C Structure:
struct Node {
int data;
struct Node* next;
};
struct HeaderNode {
int count;
struct Node* first;
};
5.10.3 Advantages of a Header Linked List
✅ Efficient List Management – The header node helps in tracking list details.
✅ Easier Insertions and Deletions – Avoids special cases when modifying the first
node.
✅ Useful for Special Implementations – Used in polynomial addition, circular lists,
and dynamic data handling.
5.10.4 Operations on a Header Linked List
1. Traversing a Header Linked List
● Start from the header node and move to the first actual node.
● Continue traversing until LINK = NULL (grounded list) or back to the header
node (circular list).
Algorithm for Traversing a Header Linked List
1. Set PTR = HEADER->LINK (skip header node).
2. While PTR != NULL:
○ Print INFO[PTR].
○ Move to LINK[PTR].
3. Exit.
2. Insertion in a Header Linked List
Insertion can occur:
1. At the beginning
2. At the end
3. At a specific position
Algorithm for Inserting at the Beginning:
1. Allocate memory for NEW node.
2. Set NEW->INFO = ITEM.
3. Set NEW->LINK = HEADER->LINK (new node points to old first node).
4. Set HEADER->LINK = NEW (header now points to new node).
5. Increment HEADER->COUNT.
● Diagram to be drawn: Insertion at the beginning of a header linked list.
3. Deletion in a Header Linked List
Algorithm for Deleting a Node:
1. Locate the node to be deleted (LOC).
2. Update previous node’s LINK to skip LOC.
3. Free memory for LOC.
4. Decrement HEADER->COUNT.
5.10.5 Applications of Header Linked Lists
📌 Polynomial Arithmetic – Used for adding and multiplying polynomials.
📌 Dynamic Data Management – Helps in tracking the number of nodes in real-time.
📌 Memory Optimization – Useful in complex linked list implementations like circular
and sparse lists.
5.10.6 Conclusion
● A header linked list simplifies list operations by maintaining list metadata in a
dedicated header node.
● Two types exist:
1. Grounded (ends in NULL)
2. Circular (loops back to header)
● Commonly used in mathematical computations and dynamic data structures.
5.11 Circular Linked List
A circular linked list is a variation of a linked list where:
● The last node points back to the first node, creating a loop.
● It can be singly or doubly linked, allowing continuous traversal.
● It is useful in applications requiring cyclic operations (e.g., CPU scheduling,
buffer management).
5.11.1 Types of Circular Linked Lists
1. Singly Circular Linked List (SCLL)
● Each node has one pointer (NEXT).
● The last node's NEXT pointer points to the first node instead of NULL.
2. Doubly Circular Linked List (DCLL)
● Each node has two pointers (NEXT and PREV).
● The last node’s NEXT points to the first node, and the first node’s PREV
points to the last node.
● Diagram to be drawn: Singly and Doubly Circular Linked List representations.
5.11.2 Structure of a Circular Linked List
Each node in a circular linked list contains:
1. INFO – Stores the actual data.
2. LINK (for SCLL) – Points to the next node in the list.
3. PREV & NEXT (for DCLL) – Points to both next and previous nodes.
Example C Structure for SCLL:
struct Node {
int data;
struct Node* next;
};
Example C Structure for DCLL:
struct Node {
int data;
struct Node* next;
struct Node* prev;
};
5.11.3 Advantages of a Circular Linked List
✅ Efficient Traversal – No need for NULL checks; continuous looping is possible.
✅ Better for Repetitive Tasks – Used in round-robin scheduling and buffer
management.
✅ Easier Queue Implementation – Eliminates the need for special handling of front
and rear pointers.
✅ More Flexible than Singly Linked Lists – Especially in DCLLs, where backward
traversal is possible.
5.11.4 Disadvantages of a Circular Linked List
❌ Complex Implementations – More difficult than regular linked lists due to looping
structure.
❌ Risk of Infinite Loops – Improper handling of pointers can lead to infinite
traversal.
❌ Extra Memory Overhead – DCLLs require additional memory for the PREV
pointer.
5.11.5 Operations on a Circular Linked List
1. Traversing a Circular Linked List
● Unlike a regular linked list, traversal stops when we reach the starting node
again.
Algorithm for Traversing a Circular Linked List (SCLL)
1. Set PTR = START.
2. Repeat until PTR == START again:
○ Print INFO[PTR].
○ Move to NEXT[PTR].
3. Exit.
2. Insertion in a Circular Linked List
Insertion can occur:
1. At the beginning
2. At the end
3. After a specific node
Algorithm for Inserting at the Beginning:
1. Allocate memory for NEW node.
2. Set NEW->INFO = ITEM.
3. If the list is empty, set NEW->NEXT = NEW (points to itself).
4. Otherwise:
○ Set NEW->NEXT = START.
○ Find the last node and update its NEXT pointer to NEW.
○ Update START = NEW.
● Diagram to be drawn: Insertion at the beginning of a circular linked list.
3. Deletion in a Circular Linked List
Deletion can occur:
1. At the beginning
2. At the end
3. A specific node
Algorithm for Deleting the First Node:
1. If the list is empty, print "UNDERFLOW" and exit.
2. If only one node exists:
○ Set START = NULL.
3. Otherwise:
○ Find the last node and update its NEXT pointer to START->NEXT.
○ Update START = START->NEXT.
4. Free the deleted node.
● Diagram to be drawn: Pointer updates after deleting a node from a circular
linked list.
5.11.6 Applications of Circular Linked Lists
📌 Round-Robin Scheduling – Used in operating systems for CPU process scheduling.
📌 Music/Video Playlists – Helps in continuous looping of tracks.
📌 Buffer Management – Used in real-time systems for managing circular queues.
📌 Traffic Light Systems – Helps cycle through traffic signals repeatedly.
5.11.7 Conclusion
● Circular linked lists are useful in applications requiring continuous traversal.
● Singly Circular Linked Lists (SCLL) use only a NEXT pointer, while Doubly
Circular Linked Lists (DCLL) use NEXT and PREV.
● Efficient for queues, scheduling, and cyclic operations.