0% found this document useful (0 votes)
37 views27 pages

Data Structures - Beginner's Complete Guide pt-00

The document is a comprehensive guide on data structures, covering essential concepts such as functions, recursion, arrays, and pointers as prerequisites. It introduces various data structures including stacks, queues, and circular queues, detailing their definitions, implementations, and operations. The guide emphasizes the importance of choosing the right data structure for specific needs to optimize program performance.

Uploaded by

thangenabil44
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
37 views27 pages

Data Structures - Beginner's Complete Guide pt-00

The document is a comprehensive guide on data structures, covering essential concepts such as functions, recursion, arrays, and pointers as prerequisites. It introduces various data structures including stacks, queues, and circular queues, detailing their definitions, implementations, and operations. The guide emphasizes the importance of choosing the right data structure for specific needs to optimize program performance.

Uploaded by

thangenabil44
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 27

Data Structures: A Beginner's Complete Guide

From Basics to Circular Queues

Table of Contents
0. Prerequisites
0.1 Functions

0.2 Recursion
0.3 Arrays

0.4 Pointers
Q&A for Prerequisites

I. Introduction to Data Structures


What are Data Structures?
Abstract Data Types (ADT)

Types of Data Structures


Operations on Data Structures
Q&A for Introduction

II. Stack
Stack as ADT

Array Implementation of Stack


Stack Applications
Parentheses Checker
Reversing a String

Q&A for Stack

III. Queue
Queue as ADT

Array Implementation of Queue


Q&A for Queue

IV. Circular Queue


Problems with Simple Queue
Circular Queue Concept

Array Implementation of Circular Queue


Q&A for Circular Queue
0. Prerequisites
Before diving into data structures, let's understand some foundational concepts that will help us better
grasp the more complex topics ahead.

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.

Basic Structure of a Function:

return_type function_name(parameter_list) {
// Function body - code that performs the task
return value; // Optional - returns a value to the caller
}

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

In this example:

int is the return type (the function returns an integer)

add is the function name

(int a, int b) are the parameters (inputs to the function)

return a + b; specifies the value to return to the caller

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.

Recursion has two essential components:

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

Example: Calculating Factorial

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

// 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

Here's how the execution works for factorial(5) :

1. Is 5 ≤ 1? No, so return 5 * factorial(4)

2. Is 4 ≤ 1? No, so return 4 * factorial(3)

3. Is 3 ≤ 1? No, so return 3 * factorial(2)


4. Is 2 ≤ 1? No, so return 2 * factorial(1)

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

Zero-based indexing (first element is at index 0)


Elements stored in adjacent memory locations

Example:

// 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

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:

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)

Key pointer operations:


&variable gets the memory address of a variable

*pointer accesses the value at the address stored in the pointer (dereferencing)

Visual representation:

Memory:
+-------------------+
| address: &number | --+
| value: 10 | |
+-------------------+ |
|
+-------------------+ |
| address: &ptr | |
| value: &number |---+
+-------------------+

Q&A for Prerequisites


Q1: What is the difference between a function and a procedure?

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.

Q2: Why would you use recursion instead of loops?

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.

Q4: What's the difference between pointers and arrays in C?

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.

Q5: What's a real-life analogy for a function?

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.

I. Introduction to Data Structures


What are Data Structures?
📘 Definition
Data structures are specialized formats for organizing, processing, retrieving, and storing data in a
computer.

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:

If data items are like physical objects, then:


An array is like a row of identical boxes numbered sequentially
A stack is like a stack of plates where you can only take from the top

A queue is like a line of people waiting for service


A linked list is like a treasure hunt where each clue points to the next location

Abstract Data Types (ADT)


📘 Definition
An Abstract Data Type (ADT) is a mathematical model for data structures that defines the behavior
from the user's perspective without specifying the implementation details.

An ADT defines:

What data is stored

What operations are allowed on the data

How errors are handled

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.

Common ADTs include:

Stack (Last-In-First-Out)

Queue (First-In-First-Out)
List
Tree

Types of Data Structures


Data structures can be categorized in several ways:

1. Linear vs. Nonlinear


Linear Data Structures:

Elements form a sequence


Each element has at most two neighbors

Examples: Arrays, Linked Lists, Stacks, Queues

Nonlinear Data Structures:

Elements don't form a sequence

An element can be connected to multiple elements


Examples: Trees, Graphs

2. Static vs. Dynamic


Static Data Structures:

Fixed size
Allocated at compile time

Memory is allocated from stack


Example: Arrays

Dynamic Data Structures:

Size can change during program execution

Allocated at runtime

Memory is allocated from heap

Examples: Linked Lists, Dynamic Arrays


Data Structures Classification

├── Based on Organization
│ ├── Linear
│ │ ├── Arrays
│ │ ├── Linked Lists
│ │ ├── Stacks
│ │ └── Queues
│ │
│ └── Non-Linear
│ ├── Trees
│ └── Graphs

└── Based on Memory Allocation
├── Static
│ └── Arrays

└── Dynamic
├── Linked Lists
├── Dynamic Arrays
└── Trees

Operations on Data Structures


Most data structures support the following operations:

Operation Description

Insertion Adding a new element to the data structure

Deletion Removing an element from the data structure

Traversal Accessing each element exactly once

Searching Finding a specific element

Sorting Arranging elements in a particular order

Merging Combining two data structures


 

Different data structures perform these operations with different efficiencies, which is why choosing the
right data structure for your specific needs is important.

Q&A for Introduction


Q1: What is the main difference between a data structure and an abstract data type?

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:

The operations you'll perform most frequently (insertion, deletion, search)

The amount of data you're working with

Memory constraints

Time complexity requirements


Whether the data size is fixed or variable

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:

A stack of plates in a cafeteria

A pile of books on a desk


The "undo" function in a text editor
The call stack in program execution

Visual representation:

│ │
│ 30 │ ← Top (Last In)
│ 20 │
│ 10 │ ← First In
└──────────┘

Basic Operations:
Operation Description

Push Add an element to the top of the stack

Pop Remove the top element from the stack

Peek/Top View the top element without removing it

IsEmpty Check if stack is empty

IsFull Check if stack is full (for array implementation)


 

Array Implementation of Stack


We can implement a stack using an array. Here's the complete implementation:
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;
}

Step-by-step explanation:

1. Initialize:
Create an array stack of size MAX to hold elements

Initialize top = -1 to indicate an empty stack

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

If full, display "Stack Overflow" error

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

If empty, display "Stack Underflow" error

4. Peek Operation:
Return the element at top without removing it

If stack is empty, display an error


Visual state changes during execution:

Initial state: top = -1, stack = []

After push(10): top = 0, stack = [10]


After push(20): top = 1, stack = [10, 20]
After push(30): top = 2, stack = [10, 20, 30]

peek() returns 30

After pop(): top = 1, stack = [10, 20], returns 30


After pop(): top = 0, stack = [10], returns 20

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

// Function to push a character onto stack


void push(char c) {
if (top == MAX - 1) { // Check if stack is full
printf("Stack Overflow\n"); // Print error message
} else {
stack[++top] = c; // Increment top and push character
}
}

// Function to pop a character from stack


char pop() {
if (top == -1) { // Check if stack is empty
printf("Stack Underflow\n"); // Print error message
return '\0'; // Return null character
} else {
return stack[top--]; // Return character and decrement top
}
}

// 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 check if two characters are matching brackets


int isMatchingPair(char character1, char character2) {
if (character1 == '(' && character2 == ')') {
return 1;
} else if (character1 == '{' && character2 == '}') {
return 1;
} else if (character1 == '[' && character2 == ']') {
return 1;
} else {
return 0;
}
}

// Function to check if an expression has balanced brackets


int areBracketsBalanced(char expr[]) {
int i = 0;
// Traverse the expression
for (i = 0; expr[i] != '\0'; i++) {
// If the current character is an opening bracket, push it onto stack
if (expr[i] == '(' || expr[i] == '{' || expr[i] == '[') {
push(expr[i]);
}
// If the current character is a closing bracket
else if (expr[i] == ')' || expr[i] == '}' || expr[i] == ']') {
// If stack is empty or the top bracket doesn't match
if (isEmpty() || !isMatchingPair(pop(), expr[i])) {
return 0; // Not balanced
}
}
}
// If stack is empty, the expression is balanced
return isEmpty();
}

// 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:

1. Scan the expression from left to right.

2. If you encounter an opening bracket ( ( , { , or [ ), push it onto the stack.

3. If you encounter a closing bracket ( ) , } , or ] ):


If the stack is empty, the expression is not balanced.
Pop the top element from the stack and check if it matches the current closing bracket.
If they don't match, the expression is not balanced.

4. After scanning the entire expression, if the stack is empty, the expression is balanced. Otherwise, it's not
balanced.

Example:

For expression {([])} :


Push { , then ( , then [

Encounter ] , pop [ - match!

Encounter ) , pop ( - match!

Encounter } , pop { - match!

Stack is empty at the end: Balanced

For expression {([)]} :


Push { , then ( , then [

Encounter ) , pop [ - mismatch!

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

// Function to push a character onto stack


void push(char c) {
if (top == MAX - 1) { // Check if stack is full
printf("Stack Overflow\n"); // Print error message
} else {
stack[++top] = c; // Increment top and push character
}
}

// Function to pop a character from stack


char pop() {
if (top == -1) { // Check if stack is empty
printf("Stack Underflow\n"); // Print error message
return '\0'; // Return null character
} else {
return stack[top--]; // Return character and decrement top
}
}

// Function to reverse a string using stack


void reverseString(char str[]) {
int i, len = strlen(str);

// Push all characters onto stack


for (i = 0; i < len; i++) {
push(str[i]);
}

// 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.

3. Since stacks follow LIFO, the characters are effectively reversed.

Example for "hello":

Original string: "hello"

After pushing all characters:


Stack: ['h', 'e', 'l', 'l', 'o'] (with 'o' at the top)

After popping all characters and placing them back:


Reversed string: "olleh"

Q&A for Stack


Q1: What is stack overflow and stack underflow?

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.

Q2: What are the advantages of using a stack?

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.

Q3: Why is stack called a LIFO data structure?

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?

A4: Real-world applications include:

Function call management in programming languages

Undo functionality in text editors and applications


Browser history (back button)
Expression evaluation and syntax parsing

Backtracking algorithms like maze solving

Q5: What's the difference between array and linked list implementation of a stack?

A5:

Array implementation:
Has a fixed size
Uses less memory

Has better cache locality


May cause stack overflow if the array size is exceeded

Linked list implementation:


Has dynamic size (limited only by available memory)
Uses more memory (additional pointer per element)

May have poorer cache locality


Won't have stack overflow (except for system memory limitations)

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:

A line of people waiting for a bus

Print queue in a computer


Call center queue
Tasks waiting to be processed in a computer
Visual representation:

FIFO
------->
┌───┬───┬───┬───┐
│ 10│ 20│ 30│ │
└───┴───┴───┴───┘
↑ ↑
Front Rear
(First In) (Last In)

Basic Operations:
Operation Description

Enqueue Add an element to the rear/back of the queue

Dequeue Remove an element from the front of the queue

Front Get the front element without removing it

IsEmpty Check if queue is empty

IsFull Check if queue is full


 

Array Implementation of Queue


Here's a complete implementation of a queue using an array:
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;
}

Step-by-step explanation:

1. Initialize:
Create an array queue of size MAX to hold elements

Initialize front = -1 and rear = -1 to indicate an empty queue

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

If queue was previously empty, set front to 0

If full, display "Queue Overflow" error

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

If the last element was dequeued, reset front and rear to -1

If empty, display "Queue Underflow" error

4. GetFront Operation:
Return the element at front without removing it

If queue is empty, display an error

Visual state changes during execution:

Initial state: front = -1, rear = -1, queue = []

After enqueue(10): front = 0, rear = 0, queue = [10]


After enqueue(20): front = 0, rear = 1, queue = [10, 20]
After enqueue(30): front = 0, rear = 2, queue = [10, 20, 30]

getFront() returns 10

After dequeue(): front = 1, rear = 2, queue = [10, 20, 30], returns

You might also like