ULTIMATE DATA STRUCTURES NOTES
📚 TABLE OF CONTENTS
1. Prerequisites
Functions
Recursion
Arrays
Pointers
Structures
2. Introduction to Data Structures
What are Data Structures?
Abstract Data Types (ADT)
Types of Data Structures
Operations on Data Structures
3. Stack
Stack as ADT
Array Implementation of Stack
Multiple Stacks
4. Queue
Queue as ADT
Array Implementation of Queue
Circular Queue
Priority Queue
Double Ended Queue (Deque)
Multiple Queues
5. Linked List
Concept of Linked List vs Array
Singly Linked List
Doubly Linked List
Circular Linked List
Stack Using Linked List
Queue Using Linked List
Reversing a Linked List
6. Tree
Tree Concepts and Terms
Binary Tree
Binary Tree Traversals
Binary Search Tree (BST)
7. Applications of Data Structures
Stack Applications
Trees Applications
8. Summary of Key Concepts
<a id="prerequisites"></a>
0. PREREQUISITES
<a id="functions"></a>
Functions
What are functions?
Functions are blocks of code that perform specific tasks. They help us organize our program into
reusable pieces.
Key Characteristics of Functions:
Reusability: Write once, use many times
Modularity: Break complex programs into manageable pieces
Abstraction: Hide implementation details from the user
Example:
// This function adds two numbers and returns the sum
int add(int a, int b) {
return a + b; // Returns the sum of a and b
}
// This is how you call the function
int result = add(5, 3); // result will be 8
Algorithm Explanation:
1. Function add takes two integer parameters a and b
2. It calculates their sum and returns the result
3. When calling the function with values 5 and 3, it returns 8
<a id="recursion"></a>
Recursion
What is recursion?
When a function calls itself to solve a problem. It breaks a problem into smaller versions of the same
problem.
Key Characteristics of Recursion:
Base Case: A condition that stops the recursion
Recursive Case: The function calls itself with a simpler version of the problem
Call Stack: Each recursive call is added to the call stack
Example: Calculating factorial
// Function to calculate factorial using recursion
int factorial(int n) {
if (n <= 1) { // Base case: factorial of 0 or 1 is 1
return 1;
} else {
return n * factorial(n-1); // Function calls itself with smaller value
}
}
// Usage: factorial(5) = 5 * 4 * 3 * 2 * 1 = 120
Algorithm Explanation:
1. Check if n is 0 or 1 (base case)
2. If true, return 1 (factorial of 0 or 1 is 1)
3. Otherwise, return n * factorial(n-1) (recursive case)
4. For factorial(5):
factorial(5) = 5 * factorial(4)
factorial(4) = 4 * factorial(3)
factorial(3) = 3 * factorial(2)
factorial(2) = 2 * factorial(1)
factorial(1) = 1 (base case reached)
Then unwind: 21 = 2, 32 = 6, 46 = 24, 524 = 120
<a id="arrays"></a>
Arrays
What are arrays?
Collections of similar data items stored at contiguous memory locations.
Key Characteristics of Arrays:
Fixed Size: Size is defined at declaration
Homogeneous Elements: All elements must be of the same data type
Random Access: Can access any element directly using its index
Zero-based Indexing: First element is at index 0
Example:
c
// Declaring and initializing an array
int marks[5] = {85, 75, 90, 67, 82}; // Array of 5 integers
// Accessing array elements
printf("First mark: %d\n", marks[0]); // Arrays start at index 0
printf("Third mark: %d\n", marks[2]); // Access the third element
// Modifying array elements
marks[1] = 95; // Change the second element to 95
Algorithm for Array Operations:
1. Declaration: Allocate contiguous memory for fixed number of elements
2. Access: Calculate address using base address + (index * size of data type)
3. Modification: Direct access to memory location to change value
<a id="pointers"></a>
Pointers (Basic Introduction)
What are pointers?
Variables that store memory addresses of other variables.
Key Characteristics of Pointers:
Address Storage: Store memory addresses, not actual values
Indirection: Access values indirectly through memory addresses
Dynamic Memory: Enable dynamic memory allocation and deallocation
Efficient Parameter Passing: Pass references instead of copying large data
Simple Example:
int number = 10; // Regular integer variable
int *ptr = &number; // Pointer storing address of 'number'
printf("Value: %d\n", number); // Prints: 10
printf("Value using pointer: %d\n", *ptr); // Prints: 10 (dereferencing)
Algorithm for Pointer Operations:
1. Declaration: Create a pointer variable of specific type
2. Assignment: Store address of a variable using & operator
3. Dereferencing: Access value at the address using * operator
<a id="structures"></a>
Structures
What are structures?
User-defined data types that allow storing different types of data items together.
Key Characteristics of Structures:
Heterogeneous Elements: Can store different data types together
Related Data Grouping: Group related data under a single name
Memory Allocation: Contiguous memory allocation for all members
Member Access: Use dot (.) operator to access members
Example:
c
// Define a structure for a student
struct Student {
char name[50]; // Character array for name
int roll_no; // Integer for roll number
float marks; // Float for marks
};
// Creating and using a structure variable
struct Student s1 = {"Alice", 101, 85.5}; // Initialize all members
printf("Name: %s\n", s1.name); // Access using dot operator
printf("Roll No: %d\n", s1.roll_no);
Algorithm for Structure Operations:
1. Definition: Define structure template with members of different types
2. Declaration: Create variables of the structure type
3. Initialization: Assign values to structure members
4. Access: Use dot operator to access individual members
📝 Prerequisites: Oral Exam Questions
1. Functions
What are functions and why are they important in programming?
Explain the difference between function declaration and function definition.
What is the purpose of the return statement in a function?
What is function overloading? Does C support it?
What are the advantages of using functions in a program?
2. Recursion
What is recursion and how does it differ from iteration?
What are the essential components of a recursive function?
What are the advantages and disadvantages of recursion?
Explain how the factorial function works recursively.
How does the call stack work during recursive function calls?
What is stack overflow in the context of recursion?
3. Arrays
What is an array? How is memory allocated for arrays?
Why is index 0 used for the first element in an array?
What are the limitations of arrays in C?
How are multi-dimensional arrays stored in memory?
What is the difference between declaration and initialization of an array?
4. Pointers
What is a pointer and what does it contain?
What is the difference between & and * operators regarding pointers?
What is dereferencing? How do you dereference a pointer?
Why are pointers useful in C programming?
What is a null pointer and when would you use it?
5. Structures
What is a structure in C? How does it differ from an array?
How do you access members of a structure?
Can structures contain other structures? Explain.
What is the difference between a structure and a union?
Why would you use structures in a program?
<a id="introduction"></a>
I. INTRODUCTION
<a id="what-are-data-structures"></a>
What are Data Structures?
Definition: Data structures are specialized formats for organizing, processing, retrieving and storing
data in a computer.
Think of it like: If data is like items, then data structures are like different types of containers to store
these items - each with its own way of adding, removing, and accessing items.
Importance of Data Structures:
Efficiency: Proper data structures can make algorithms more efficient
Organization: Help organize and manage large amounts of data
Problem Solving: Different problems require different data organization techniques
Memory Utilization: Optimize memory usage for specific applications
<a id="abstract-data-types-adt"></a>
Abstract Data Types (ADT)
Definition: An ADT is a mathematical model for data structures, defined by its behavior from the
user's perspective.
Real-life example: Think of a TV remote. You know what buttons do what functions (interface), but
you don't need to know the electronic circuitry inside (implementation) to use it.
Key Characteristics of ADTs:
Abstraction: Hide implementation details from the user
Operations: Define operations that can be performed
Data: Define the data to be stored
Interface: Provide clear interface for interactions
Common ADTs include:
Stack (Last-In-First-Out)
Queue (First-In-First-Out)
List
Tree
<a id="types-of-data-structures"></a>
Types of Data Structures
1. Linear vs Nonlinear:
Linear: Elements form a sequence
Arrays, Linked Lists, Stacks, Queues
Each element has at most one predecessor and one successor
Elements can be traversed sequentially
Nonlinear: Elements don't form a sequence
Trees, Graphs
Elements can have multiple predecessors and successors
Multiple paths between elements
2. Static vs Dynamic:
Static: Fixed size, allocated at compile time
Arrays
Size cannot be changed during execution
Memory allocated once
Dynamic: Size can change during program execution
Linked Lists, Dynamic Arrays
Can grow or shrink as needed
Memory allocated and deallocated during runtime
3. Primitive vs Non-Primitive:
Primitive: Basic data types built into a language
Integer, Float, Character, Boolean
Directly operated upon by machine instructions
Non-Primitive: Derived from primitive data types
Arrays, Lists, Stacks, Queues, Trees, Graphs
More complex operations and structures
<a id="operations-on-data-structures"></a>
Operations on Data Structures
Common operations include:
Insertion: Adding a new element
Can be at the beginning, end, or specific position
May require reorganization of existing elements
Deletion: Removing an element
Can be from beginning, end, or specific position
May require reorganization of remaining elements
Traversal: Accessing each element exactly once
Sequential visit of all elements
Used for operations like printing, searching, etc.
Searching: Finding a specific element
Linear search, binary search, etc.
Efficiency depends on data structure organization
Sorting: Arranging elements in a particular order
Ascending, descending, or custom order
Various algorithms like bubble sort, quick sort, etc.
Merging: Combining two or more data structures
Splitting: Dividing a data structure into parts
📝 Introduction to Data Structures: Oral Exam Questions
1. What are data structures and why are they important in computer science?
2. Differentiate between data structures and abstract data types.
3. Give real-world examples for abstract data types.
4. What is the difference between linear and non-linear data structures? Give examples of each.
5. Explain the difference between static and dynamic data structures with examples.
6. What factors should be considered when choosing a data structure for a specific problem?
7. How does memory allocation differ between static and dynamic data structures?
8. Describe the common operations that can be performed on data structures.
9. What is traversal and why is it important in data structures?
10. How do primitive data structures differ from non-primitive data structures?
11. What is the relationship between algorithms and data structures?
12. Explain how efficiency of operations varies across different data structures.
<a id="stack"></a>
II. STACK
<a id="stack-as-adt"></a>
Stack as ADT
Definition: A stack is a linear data structure that follows the Last-In-First-Out (LIFO) principle.
Real-life example: Think of a stack of plates. You can only take the top plate (last one placed), not
any from the middle.
Key Principles of Stack:
LIFO (Last-In-First-Out): The last element added is the first one to be removed
Restricted Access: Elements can only be added or removed from one end (top)
Sequential Access: Elements are accessed in reverse order of their addition
Basic Operations:
Push: Add an element to the top
Pop: Remove the top element
Peek/Top: View the top element without removing it
IsEmpty: Check if stack is empty
IsFull: Check if stack is full (for array implementation)
Visual Representation:
│ │
│ C │ ← Top (most recently added)
│ B │
│ A │ ← Bottom (least recently added)
└───────┘
<a id="array-implementation-of-stack"></a>
Array Implementation of Stack
c
#include <stdio.h>
#define MAX 5 // Maximum size of stack
// Global variables
int stack[MAX]; // Array to store the stack elements
int top = -1; // Index of top element, -1 means empty stack
// Function to check if stack is full
int isFull() {
if (top == MAX - 1) { // If top is at the last index
return 1; // Return true (stack is full)
} else {
return 0; // Return false (stack is not full)
}
}
// Function to check if stack is empty
int isEmpty() {
if (top == -1) { // If top is -1
return 1; // Return true (stack is empty)
} else {
return 0; // Return false (stack is not empty)
}
}
// Function to add an element to the stack
void push(int item) {
if (isFull()) { // Check if stack is full
printf("Stack Overflow\n"); // Print error message
} else {
top++; // Increment top
stack[top] = item; // Place the item at top position
printf("%d pushed to stack\n", item);
}
}
// Function to remove an element from the stack
int pop() {
if (isEmpty()) { // Check if stack is empty
printf("Stack Underflow\n"); // Print error message
return -1; // Return error value
} else {
int item = stack[top]; // Get the top item
top--; // Decrement top
return item; // Return the item
}
}
// Function to view the top element without removing it
int peek() {
if (isEmpty()) { // Check if stack is empty
printf("Stack is empty\n"); // Print error message
return -1; // Return error value
} else {
return stack[top]; // Return the top item
}
}
// Sample usage
int main() {
push(10); // Add 10 to stack
push(20); // Add 20 to stack
push(30); // Add 30 to stack
printf("Top element: %d\n", peek()); // Should print 30
printf("Popped: %d\n", pop()); // Should print 30
printf("Popped: %d\n", pop()); // Should print 20
printf("Top element after pops: %d\n", peek()); // Should print 10
return 0;
}
Algorithm Explanation for Stack Operations:
1. Initialization:
Create an array of fixed size (MAX)
Initialize top = -1 (indicating empty stack)
2. isFull() Operation:
Check if top == MAX-1
If true, stack is full, return 1
Otherwise, return 0
3. isEmpty() Operation:
Check if top == -1
If true, stack is empty, return 1
Otherwise, return 0
4. push() Operation:
Check if stack is full using isFull()
If full, print "Stack Overflow"
Otherwise:
Increment top
Place the new item at stack[top]
5. pop() Operation:
Check if stack is empty using isEmpty()
If empty, print "Stack Underflow" and return error value (-1)
Otherwise:
Store the value at stack[top] in a variable
Decrement top
Return the stored value
6. peek() Operation:
Check if stack is empty using isEmpty()
If empty, print "Stack is empty" and return error value (-1)
Otherwise, return the value at stack[top] without changing top
<a id="multiple-stacks"></a>
Multiple Stacks
Multiple stacks can be implemented in a single array by:
1. Dividing the array into fixed sections:
Allocate specific sections of the array for each stack
Each stack has its own top pointer
Simple but less memory efficient if stacks grow unevenly
2. Starting stacks from opposite ends of the array and growing towards the middle:
For two stacks, first stack grows from left to right
Second stack grows from right to left
Stacks are full when they meet in the middle
More efficient use of memory
Visual Representation of Two Stacks in One Array:
[0][1][2][3][4][5][6][7][8][9]
↑ ↑
top1 top2
📝 Stack: Oral Exam Questions
1. What is a stack data structure and what is its fundamental principle?
2. Explain the LIFO (Last-In-First-Out) property with an example.
3. List and explain the basic operations that can be performed on a stack.
4. What are the applications of stack in computer science?
5. What happens when you try to pop an element from an empty stack?
6. What happens when you try to push an element into a full stack?
7. How is a stack implemented using an array? What are the limitations of this implementation?
8. What is the time complexity of push, pop, and peek operations in a stack?
9. What are the advantages and disadvantages of implementing a stack using an array?
10. How can multiple stacks be implemented in a single array?
11. Compare the fixed partition and flexible partition methods for implementing multiple stacks.
12. What is stack overflow and stack underflow? When do they occur?
13. How does the call stack work in programming languages?
14. How is memory managed in a stack implementation?
15. How can a stack be implemented without using an array?
<a id="queue"></a>
III. QUEUE
<a id="queue-as-adt"></a>
Queue as ADT
Definition: A queue is a linear data structure that follows the First-In-First-Out (FIFO) principle.
Real-life example: Think of people standing in line for movie tickets. The person who came first gets
served first.
Key Principles of Queue:
FIFO (First-In-First-Out): The first element added is the first one to be removed
Two Ends: Elements are added at one end (rear) and removed from the other end (front)
Sequential Processing: Processing in the exact order of arrival
Basic Operations:
Enqueue: Add an element to the rear/back
Dequeue: Remove an element from the front
Front: Get the front element without removing
IsEmpty: Check if queue is empty
IsFull: Check if queue is full
Visual Representation:
Front Rear
↓ ↓
[A][B][C][D][ ][ ][ ][ ]
<a id="array-implementation-of-queue"></a>
Array Implementation of Queue
c
#include <stdio.h>
#define MAX 5 // Maximum size of queue
// Global variables
int queue[MAX]; // Array to store queue elements
int front = -1; // Index of front element
int rear = -1; // Index of rear element
// Function to check if queue is full
int isFull() {
if (rear == MAX - 1) { // If rear is at the last index
return 1; // Return true (queue is full)
} else {
return 0; // Return false (queue is not full)
}
}
// Function to check if queue is empty
int isEmpty() {
if (front == -1 || front > rear) { // If front is -1 or has crossed rear
return 1; // Return true (queue is empty)
} else {
return 0; // Return false (queue is not empty)
}
}
// Function to add an element to the queue
void enqueue(int item) {
if (isFull()) { // Check if queue is full
printf("Queue Overflow\n"); // Print error message
} else {
if (front == -1) { // If queue was empty
front = 0; // Set front to the first position
}
rear++; // Increment rear
queue[rear] = item; // Place the item at rear position
printf("%d enqueued to queue\n", item);
}
}
// Function to remove an element from the queue
int dequeue() {
if (isEmpty()) { // Check if queue is empty
printf("Queue Underflow\n"); // Print error message
return -1; // Return error value
} else {
int item = queue[front]; // Get the front item
front++; // Increment front
if (front > rear) { // If the last item was dequeued
front = rear = -1; // Reset front and rear
}
return item; // Return the item
}
}
// Function to get the front element without removing it
int getFront() {
if (isEmpty()) { // Check if queue is empty
printf("Queue is empty\n"); // Print error message
return -1; // Return error value
} else {
return queue[front]; // Return the front item
}
}
// Sample usage
int main() {
enqueue(10); // Add 10 to queue
enqueue(20); // Add 20 to queue
enqueue(30); // Add 30 to queue
printf("Front element: %d\n", getFront()); // Should print 10
printf("Dequeued: %d\n", dequeue()); // Should print 10
printf("Dequeued: %d\n", dequeue()); // Should print 20
printf("Front element after dequeues: %d\n", getFront()); // Should print 30
return 0;
}
Algorithm Explanation for Queue Operations:
1. Initialization:
Create an array of fixed size (MAX)
Initialize front = -1 and rear = -1 (indicating empty queue)
2. isFull() Operation:
Check if rear == MAX-1
If true, queue is full, return 1
Otherwise, return 0
3. isEmpty() Operation:
Check if front == -1 OR front > rear
If true, queue is empty, return 1
Otherwise, return 0
4. enqueue() Operation:
Check if queue is full using isFull()
If full, print "Queue Overflow"
Otherwise:
If front == -1, set front = 0 (first element)
Increment rear
Place the new item at queue[rear]
5. dequeue() Operation:
Check if queue is empty using isEmpty()
If empty, print "Queue Underflow" and return error value (-1)
Otherwise:
Store the value at queue[front] in a variable
Increment front
If front > rear (last element was dequeued), reset front and rear to -1
Return the stored value
6. getFront() Operation:
Check if queue is empty using isEmpty()
If empty, print "Queue is empty" and return error value (-1)
Otherwise, return the value at queue[front]
Problem with Simple Queue Implementation:
Space Inefficiency: Once the queue is full and elements are dequeued, the front moves forward
leaving unused space at the beginning
Cannot reuse the free space without resetting the queue completely
<a id="circular-queue"></a>
Circular Queue
A circular queue solves the problem of wasted space in a simple queue by making the array circular
(connecting the end to the beginning).
c
#include <stdio.h>
#define MAX 5 // Maximum size of circular queue
// Global variables
int cqueue[MAX]; // Array to store circular queue elements
int front = -1; // Index of front element
int rear = -1; // Index of rear element
// Function to check if circular queue is full
int isFull() {
if ((front == 0 && rear == MAX - 1) || (front == rear + 1)) {
return 1; // Return true (queue is full)
} else {
return 0; // Return false (queue is not full)
}
}
// Function to check if circular queue is empty
int isEmpty() {
if (front == -1) { // If front is -1
return 1; // Return true (queue is empty)
} else {
return 0; // Return false (queue is not empty)
}
}
// Function to add an element to the circular queue
void enqueue(int item) {
if (isFull()) { // Check if queue is full
printf("Circular Queue Overflow\n"); // Print error message
} else {
if (front == -1) { // If queue was empty
front = 0; // Set front to the first position
}
rear = (rear + 1) % MAX; // Increment rear in circular manner
cqueue[rear] = item; // Place the item at rear position
printf("%d enqueued to circular queue\n", item);
}
}
// Function to remove an element from the circular queue
int dequeue() {
if (isEmpty()) { // Check if queue is empty
printf("Circular Queue Underflow\n"); // Print error message
return -1; // Return error value
} else {
int item = cqueue[front]; // Get the front item
if (front == rear) { // If the last item was dequeued
front = rear = -1; // Reset front and rear
} else {
front = (front + 1) % MAX; // Increment front in circular manner
}
return item; // Return the item
}
}
// Function to get the front element without removing it
int getFront() {
if (isEmpty()) { // Check if queue is empty
printf("Circular Queue is empty\n"); // Print error message
return -1; // Return error value
} else {
return cqueue[front]; // Return the front item
}
}
// Sample usage
int main() {
enqueue(10); // Add 10 to queue
enqueue(20); // Add 20 to queue
enqueue(30); // Add 30 to queue
enqueue(40); // Add 40 to queue
printf("Front element: %d\n", getFront()); // Should print 10
printf("Dequeued: %d\n", dequeue()); // Should print 10
printf("Dequeued: %d\n", dequeue()); // Should print 20
// Now we can add more elements even though the array is "full"
enqueue(50); // Add 50 to queue
enqueue(60); // Add 60 to queue
printf("Front element after changes: %d\n", getFront()); // Should print 30
return 0;
}
Algorithm Explanation for Circular Queue Operations:
1. Initialization:
Create an array of fixed size (MAX)
Initialize front = -1 and rear = -1 (indicating empty queue)
2. isFull() Operation:
Check if (front == 0 AND rear == MAX-1) OR (front == rear+1)
If true, circular queue is full, return 1
Otherwise, return 0
3. isEmpty() Operation:
Check if front == -1
If true, circular queue is empty, return 1
Otherwise, return 0
4. enqueue() Operation:
Check if queue is full using isFull()
If full, print "Circular Queue Overflow"
Otherwise:
If front == -1, set front = 0 (first element)
Update rear = (rear + 1) % MAX (circular increment)
Place the new item at cqueue[rear]
5. dequeue() Operation:
Check if queue is empty using isEmpty()
If empty, print "Circular Queue Underflow" and return error value (-1)
Otherwise:
Store the value at cqueue[front] in a variable
If front == rear (last element), reset front and rear to -1
Otherwise, update front = (front + 1) % MAX (circular increment)
Return the stored value
6. getFront() Operation:
Check if queue is empty using isEmpty()
If empty, print "Circular Queue is empty" and return error value (-1)
Otherwise, return the value at cqueue[front]
Visual Representation of Circular Queue:
[0]
[4] [1]
↓
[3] [2]
<a id="priority-queue"></a>
Priority Queue
A priority queue is a queue where elements