BCS304-Data Structures and Applications-Digital Notes-Module 1
BCS304-Data Structures and Applications-Digital Notes-Module 1
(BCS304)
DIGITAL NOTES
B.E.
(II YEAR – III SEM)
(2024-25)
Prepared by
Dr. Nagaraj Bhat
Professor
Department of CSE
SHRI MADHWA VADIRAJA INSTITUTE OF
TECHNOLOGY AND MANAGEMENT
BCS304 | DATA STRUCTURES AND APPLICATIONS
TABLE OF CONTENTS
2. Queues ................................................................................................................................................. 64
1. Data Structure
A data structure is a way of organizing and storing data in a computer so that it can be accessed and
modified efficiently. It provides a means to manage large amounts of data for various computational
tasks, such as storing, retrieving, and manipulating data.
Optimized Performance:
o Properly chosen data structures improve the performance of algorithms by optimizing
operations such as searching, sorting, and updating data.
Memory Utilization:
o Data structures help in utilizing memory efficiently by storing data in a way that
minimizes space usage and avoids memory wastage.
Scalability:
o As data grows, well-designed data structures can scale efficiently, handling larger
datasets without significant performance degradation.
Data structures can be broadly classified into two categories: Primitive Data Structures and Non-
Primitive Data Structures.
Integer:
o Description: Used to store numeric values without decimal points.
o Example: int age = 25;
o Usage: Useful for counting and indexing.
Float:
o Description: Used to store numbers with fractional parts.
o Example: float temperature = 36.5;
o Usage: Used in calculations requiring precision, such as scientific computations.
Character:
o Description: Stores individual characters.
o Example: char grade = 'A';
o Usage: Handling text and single-character inputs.
Boolean:
o Description: Stores true or false values.
o Example: bool isPassed = true;
o Usage: Common in conditional statements.
Pointer:
o Description: Stores memory addresses of other variables.
o Example:
int num = 10; int* ptr = #
o Usage: Dynamic memory allocation, passing by reference.
Arrays:
o Description: A collection of elements, all of the same type, stored in contiguous memory
locations.
o Example: int numbers[5] = {1, 2, 3, 4, 5};
o Usage: Useful for fixed-size collections of similar items.
Stacks:
o Description: Follows the Last In, First Out (LIFO) principle.
o Example:
#define MAX 10
int stack[MAX];
int top = -1;
o Usage: Undo mechanisms, backtracking.
Queues:
o Description: Follows the First In, First Out (FIFO) principle.
o Example:
#define MAX 10
int queue[MAX];
int front = -1, rear = -1;
o Usage: Scheduling, managing resources.
Linked Lists:
o Description: Elements (nodes) are stored in non-contiguous memory locations,
connected by pointers.
o Example:
struct Node {
int data;
struct Node* next;
};
o Usage: Dynamic collections, frequent insertions/deletions.
Trees:
o Description: A hierarchical data structure with a root node and child nodes forming a
tree-like structure.
o Example:
struct Node {
int data;
struct Node* left;
struct Node* right;
};
o Usage: Databases, file systems.
Graphs:
o Description: A collection of nodes (vertices) connected by edges. Can be directed or
undirected.
o Example:
struct Graph {
int V;
int E;
int** adjMatrix;
};
o Usage: Networks, social connections.
Hash Tables:
o Description: Stores key-value pairs with fast access through hashing.
o Example:
int hashTable[10];
int hashFunction(int key) {
return key % 10;
}
o Usage: Efficient searching, indexing.
Traversing:
o Description: Accessing each element of the data structure exactly once, usually to
perform some action on each element.
o Example:
▪ Array Traversal: Iterating through an array to print all its elements.
▪ Linked List Traversal: Visiting each node in a linked list to calculate the sum of
all elements.
o Usage: Useful for displaying data, performing calculations, and processing each element
of the structure.
Searching:
o Description: Finding the location of a specific element within the data structure.
o Example:
▪ Linear Search: Searching for a number in an unsorted array.
▪ Binary Search: Searching for a number in a sorted array using a divide-and-
conquer approach.
o Usage: Essential for data retrieval, such as finding a record in a database or checking the
existence of a value.
Inserting:
o Description: Adding a new element to the data structure.
o Example:
▪ Array Insertion: Adding a new element at a specific position in an array (may
require shifting elements).
▪ Linked List Insertion: Inserting a new node at the beginning, end, or any specific
position in the list.
o Usage: Required when expanding the dataset, such as adding new entries to a database
or appending elements to a list.
Deleting:
o Description: Removing an element from the data structure.
o Example:
▪ Array Deletion: Removing an element from an array (may require shifting
elements).
▪ Linked List Deletion: Removing a specific node from a linked list.
o Usage: Important for data maintenance, like deleting obsolete records from a database
or removing an item from a collection.
Sorting:
o Description: Arranging the elements of a data structure in a specific order, typically
ascending or descending.
o Example:
▪ Bubble Sort: Sorting an array by repeatedly swapping adjacent elements that
are out of order.
▪ Quick Sort: A divide-and-conquer algorithm that sorts elements by partitioning
the array into sub-arrays.
o Usage: Critical for data organization, such as preparing data for binary search or
displaying sorted lists to users.
Merging:
o Description: Combining elements from two data structures into a single data structure.
o Example:
▪ Merging Sorted Arrays: Combining two sorted arrays into a single sorted array.
▪ Merging Linked Lists: Combining two sorted linked lists into one sorted list.
o Usage: Often used in applications that involve combining datasets, such as merging
customer records or combining results from different sources.
Updating:
o Description: Modifying the value of an existing element in the data structure.
o Example:
▪ Array Update: Changing the value of an element at a specific index.
▪ Linked List Update: Changing the data in a particular node.
o Usage: Used when the data within the structure changes, such as updating a user’s
profile information in a database.
An array is a data structure that stores a fixed-size sequence of elements of the same type in contiguous
memory locations. Arrays are used when we need to store multiple values of the same type in a single
variable, and they allow for efficient access to elements using an index.
Array as a Set of Pairs <index, value>
An array can be conceptually understood as a set of pairs <index, value>. Each element in an array is
associated with a unique index, which serves as a key to access the corresponding value. The index
represents the position of the element within the array, while the value is the actual data stored at that
position.
Let's consider an example where an array stores the marks of students in a class.
Example: Storing Student Marks in an Array
Suppose we have an array that holds the marks of 5 students in a subject:
int marks[5] = {85, 92, 76, 88, 95};
For instance, if you want to find the marks of the third student, you can access marks[2], which directly
gives you the value 76. This efficiency is one of the primary advantages of using arrays in programming.
This concept of an array as a set of <index, value> pairs makes it a fundamental data structure for
storing and accessing data sequentially, with direct access to any element via its index.
int main() {
int a[5] = {1, 2, 3, 4, 5}; // Array declaration and initialization during compilation
int sum = 0;
return 0;
}
• Explanation:
o In this example, the array a is declared with a size of 5 and initialized with the values {1,
2, 3, 4, 5} at compile time.
o The size of the array and the values it contains are fixed and known before the program
is executed.
o The program calculates the sum of all elements in the array and outputs it.
1.4.3 Array Declaration and Initialization During Runtime (Without Using Any Memory
Allocation Function)
When an array is declared and initialized during runtime, the size of the array is determined while the
program is running, based on user input or other runtime conditions. However, the array is still created
in the stack memory, not in the heap, and does not use dynamic memory allocation functions like
malloc() or calloc().
Example:
#include <stdio.h>
int main() {
int n, i, sum = 0;
return 0;
}
• Explanation:
o Array Declaration During Runtime: In this program, the array a is declared after
determining its size n from the user's input at runtime.
o No Dynamic Memory Allocation: The array is created on the stack (not dynamically
allocated in the heap), meaning that memory is allocated automatically when the array
is declared.
o Runtime Initialization: The user inputs the elements of the array during the execution of
the program, which are then stored in the array a.
o The program calculates the sum of all elements entered by the user and prints the
result.
Explanation:
• Array Declaration: int matrix[2][3]; declares a two-dimensional array with 2 rows and 3
columns.
• Input: Nested loops are used to input elements into the matrix. The outer loop iterates over the
rows, and the inner loop iterates over the columns.
• Output: The same nested loop structure is used to print the matrix, formatting it into rows and
columns.
Accessing Elements:
• Row-Major Order: In a two-dimensional array, elements are stored in memory in row-major
order, meaning the elements of each row are stored in contiguous memory locations.
• Access Syntax:
o To access an element in the ith row and jth column: array_name[i][j].
o Example: matrix[1][2] accesses the element in the second row and third column of the
matrix.
Text Area:
o Description: The Text Area, also known as the Code Segment, stores the actual code or
instructions of the program. This section is usually read-only to prevent accidental
modification of the instructions.
o Characteristics:
▪ Contains compiled machine code.
▪ Fixed size, determined at compile time.
Heap Memory:
o Description: Heap Memory is used for dynamic memory allocation. Memory in the heap
is allocated and deallocated by the programmer at runtime using functions like malloc(),
calloc(), realloc(), and free().
o Characteristics:
▪ Size can grow and shrink dynamically as needed during program execution.
▪ Not automatically managed by the system; requires manual deallocation.
▪ Useful for allocating memory when the size of data structures is not known in
advance.
o Example:
int* ptr = (int*)malloc(10 * sizeof(int)); // Allocates memory for an array of 10 integers
free(ptr); // Frees the allocated memory
Stack Memory:
o Description: Stack Memory is used for storing local variables, function parameters, and
return addresses. It operates in a Last-In-First-Out (LIFO) manner.
o Characteristics:
▪ Memory allocation and deallocation are managed automatically by the system.
▪ Each function call creates a new stack frame, which includes the function's local
variables and return address.
▪ Stack memory is limited in size, leading to a stack overflow if too much memory
is used (e.g., excessive recursion).
o Example:
void function() {
int local_var = 5; // Stored in the stack
}
Pointer Declaration:
o Pointers are declared using the * operator before the pointer variable's name.
o Syntax: datatype *pointer_name;
o Example:
int *ptr; // Declares a pointer to an integer
Pointer Initialization:
o Pointers are typically initialized to the address of a variable using the address-of
operator &.
o Example:
int num = 10;
int *ptr = # // ptr now holds the address of num
2. Dereferencing Pointers:
o Dereferencing a pointer means accessing the value stored at the memory address the
pointer is pointing to, using the * operator.
o Example:
int value = *ptr; // Retrieves the value at the address stored in ptr (value will be 10)
3. Null Pointer:
o A pointer that is not assigned any address can be set to NULL, indicating that it points to
nothing.
o Example:
int *ptr = NULL; // ptr is initialized but points to nothing
4. Pointer Arithmetic:
o Pointers can be incremented or decremented to point to the next or previous memory
locations, especially in arrays.
o Example:
ptr++; // Moves the pointer to the next memory location of its type
int main() {
int num = 25; // Declare an integer variable
int *ptr = # // Declare a pointer and assign it the address of num
return 0;
}
Output:
Value of num: 25
Address of num: 0x7ffee3c5b74c
Value of ptr: 0x7ffee3c5b74c
Value pointed by ptr: 25
Updated value of num: 30
In C, arrays and pointers are closely related. The name of an array acts like a pointer to the first element
of the array, which means you can use pointers to access and manipulate array elements. By using
pointers, you can perform operations on arrays more efficiently and flexibly.
Explanation:
The program dynamically allocates memory for an array of n integers based on user input using malloc().
After inputting the elements and calculating their sum, the memory is freed using free().
return 1;
}
return 0;
}
Explanation:
The program initially allocates memory for n integers using malloc(). It then reallocates the memory
block to hold new_n integers using realloc(). After inputting any additional elements and calculating the
sum, the memory is freed.
Syntax:
free(ptr);
Where:
o ptr: A pointer to a previously allocated memory block that needs to be deallocated.
Explanation:
The free() function is used in each of the above examples to deallocate the dynamically allocated
memory, preventing memory leaks. It is essential to free any dynamically allocated memory after it is no
longer needed.
1.9.1 Structures
Structure (often abbreviated as struct) is a user-defined data type in C that allows the combination of
data items of different types under a single name. Each data item in a structure is called a member or
field.
Key Characteristics:
• Structures can contain variables of various data types.
• Each member in a structure has its own storage location, meaning all members can hold their
values independently.
• Structures are useful for representing complex data entities, like records or objects.
Basic Syntax:
struct StructureName {
data_type1 member1;
data_type2 member2;
...
};
Example: Basic Structure
#include <stdio.h>
struct Student {
int rollNumber;
char name[50];
float marks;
};
int main() {
struct Student student1;
return 0;
}
Explanation:
• Structure Definition: The struct Student defines a structure named Student with three
members: rollNumber (an integer), name (a character array), and marks (a float).
• Structure Variable: The student1 is a variable of type struct Student.
• Accessing Members: Members of the structure are accessed using the dot operator (.) with the
structure variable.
typedef struct {
int rollNumber;
char name[50];
float marks;
} Student; // Alias 'Student' for the structure
int main() {
Student student1;
return 0;
}
Explanation:
• The typedef keyword is used to create an alias Student for the structure type. This allows the
use of Student instead of struct Student when declaring variables.
struct Student {
int rollNumber;
char name[50];
float marks;
};
int main() {
struct Student student1 = {103, "David", 78.5};
return 0;
}
Explanation:
• The printStudent() function takes a Student structure as an argument and prints its members.
The structure is passed by value, meaning a copy of student1 is passed to the function.
struct Student {
int rollNumber;
char name[50];
float marks;
};
int main() {
struct Student student1 = {104, "Eve", 91.0};
return 0;
Explanation:
• The printStudent() function now takes a pointer to a Student structure as an argument. The
arrow operator (->) is used to access the members of the structure through the pointer. The
structure is passed by reference, so any changes made within the function would affect the
original structure.
A self-referential structure is a structure in which one or more members are pointers that
reference the same structure type. This allows the creation of dynamic data structures such as
linked lists, trees, and graphs, where elements are connected to other elements of the same
type.
• Pointer to Same Structure Type: A self-referential structure contains a pointer that points to
another structure of the same type.
• Used in Dynamic Data Structures: Self-referential structures are the foundation for many
dynamic data structures, such as linked lists, binary trees, and graphs.
• Memory Allocation: While dynamic memory allocation (malloc() and free()) is commonly
used, self-referential structures can also be created with static memory allocation, as shown in
the textbook example.
Structure Definition:
• data: This field stores the data of the node (in this case, an integer).
• link: This pointer of type struct Node points to another node of the same type, making the
structure self-referential.
This program creates a simple linked list of three items without using dynamic memory
allocation.
#include <stdio.h>
int main() {
// Create three static nodes
List item1, item2, item3;
return 0;
}
Explanation:
Output:
1.9.2 Unions
Union is similar to a structure in that it is also a user-defined data type that can contain different data
types. However, unlike structures, all members of a union share the same memory location. This means
that at any given time, a union can only store a value for one of its members.
Key Characteristics:
• Unions can store different data types, but only one member can hold a value at a time because
all members share the same memory space.
• The size of a union is determined by the size of its largest member.
• Unions are useful when you want to work with different data types in the same memory
location, which can help save memory.
Syntax:
union UnionName {
data_type1 member1;
data_type2 member2;
...
};
Example:
#include <stdio.h>
union Data {
int intValue;
float floatValue;
char charValue;
};
int main() {
union Data data;
return 0;
}
Explanation:
• Union Definition: The union Data defines a union named Data with three members: intValue (an
integer), floatValue (a float), and charValue (a character).
• Union Variable: The data is a variable of type union Data.
• Memory Sharing: Since all members share the same memory location, writing to one member
will overwrite the previous value. For example, after setting floatValue, the value in intValue will
no longer be valid.
1.10 Polynomials
Introduction
Viewed from a mathematical perspective, a polynomial is a sum of terms, where each term has
the form axe, where:
• x is the variable,
• a is the coefficient,
• e is the exponent.
For example, a polynomial such as a(x)=15x4+24x2+12x+4 consists of terms that are combined
based on their exponents. Polynomials are widely used in algebra, mathematical computations,
and scientific modeling.
The Polynomial ADT defines a set of operations that can be performed on polynomials, such as
creating, accessing, or modifying terms, and performing arithmetic operations like addition and
multiplication.
structure Polynomial is
objects: p(x) = a1xe1 + • • • + anxen; a set of ordered pairs of < ei, ai > where ai in Coefficients and ei
in Exponents, ei are integers >= 0
functions:
Operation
Return Type Function Name Description
Type
Returns a zero polynomial
Polynomial Zero() Creation
p(x)=0
Returns TRUE if the
Boolean IsZero(poly) Check polynomial is zero, otherwise
FALSE.
Returns the coefficient of the
Coefficient Coef(poly, exp) Retrieval term with exponent exp in
poly.
Returns the largest exponent
Exponent Lead_Exp(poly) Information
in the polynomial.
Inserts a new with the term
Polynomial Attach(poly, coef, exp) Modification
<coef, expon> in poly.
Removes the term with
Polynomial Remove(poly, exp) Deletion
exponent exp from poly.
Multiplies the polynomial by
Polynomial SingleMult(poly, coef, exp) Multiplication a. xe where a is the coefficient
and e is the exponent.
Polynomial Add(poly1, poly2)
Adds two polynomials and
Addition
returns the result.
Multiplies two polynomials
Polynomial Mult(poly1, poly2) Multiplication
and returns the result.
Given Polynomials:
1. a(x)=4x3+3x2+5x+6
2. b(x)=5x4+4x2+2x+1
1. Zero()
Operation: This function creates and returns a zero polynomial p(x)=0.
Example:
Polynomial p = Zero();
2. IsZero(poly)
Operation: This function checks if the polynomial is a zero polynomial.
Example:
Explanation:
• a(x)= 4x3+3x2+5x+6, so IsZero(a) will return FALSE because a(x) is not zero.
3. Coef(poly, exp)
Operation: This function retrieves the coefficient of the term with a given exponent in a polynomial.
Example:
Explanation:
For 4x3+3x2+5x+6, calling Coef(a, 2) will return 3 (the coefficient of x²).
4. Lead_Exp(poly)
Operation: This function returns the largest exponent (leading term) in the polynomial.
Example:
Explanation:
For b(x)=5x4+4x2+2x+1, the largest exponent is 4 because 5x⁴ is the leading term.
Example:
Explanation:
anew(x)=2x5+4x3+3x2+5x+6
6. Remove(poly, exp)
Operation: This function removes the term with a given exponent from the polynomial.
Example:
Explanation:
For b(x)=5x4+4x2+2x+1, removing the x² term results in:
bnew(x)=5x4+2x+1
Example:
Explanation:
Multiplying a(x)=4x3+3x2+5x+6 by 3x² results in:
8. Add(poly1, poly2)
Operation: This function adds two polynomials.
Example:
Explanation:
Adding a(x) = 4x³ + 3x² + 5x + 6 and b(x) = 5x⁴ + 4x² + 2x + 1 gives:
9. Mult(poly1, poly2)
Operation: This function multiplies two polynomials.
Example:
Explanation:
Multiplying a(x) = 4x³ + 3x² + 5x + 6 and b(x) = 5x⁴ + 4x² + 2x + 1 results in:
1. Array Representation: The index represents the exponent, and the value at each index is the
corresponding coefficient.
2. Array of Structures: Each term is stored in a structure containing the coefficient and exponent,
saving memory in sparse polynomials.
In array representation, the polynomial is stored in an array where the index is the degree
(exponent) and the value is the coefficient.
Polynomial a;
a.degree = 4; // Highest degree is 4
a.coeff[4] = 15; // Coefficient for x^4
a.coeff[3] = 0; // Coefficient for x^3 (missing term)
a.coeff[2] = 24; // Coefficient for x^2
a.coeff[1] = 12; // Coefficient for x^1
a.coeff[0] = 4; // Coefficient for x^0 (constant)
degree coeff [4] coeff [3] coeff [2] coeff [1] coeff [0]
4 15 0 24 12 4
For sparse polynomials, an array of structures is used, where each structure contains both the
coefficient and exponent, which makes this more memory-efficient by skipping zero terms.
Structure Definition:
#define MAX_TERMS 7
typedef struct {
float coeff; // Coefficient of the term
int expon; // Exponent (degree) of the term
} PolynomialTerm;
• a(x)=15x4+24x2+12x+4
• b(x)=13x3+7x+3
The memory stores only non-zero coefficients and their corresponding degrees, ignoring zero
terms.
Memory Representation
starta finisha startb finishb avail
terms[0] terms[1] terms[2] terms[3] terms[4] terms[5] terms[6]
coeff 15 24 12 4 13 7 3
expon 4 2 1 0 3 1 0
Code Structure:
break;
Explanation:
1. Initialization:
o The result polynomial d(x) is initialized as a zero polynomial using d = Zero(). This means
that d(x)= 0.
2. While Loop:
o The loop continues until one of the input polynomials a(x)or b(x) becomes a zero
polynomial (!IsZero(a) and !IsZero(b)).
o Inside the loop, the leading terms of a(x) and b(x) are compared using the
COMPARE(Lead_Exp(a), Lead_Exp(b)) function. Based on the result of this comparison,
different actions are taken:
3. Switch Cases:
o Case -1: If the leading exponent of b(x) is greater than that of a(x), the leading term
from b(x) is added to d(x), and then it is removed from b(x).
o Case 0: If the leading exponents of a(x) and b(x) are equal, their coefficients are added
together. If the sum of the coefficients is non-zero, the result is attached to d(x). The
leading terms are then removed from both a(x) and b(x).
o Case 1: If the leading exponent of a(x) is greater than that of b(x), the leading term from
a(x) is added to d(x), and then it is removed from a(x).
o After the loop exits, any remaining terms in either a(x) or b(x) are attached to the result
polynomial d(x).
Key Functions:
• Coef(a, exp): Returns the coefficient of the term in polynomial a(x) with exponent exp.
• Attach(d, coef, exp): Adds a new term with coefficient coef and exponent exp to the polynomial
d(x).
• Remove(a, exp): Removes the term in polynomial a(x) with exponent exp.
Example:
• a(x)=4x3+3x2+5x+6
• b(x)=5x4+4x2+2x+1
o 7x2 (sum of 3x2 from a(x) and 4x2 from b(x)) is added to d(x).
o The constant 7 (sum of constants 6 from a(x) and 1 from b(x)) is added to d(x).
This program enhances the initial version of the padd function (Program 2.4), refining how two
polynomials are added. In this version, two polynomials A(x)and B(x) are represented as arrays of terms,
and the function computes their sum D(x). It manages each term of both polynomials and adds them in
decreasing order of exponents.
void padd(Polynomial *terms, int starta, int finisha, int startb, int finishb, int *startd, int *finishd) {
float coefficient;
*startd = avail; // Start of result polynomial D(x)
1. Initialization:
o The result polynomial D(x) begins at the current available position in the global array
terms (*startd = avail).
o The while loop continues as long as there are terms left in both A(x) and B(x).
o The function compares the exponents of the current terms in A(x) and B(x) using the
COMPARE() function, which returns:
3. Switch Cases:
o Case -1: If the exponent of B(x) is greater, the term from B(x) is added to D(x), and the
index for B(x) (startb) is incremented.
o Case 0: If the exponents are equal, the coefficients of both terms are added. If the sum
of the coefficients is non-zero, the new term is added to D(x), and both indices (starta,
startb) are incremented.
o Case 1: If the exponent of A(x) is greater, the term from A(x) is added to D(x), and the
index for A(x) (starta) is incremented.
o After the while loop finishes, any remaining terms from either polynomial are added to
D(x). If A(x) still has terms, they are appended to D(x); the same process is followed for
B(x).
5. Finalization:
o The result polynomial D(x) ends at the next available position in the global array terms
(*finishd = avail - 1).
Helper Functions:
o 0 if exp1 == exp2,
• attach(coefficient, exponent): Adds a new term to the polynomial D(x) at the next available
position in the array.
Example:
• A(x)=4x3+3x2+5x+6
• B(x)=5x4+4x2+2x+1
In this process:
• The leading terms 5x4 from B(x) and 4x3 from A(x) are added to D(x).
• The x2 terms from both polynomials are added together to get 7x2
• Objects: A set of triples, <row,column,value>, where row and column are integers and form a
unique combination, and value comes from the set item.
• Functions:
For all a,b ∈ Sparse-Matrix, x ∈ item, and i,j,max-row,max-col ∈ index, we define operations that
can be performed on sparse matrices as follows:
15 0 0 22 0 -15
0 11 3 0 0 0
0 0 0 -6 0 0
0 0 0 0 0 0
91 0 0 0 0 0
0 0 28 0 0 0
int currentb = 1; // Index for the next element in the transposed matrix b
Explanation:
1. Input Parameters:
o a[]: This is the original sparse matrix stored as an array of triples [row,column,value]
o b[]: This is the transposed sparse matrix, which will store the result of the transpose
operation.
2. Initial Setup:
o The outer loop iterates through each column index (i) of the original matrix a. The goal is
to transpose by swapping row and column indices.
o The inner loop (j) iterates over all the non-zero elements in matrix a. Whenever it finds
an element where the column index matches i, it transposes the element by:
▪
Setting the row of matrix b to the column of matrix a.
▪ Setting the column of matrix b to the row of matrix a.
▪ Copying the value from matrix a to matrix b.
5. Updating the Transposed Matrix:
o The variable currentb keeps track of the position in the transposed matrix b[] where the
next transposed element should be stored.
Example:
a b
15 0 0 22 0 -15 15 0 0 0 91 0
0 11 3 0 0 0 0 11 0 0 0 0
0 0 0 -6 0 0 0 3 0 0 0 28
0 0 0 0 0 0 22 0 -6 0 0 0
91 0 0 0 0 0 0 0 0 0 0 0
0 0 28 0 0 0 -15 0 0 0 0 0
This matrix has 8 non-zero elements, and they are represented in the array a[] and transposed matrix
represented in matrix b[].
• Addition: Combines the non-zero elements of two sparse matrices by summing elements with
the same row and column indices.
• Multiplication: Sparse matrix multiplication requires taking the dot product of rows from one
matrix and columns from the other.
1.12.1 Definition:
A multidimensional array is an array with more than one dimension, such as a 2D array (matrix)
or a 3D array (tensor). In C, they are often declared like this:
For a 3D array:
1. Row-major Order: This stores array elements row by row, which is common in C.
2. Column-major Order: This stores elements column by column, typical in languages like Fortran.
Row-major Order:
Row-major order stores the elements of the array sequentially by rows. This means that the entire
first row is stored, followed by the entire second row, and so on.
For a two-dimensional array A[upper0][upper1], the formula to calculate the address of element
A[i][j] is:
Where:
Example:
For a 2D array A[3][4]A[3][4]A[3][4]:
int A[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
1,2,3,4,5,6,7,8,9,10,11,12
To calculate the memory address of A[2][3], where i=2i = 2i=2 and j=3j = 3j=3:
This formula ensures that the correct offset is calculated for each element based on its position in
the multidimensional array.
Consider a 2D array A[3][4], and you want to calculate the address of the element A[2][3].
=1000+(2×4+3)×4
=1000+(8+3)×4
=1000+11×4
=1000+44=1044
• Efficient Storage: Data can be stored in a contiguous block of memory, which helps in faster
access and retrieval.
• Data Organization: It provides a structured way to organize data with multiple dimensions, such
as matrices or higher-dimensional tables.
Disadvantages:
• Memory Usage: Multidimensional arrays can consume large amounts of memory if the
dimensions are large, especially when declared statically.
• Complex Address Calculation: As the number of dimensions increases, the complexity of
calculating memory addresses increases.
1.13 Strings
A String is an abstract data type (ADT) that represents a sequence of characters. Strings are one
of the most common data structures used in programming, especially for handling text.
Operations on Strings
The essential operations that can be defined for a string ADT are as follows:
In the C programming language, strings are typically represented as character arrays that end
with a null character (\0), which indicates the end of the string.
In the above example, s holds the string "dog" and t holds the string "house". These arrays
include the null character (\0) at the end to mark the string's termination.
Example:
To concatenate the strings "dog" and "house" into the string "doghouse", we can use the
strcat function from the C standard library:
#include <stdio.h>
#include <string.h>
int main() {
char s[100] = "dog";
char t[] = "house";
strcat(s, t); // Concatenate t to s
printf("%s\n", s); // Outputs "doghouse"
return 0;
}
Function Description
strcat(char *dest, const char *src) Concatenates src to the end of dest.
strcmp(const char *str1, const char *str2) Compares two strings lexicographically.
strcpy(char *dest, const char *src) Copies the string src to dest.
strlen(const char *str) Returns the length of the string str.
Returns a pointer to the first occurrence
strchr(const char *str, int c)
of the character c in str.
strstr(const char *haystack, const char Returns a pointer to the first occurrence
*needle) of the substring needle in haystack.
Inserting one string into another is a common string operation. Suppose we have two strings, s1
and s2, and we want to insert s2 into s1 at position i.
#include <stdio.h>
#include <string.h>
int main() {
char s1[100] = "Hello World!";
char s2[] = "Beautiful ";
insert(s1, s2, 6); // Insert s2 into s1 at position 6
printf("%s\n", s1); // Outputs "Hello Beautiful World!"
return 0;
}
String pattern matching is used to find a specific substring (pattern) within a larger string. The
simplest method is to use the strstr function, which returns a pointer to the first occurrence of
the pattern in the string.
int main() {
char str[] = "The quick brown fox";
char pat[] = "brown";
char *result = strstr(str, pat);
if (result) {
printf("Pattern found at position: %ld\n", result - str);
} else {
printf("Pattern not found.\n");
}
return 0;
}
Here, strstr returns the pointer to the first occurrence of pat in str, and we calculate its
position by subtracting the base pointer of str.
1.14 Stacks
A stack is a fundamental data structure that operates under the Last In, First Out (LIFO)
principle. This means that the last element added to the stack is the first one to be removed. It’s
akin to stacking plates on top of each other: the last plate you put on the pile is the first one you
take off when you need one.
• LIFO Principle: The last item added (pushed) to the stack is the first item to be removed
(popped).
• Operations:
o Push: Add an element to the top of the stack.
o Pop: Remove and return the element at the top of the stack.
o Peek or Top: View the element at the top of the stack without removing it.
o IsEmpty: Check if the stack is empty.
o IsFull (for fixed-size stacks): Check if the stack has reached its maximum capacity.
Stacks are often used for managing data in recursive algorithms, expression evaluation, and
backtracking problems.
1. CreateStack(n) - Creation
This function creates a stack of size n and initializes the top to -1.
#include <stdio.h>
#include <stdlib.h>
int main() {
int size;
printf("Enter the size of the stack: ");
scanf("%d", &size);
return 0;
}
2. IsFull(S) - Check
This function checks if the stack is full by comparing the top index with the maximum size.
int main() {
if (IsFull()) {
printf("Stack is full\n");
} else {
printf("Stack is not full\n");
}
return 0;
3. IsEmpty(S) - Check
This function checks if the stack is empty by checking if the top index is -1.
int main() {
if (IsEmpty()) {
printf("Stack is empty\n");
} else {
printf("Stack is not empty\n");
}
return 0;
}
This function pushes an item onto the stack. It first checks if the stack is full, then increments the
top and adds the item.
int main() {
Push(10); // Push 10 onto the stack
Push(20); // Push 20 onto the stack
return 0;
}
5. Pop(S) - Deletion
This function pops the top element from the stack. It checks if the stack is empty before popping.
int main() {
Push(10);
Push(20);
printf("Popped: %d\n", Pop()); // Pops 20
printf("Popped: %d\n", Pop()); // Pops 10
return 0;
}
6. Peek(S) - Access
This function returns the top element of the stack without removing it. It checks if the stack is
empty.
int main() {
Push(10);
Push(20);
printf("Top element: %d\n", Peek()); // Returns 20 without
removing it
return 0;
}
Stacks can be implemented using a one-dimensional array. The variable top keeps track of the
index of the top element in the stack. Initially, the top is set to -1 to indicate that the stack is
empty.
1. Push Operation:
o Before adding an element, the IsFull function checks if there is space in the stack.
o If the stack is not full, increment the top and insert the new element at stack[++top].
2. Pop Operation:
o The IsEmpty function checks if the stack is empty before removing an element.
o If not empty, return the top element and decrement the top.
Here is a simple program that demonstrates push, pop, and display operations on a stack
implemented using an array in C.
int main() {
// Demonstrate push operations
Push(10);
Push(20);
Push(30);
Display(); // Display current stack elements
return 0;
}
Explanation:
1. Push Function: Adds an item to the stack if the stack is not full. If the stack is full, it prints an
overflow error.
2. Pop Function: Removes and returns the top element from the stack if it's not empty. If the stack
is empty, it prints an underflow error.
3. Display Function: Displays all the elements in the stack from bottom to top.
Sample Output:
Pushed 10 onto the stack.
Pushed 20 onto the stack.
Pushed 30 onto the stack.
Stack elements: 10 20 30
Popped 30 from the stack.
Stack elements: 10 20
Pushed 40 onto the stack.
Stack elements: 10 20 40
Here is the same program using a structure to represent the stack, including the push, pop, and
display operations.
struct Stack {
int items[MAX]; // Array to hold stack elements
int top; // Index of the top element
} S; // Global instance of the Stack
int main() {
S.top = -1; // Initialize the stack top as empty
Explanation:
1. Stack Structure:
o The stack is represented as a structure Stack containing an array items to hold the
elements and an integer top to track the top index.
2. InitStack Function:
o Initializes the stack by setting the top to -1 to indicate that the stack is empty.
3. Push Function:
o Adds an element to the top of the stack if it is not full. Otherwise, it prints an overflow
message.
4. Pop Function:
o Removes and returns the top element from the stack if it is not empty. Otherwise, it
prints an underflow message.
5. Display Function:
o Displays all elements of the stack from the bottom to the top.
Sample Output:
Stack elements: 10 20 30
Popped 30 from the stack.
Stack elements: 10 20
Pushed 40 onto the stack.
Stack elements: 10 20 40
In this implementation, the stack is dynamically allocated using malloc and resized using
realloc to accommodate new elements as they are pushed onto the stack. The stack size begins
with 1 and expands by 1 element each time a new item is pushed.
Key Concepts:
Here is a simple program demonstrating push and pop operations using dynamic memory
allocation:
#include <stdio.h>
#include <stdlib.h>
int *stack = NULL; // Dynamic array for the stack
int top = -1; // Top of the stack (index)
int size = 0; // Current size of the stack
// Function to push an element onto the stack
void Push(int item) {
if (size == 0) {
// Initial allocation for the stack (size 1)
stack = (int*)malloc(sizeof(int));
size = 1;
} else {
// Resize the stack by 1 using realloc
size++;
stack = (int*)realloc(stack, size * sizeof(int));
}
Explanation:
1. Push Function:
o Initially, the stack size is set to 0, and when the first element is pushed, malloc is used
to allocate memory for 1 integer.
o For subsequent pushes, realloc is used to increase the size of the stack by 1 and
adjust the memory block size.
o The top index is incremented, and the new item is added to the stack.
2. Pop Function:
o Before popping, the function checks if the stack is empty by checking if top is -1.
o If the stack is not empty, the top element is removed, the size is reduced, and realloc
is called to shrink the memory block.
3. Memory Management:
o After all operations, free(stack) is called to release the memory allocated to the
stack dynamically. This prevents memory leaks.
Output Example:
Pushed 10 onto the stack. Current size: 1
Pushed 20 onto the stack. Current size: 2
Pushed 30 onto the stack. Current size: 3
Popped 30 from the stack. Current size: 2
Popped 20 from the stack. Current size: 1
Pushed 40 onto the stack. Current size: 2
1. Infix: Operators are placed between operands (e.g., A+B). Requires parentheses to specify
precedence.
2. Postfix: Operators come after operands (e.g., AB+). No need for parentheses, and evaluated left
to right.
3. Prefix: Operators come before operands (e.g., +AB). Evaluated right to left without parentheses.
• Once in postfix form, the expression can be evaluated using a stack. Operands are pushed onto
the stack, and when an operator is encountered, it is applied to the top elements of the stack.
Example 1: Conversion of (a * b) + c
The simplified method to convert the infix expression (a∗b)+c directly to postfix without
explicitly using a stack involves recognizing basic patterns of operator precedence and
associativity. Here's how you can convert it:
Simplified Steps:
For (a∗b)+c:
End- Place the remaining operators i.e ‘+’ into the Postfix. So, Postfix expression will be ab*c+
Example 2: Conversion of a + b * c - d / e
Step Symbol Postfix Stack Action Variable
initial - - # Push # to stack top=0, pos=0
0 a a # Add a to postfix top=0, pos=1
1 + a #+ Push + to stack top=1, pos=1
2 b ab #+ Add b to postfix top=1, pos=2
3 * ab #+* Push * to stack top=2, pos=2
4 c abc #+* Add c to postfix top=2, pos=3
5 - abc*+ #- Pop *, Pop +, Push - top=1, pos=5
End- Place the remaining operators i.e ‘-’ into the Postfix. So, Postfix expression will be abc*+de/-
Example 3: Conversion of x * y + (a - b)
Step Symbol Postfix Stack Action Variable
init - - # Push # to stack top=0, pos=0
0 x x # Add x to postfix top=0, pos=1
1 * x #* Push * to stack top=1, pos=1
2 y xy #* Add y to postfix top=1, pos=2
3 + xy* #+ Pop *, Push + to stack top=1, pos=3
4 ( xy* #+( Push ( to stack top=2, pos=3
5 a xy*a #+( Add a to postfix top=2, pos=4
6 - xy*a #+(- Push - to stack top=3, pos=4
7 b xy*ab #+(- Add b to postfix top=3, pos=5
8 ) x y * a b- #+ Pop -, apply to postfix top=1, pos=6
9 End x y * a b- + # Pop +, apply to postfix top=0, pos=7
Final result: xy*ab-+
Example 4: Conversion of (p + q) * r - s
Step Symbol Postfix Stack Action Variable
init - - # Push # to stack top=0, pos=0
0 ( - #( Push ( to stack top=1, pos=0
1 p p #( Add p to postfix top=1, pos=1
2 + p #(+ Push + to stack top=2, pos=1
3 q pq #(+ Add q to postfix top=2, pos=2
4 ) pq+ # Pop +, apply to postfix top=0, pos=3
5 * pq+ #* Push * to stack top=1, pos=3
6 r pq+r #* Add r to postfix top=1, pos=4
7 - pq+r* #- Pop *, Push - to stack top=1, pos=5
8 s pq+r*s #- Add s to postfix top=1, pos=6
Example 5: Conversion of h + i * (j - k) / l
Step Symbol Postfix Stack Action Variable
init - - # Push # to stack top=0, pos=0
0 h h # Add h to postfix top=0, pos=1
1 + h #+ Push + to stack top=1, pos=1
2 i hi #+ Add i to postfix top=1, pos=2
3 * hi #+* Push * to stack top=2, pos=2
4 ( hi #+*( Push ( to stack top=3, pos=2
5 j hij #+*( Add j to postfix top=3, pos=3
6 - hij #+*(- Push - to stack top=4, pos=3
7 k hijk #+*(- Add k to postfix top=4, pos=4
8 ) hijk- #+* Pop -, apply to postfix top=2, pos=5
9 / hijk- #+/ Pop *, Push / to stack top=2, pos=5
10 l hijk-l #+/ Add l to postfix top=2, pos=6
11 End hijk-l/+ # Pop /, *, +, apply to postfix top=0, pos=7
Final result: hijkl-/*+
c) Perform the operation using the current operator: res = opnd1 (operator) opnd2.
d) Push the result of the operation (res) back onto the stack.
Step 4: Continue scanning the postfix expression until all symbols are processed.
Step 5: The final result of the postfix expression will be the only value remaining on the stack. Pop this
value to obtain the result.
Example 2: 5 6 2 + * 12 4 / -
Step Symbol Operand1 (opnd1) Operand2 (opnd2) Stack Operation Description
0 5 - - 5 Push '5' to the stack.
1 6 - - 5, 6 Push '6' to the stack.
2 2 - - 5, 6, 2 Push '2' to the stack.
3 + 6 2 5, 8 Pop '6' and '2', add them, push '8'.
4 * 5 8 40 Pop '5' and '8', multiply, push '40'.
5 12 - - 40, 12 Push '12' to the stack.
6 4 - - 40, 12, 4 Push '4' to the stack.
7 / 12 4 40, 3 Pop '12' and '4', divide, push '3'.
8 - 40 3 37 Pop '40' and '3', subtract, push '37'.
Final result: 37
Example 3: 9 8 - 7 +
Step Symbol Operand1 (opnd1) Operand2 (opnd2) Stack Operation Description
0 9 - - 9 Push '9' to the stack.
1 8 - - 9, 8 Push '8' to the stack.
2 - 9 8 1 Pop '9' and '8', subtract, push '1'.
Example 4: 3 4 2 * 1 5 - 2 3 ^ / +
Step Symbol Operand1 (opnd1) Operand2 (opnd2) Stack Operation Description
0 3 - - 3 Push '3' to the stack.
1 4 - - 3, 4 Push '4' to the stack.
2 2 - - 3, 4, 2 Push '2' to the stack.
3 * 4 2 3, 8 Pop '4' and '2', multiply, push '8'.
4 1 - - 3, 8, 1 Push '1' to the stack.
5 5 - - 3, 8, 1, 5 Push '5' to the stack.
6 - 1 5 3, 8, -4 Pop '1' and '5', subtract, push '-4'.
7 2 - - 3, 8, -4, 2 Push '2' to the stack.
8 3 - - 3, 8, -4, 2, 3 Push '3' to the stack.
9 ^ 2 3 3, 8, -4, 8 Pop '2' and '3', exponentiate, push '8'.
10 / -4 8 3, 8, -0.5 Pop '-4' and '8', divide, push '-0.5'.
11 + 3 8 3, 7.5 Pop '3' and '8', add, push '7.5'.
Final result: 7.5
2. Queues
What is a Queue?
A queue is a linear data structure that follows the First-In-First-Out (FIFO) principle. This means that the
element that is inserted first is the one that is removed first. A queue is an ordered list of elements
where insertions happen at one end, called the rear, and deletions happen from the other end, called
the front.
Key Operations:
• Enqueue: Insert an element at the rear of the queue.
• Dequeue: Remove an element from the front of the queue.
Queues are commonly used in situations where data needs to be processed in the order it arrives, such
as in print spooling or task scheduling systems.
Front and Rear
• Front: The front of the queue refers to the position where elements are removed (dequeue
operation). It indicates the first element of the queue.
• Rear: The rear of the queue refers to the position where new elements are added (enqueue
operation). It indicates the last element added to the queue.
Real-time Examples of Queue:
1. Ticket counter line: People are served in the order they arrive, the first person in line is the first
to be served.
2. Call center management: The first customer to call is the first customer to get assistance.
3. CPU Scheduling: Processes are queued up in a ready queue and are executed based on their
arrival.
Applications of Queues:
1. Operating systems: Queues are used for managing tasks in job scheduling, printer spooling, and
interrupt handling.
2. Network applications: Queues manage the buffer in networking for packet processing.
3. Simulations: Queues are used to simulate real-world waiting lines, such as people at a counter,
cars at a toll booth, etc.
4. Breadth-First Search (BFS) algorithm: Queues are used in this graph traversal algorithm to track
nodes at the current level before moving to the next.
Advantages of Queues:
1. Order Preservation: Queues maintain the order of elements as they are added, making it ideal
for scenarios that require processing in arrival order.
2. Simple to implement: Easy to implement using arrays or linked lists.
3. Widely applicable: Useful in various domains such as operating systems, network traffic
management, and real-time simulations.
• Functions: For all queue ∈ Queue, item ∈ element, max-queue-size ∈ positive integer.
insert():
#include <stdio.h>
#define MAX 5
int queue[MAX];
int front = -1, rear = -1;
return;
}
if (front == -1) { // First insertion
front = 0;
}
rear++;
queue[rear] = item;
printf("Inserted %d into the queue\n", item);
}
Delete() Function:
// Function to delete an element from the front of the queue
void deletefront() {
if (front == -1 || front > rear) {
printf("Queue is empty\n");
if (front > rear) { // Reset when the queue becomes empty
front = rear = -1;
return;
}
printf("Deleted %d from the queue\n", queue[front++]);
}
}
display:
void display() {
if (front == -1) {
printf("Queue is empty\n");
return;
}
printf("Queue elements are: ");
for (int i = front; i <= rear; i++) {
printf("%d ", queue[i]);
}
printf("\n");
}
#include <stdio.h>
#define MAX 5
int queue[MAX];
int front = -1, rear = -1;
}
}
while (1) {
printf("\nQueue Operations: \n");
printf("1. Insert\n");
printf("2. Delete\n");
printf("3. Display\n");
printf("4. Exit\n");
printf("Enter your choice: ");
scanf("%d", &choice);
switch (choice) {
case 1:
printf("Enter the element to insert: ");
scanf("%d", &item); // Prompt and read input directly under case 1
inserrear(item);
break;
case 2:
deletefront();
break;
case 3:
display();
break;
case 4:
printf("Exiting...\n");
return 0;
default:
printf("Invalid choice! Please try again.\n");
}
}
return 0;
}
Sample Output:
Queue Operations:
1. Insert
2. Delete
3. Display
4. Exit
Enter your choice: 1
Enter the element to insert: 15
Inserted 15 into the queue
Queue Operations:
1. Insert
2. Delete
3. Display
4. Exit
Enter your choice: 3
Queue elements are: 15
Queue Operations:
1. Insert
2. Delete
3. Display
4. Exit
Enter your choice: 2
Deleted 15 from the queue
Queue Operations:
1. Insert
2. Delete
3. Display
4. Exit
Enter your choice: 4
Exiting...