Data Structures - Beginner's Complete Guide pt-00
Data Structures - Beginner's Complete Guide pt-00
Table of Contents
0. Prerequisites
0.1 Functions
0.2 Recursion
0.3 Arrays
0.4 Pointers
Q&A for Prerequisites
II. Stack
Stack as ADT
III. Queue
Queue as ADT
0.1 Functions
📘 Definition
A function is a self-contained block of code designed to perform a specific task. Functions help organize
code into reusable pieces, making programs more modular and easier to maintain.
Think of functions like mini-programs within your main program. They take inputs (parameters), process
them, and can return outputs.
return_type function_name(parameter_list) {
// Function body - code that performs the task
return value; // Optional - returns a value to the caller
}
Example:
In this example:
0.2 Recursion
📘 Definition
Recursion occurs when a function calls itself to solve a problem. It breaks a complex problem into
smaller instances of the same problem.
1. Base case: The condition where the function stops calling itself
2. Recursive case: Where the function calls itself with a simpler version of the problem
The factorial of a number n (written as n!) is the product of all positive integers less than or equal to n.
5! = 5 × 4 × 3 × 2 × 1 = 120
5. Is 1 ≤ 1? Yes, so return 1
6. Now calculate backward: 2 * 1 = 2, 3 * 2 = 6, 4 * 6 = 24, 5 * 24 = 120
0.3 Arrays
📘 Definition
An array is a collection of similar data items stored at contiguous memory locations. Each element can
be accessed directly using its index.
Arrays are one of the simplest data structures. They store multiple items of the same data type.
Key characteristics:
Fixed size (determined at declaration)
Same data type for all elements
Example:
Index 0 1 2 3 4
Value 85 75 90 67 82
After modification:
Index 0 1 2 3 4
Value 85 95 90 67 82
0.4 Pointers
📘 Definition
A pointer is a variable that stores the memory address of another variable.
Pointers are fundamental for understanding more complex data structures like linked lists and trees.
Example:
*pointer accesses the value at the address stored in the pointer (dereferencing)
Visual representation:
Memory:
+-------------------+
| address: &number | --+
| value: 10 | |
+-------------------+ |
|
+-------------------+ |
| address: &ptr | |
| value: &number |---+
+-------------------+
A1: A function returns a value to the caller, while a procedure (typically implemented as a void function in C)
performs actions but doesn't return a value.
A2: Recursion is helpful for problems that can be broken down into similar subproblems, like tree traversals,
and when the solution is easier to express recursively. However, it has more overhead than loops and can
cause stack overflow for deep recursions.
Q3: What happens when you try to access an array element outside its bounds?
A3: C doesn't check array bounds, so accessing outside the array's bounds leads to undefined behavior. It
might access random memory, cause crashes, or corrupt data in your program.
A4: An array name represents the address of its first element but cannot be reassigned. A pointer is a
variable that can be reassigned to point to different memory locations. Arrays allocate memory for all
elements at once, while pointers just store addresses.
A5: A function is like a kitchen appliance (like a blender) that takes specific inputs (ingredients), processes
them in a predefined way (blending), and produces an output (smoothie). You can reuse it multiple times
with different inputs.
Think of data structures as different types of containers for storing items. Just as we might choose different
containers in real life based on what we're storing and how we need to access it, we choose different data
structures based on our specific needs.
Real-life analogy:
An ADT defines:
Real-life example:
Think of a TV remote control. You know what buttons do what functions (the interface), but you don't need
to know about the electronic circuitry inside (the implementation) to use it.
Stack (Last-In-First-Out)
Queue (First-In-First-Out)
List
Tree
Fixed size
Allocated at compile time
Allocated at runtime
Operation Description
Different data structures perform these operations with different efficiencies, which is why choosing the
right data structure for your specific needs is important.
A1: A data structure is a concrete implementation that organizes data in memory, while an abstract data
type (ADT) is a mathematical model that defines the behavior of a data structure without specifying the
implementation details.
Q2: Why do we need different types of data structures when arrays seem sufficient for most tasks?
A2: Different data structures excel at different operations. Arrays are good for random access but poor for
insertions and deletions. Linked lists excel at insertions and deletions but are inefficient for random access.
The right data structure can significantly improve a program's performance.
Q3: Give a real-world example for linear and non-linear data structures.
A3: A line of people waiting for movie tickets is a linear data structure (queue). A family tree is a non-linear
data structure (tree) because each person can have multiple children.
Q4: What factors should you consider when choosing a data structure?
A4: Consider:
Memory constraints
Q5: How does the choice of data structure affect program efficiency?
A5: The choice of data structure directly impacts the time and space complexity of operations. For example,
searching in an unsorted array is O(n), but in a binary search tree, it's O(log n). Using the wrong data
structure can make your program unnecessarily slow or memory-intensive.
II. Stack
Stack as ADT
📘 Definition
A stack is a linear data structure that follows the Last-In-First-Out (LIFO) principle. The last element
added to the stack is the first one to be removed.
Real-life examples:
Visual representation:
│ │
│ 30 │ ← Top (Last In)
│ 20 │
│ 10 │ ← First In
└──────────┘
Basic Operations:
Operation Description
// Global variables
int stack[MAX]; // Array to store the stack elements
int top = -1; // Index of top element, -1 means empty stack
// 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;
}
Step-by-step explanation:
1. Initialize:
Create an array stack of size MAX to hold elements
2. Push Operation:
Check if the stack is full ( top == MAX-1 )
If not full, increment top and place the new element at this position
3. Pop Operation:
Check if the stack is empty ( top == -1 )
If not empty, get the element at top , decrement top , and return the element
4. Peek Operation:
Return the element at top without removing it
peek() returns 30
peek() returns 10
Stack Applications
Parentheses Checker
One common application of stacks is checking for balanced parentheses in an expression.
c
#include <stdio.h>
#include <string.h>
#define MAX 100
// Global variables
char stack[MAX]; // Stack to store opening brackets
int top = -1; // Index of top element
// Sample usage
int main() {
char expr[MAX];
printf("Enter an expression: ");
gets(expr); // Read an expression
if (areBracketsBalanced(expr)) {
printf("Balanced\n");
} else {
printf("Not Balanced\n");
}
return 0;
}
Algorithm Explanation:
4. After scanning the entire expression, if the stack is empty, the expression is balanced. Otherwise, it's not
balanced.
Example:
Not Balanced
Reversing a String
Another application of stacks is to reverse a string.
c
#include <stdio.h>
#include <string.h>
#define MAX 100
// Global variables
char stack[MAX]; // Stack to store characters
int top = -1; // Index of top element
// Pop all characters from stack and overwrite the original string
for (i = 0; i < len; i++) {
str[i] = pop();
}
}
// Sample usage
int main() {
char str[MAX];
printf("Enter a string: ");
gets(str); // Read a string
printf("Original string: %s\n", str);
reverseString(str); // Reverse the string
printf("Reversed string: %s\n", str);
return 0;
}
Algorithm Explanation:
1. Push each character of the input string onto the stack, from first to last.
2. Pop characters from the stack and place them back into the original string array.
A1: Stack overflow occurs when you try to push an element onto an already full stack. Stack underflow
occurs when you try to pop an element from an empty stack. Both are error conditions that should be
handled in your implementation.
A2: Stacks provide a simple, efficient way to handle data in a Last-In-First-Out manner. They have constant
time O(1) complexity for push and pop operations. They're useful for solving problems involving nested
structures, like function calls, expression evaluation, and backtracking algorithms.
A3: LIFO stands for Last-In-First-Out, which describes the behavior of a stack. The last element that is
pushed (inserted) into the stack is the first element to be popped (removed) from the stack, like a stack of
plates.
Q4: What are some real-world applications of stacks?
Q5: What's the difference between array and linked list implementation of a stack?
A5:
Array implementation:
Has a fixed size
Uses less memory
III. Queue
Queue as ADT
📘 Definition
A queue is a linear data structure that follows the First-In-First-Out (FIFO) principle. The first element
added to the queue is the first one to be removed.
Real-life examples:
FIFO
------->
┌───┬───┬───┬───┐
│ 10│ 20│ 30│ │
└───┴───┴───┴───┘
↑ ↑
Front Rear
(First In) (Last In)
Basic Operations:
Operation Description
// Global variables
int queue[MAX]; // Array to store queue elements
int front = -1; // Index of front element
int rear = -1; // Index of rear element
// 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;
}
Step-by-step explanation:
1. Initialize:
Create an array queue of size MAX to hold elements
2. Enqueue Operation:
Check if the queue is full ( rear == MAX-1 )
If not full, increment rear and place the new element at this position
3. Dequeue Operation:
Check if the queue is empty ( front == -1 or front > rear )
If not empty, get the element at front , increment front , and return the element
4. GetFront Operation:
Return the element at front without removing it
getFront() returns 10