Data Structure
Data Structure
Data Structure
CAP4103
School of Engineering
Department of Computer Science and Engineering
Submitted By
Student Name Rakesh Dey sarkar
Enrolment Number 230160307023
Programme Masters in Computer Application
Department Computer Science and Engineering
Session/Semester 2023-2025/Second Semester
Submitted To
Faculty Name Ms. Sapna Sharma
2
1. Arrays:
An array is a collection of elements, each identified by an index or a
key.
Elements in an array are stored in contiguous memory locations.
Accessing elements in an array is fast, O(1) time complexity.
3
8. Heaps:
A specialized tree-based data structure that satisfies the heap property.
Max Heap: Parent nodes are greater than or equal to their child nodes.
Min Heap: Parent nodes are less than or equal to their child nodes.
Used for priority queues and heap sort.
These data structures serve as building blocks for designing algorithms and solving
various computational problems. The choice of the appropriate data structure
depends on the specific requirements and characteristics of the problem at hand.
5
# Declaring an array
my_array = [1, 2, 3, 4, 5]
# Accessing elements
# Modifying elements
Arrays are widely used in programming for tasks like storing lists of items, iterating
through collections, and implementing algorithms that require constant-time access
to elements. Despite their efficiency for random access, arrays may have limitations,
such as fixed size and potential inefficiencies in insertions and deletions, particularly
in the middle of the array. In such cases, other data structures like linked lists may be
more suitable.
|-----------|-----------|-----------|-----------|-----|-------------|
^ Starting address of the array
In this diagram, each box represents the memory location of an array element, and
the arrow indicates the starting address of the array. Accessing an element involves a
direct calculation based on the index and the size of each element. This simplicity
and efficiency in memory addressing make arrays a fundamental and widely used
data structure in programming.
In computer science, arrays come in various types, each serving specific purposes and
addressing different requirements. Here are some of the common types of arrays:
1.One-dimensional Array:
A simple, linear array where elements are stored in a single line or row.
Accessing elements involves using a single index.
Examples include arrays of integers, characters, or floating-point
numbers.
# One-dimensional array in Python
my_array = [1, 2, 3, 4, 5]
2.Multi-dimensional Array:
Arrays with more than one dimension. Common types include 2D arrays
(matrices) and 3D arrays.
Elements are accessed using multiple indices corresponding to the array's
dimensions.
Useful for representing tables, grids, and matrices.
# Two-dimensional array (matrix) in Python
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
3. Dynamic Array:
An array that can dynamically resize during runtime.
Provides a higher-level abstraction than traditional fixed-size arrays.
Commonly used in languages like Python with lists or Java with ArrayList.
8
dynamic_array = [1, 2, 3]
4. Jagged Array:
An array of arrays where each sub-array can have a different size.
Useful when the number of elements varies in different dimensions.
Offers flexibility but may result in uneven memory allocation.
// Jagged array in C#
5. Sparse Array:
An array in which most of the elements have the same default value, typically
zero or null.
Efficiently stores and represents arrays with a small number of non-default
elements.
Often used in applications dealing with large datasets with sparse
characteristics.
# Sparse array in Python using a dictionary
sparse_array = {0: 1, 2: 3, 5: 6}
6. String Array:
An array where each element is a string.
Useful for storing and manipulating a collection of strings.
// String array in Java
7. Circular Array:
An array where the last element is followed by the first element, creating a
circular structure.
Useful in scenarios where cyclic operations are required, like rotating
elements.
# Circular array in Python
1. Operations:
Push: Adds an element to the top of the stack.
Pop: Removes the element from the top of the stack.
Peek (or Top): Retrieves the element from the top without removing it.
isEmpty: Checks if the stack is empty.
2. Usage:
Stacks are commonly used in various algorithms and applications,
including:
Managing function calls and recursion (call stack).
Undo mechanisms in applications.
Parsing expressions and syntax checking.
Depth-first search algorithms.
Memory management, such as maintaining a pool of available
memory blocks.
3. Implementation:
Stacks can be implemented using arrays or linked lists. The choice of
implementation depends on the specific requirements and constraints
of the application.
class Stack:
def __init__(self):
self.items = []
def is_empty(self):
return len(self.items) == 0
11
self.items.append(item)
def pop(self):
if not self.is_empty():
return self.items.pop()
def peek(self):
if not self.is_empty():
return self.items[-1]
def size(self):
return len(self.items)
my_stack = Stack()
my_stack.push(1)
my_stack.push(2)
my_stack.push(3)
print(my_stack.peek()) # Output: 3
print(my_stack.pop()) # Output: 3
print(my_stack.pop()) # Output: 2
print(my_stack.size()) # Output: 1
12
In this example, the stack is used to push and pop elements. The last element pushed
onto the stack (3) is the first one to be popped off. Stacks are versatile and play a
crucial role in many aspects of computer science and programming.
The term "LIFO" stands for "Last In, First Out," and it describes the fundamental
behavior of a stack data structure. In a stack, the last element that is added (or
"pushed") onto the stack is the first one to be removed (or "popped") from the stack.
The LIFO property simplifies the operations on a stack and has practical applications
in various computing scenarios. For example:
The LIFO principle simplifies the management of items in a stack, making it a natural
and efficient choice for various applications in computer science and programming.
Below are simple algorithms for the basic operations on a stack: PUSH, POP, PEEK,
and CHANGE.
Stack Implementation
13
class Stack:
def __init__(self):
self.items = []
def is_empty(self):
return len(self.items) == 0
self.items.append(item)
def pop(self):
if not self.is_empty():
return self.items.pop()
def peek(self):
if not self.is_empty():
return self.items[-1]
def size(self):
return len(self.items)
self.items[index] = new_value
PUSH Algorithm
stack.push(element)
POP Algorithm
The POP operation removes the element from the top of the stack.
Algorithm POP(stack):
return stack.pop()
PEEK Algorithm
The PEEK operation retrieves the element from the top of the stack without removing
it.
Algorithm PEEK(stack):
return stack.peek()
CHANGE Algorithm
The CHANGE operation modifies the value of an element at a specific index in the
stack
stack.change(index, new_value)
These algorithms assume the stack is implemented using the Stack class mentioned
earlier. Note that the CHANGE operation checks whether the index is within the
valid range before attempting to change the value. Adjustments may be needed
based on the specific language or context of your implementation.
15
1. Circular Structure:
The last node in the list points to the first node, creating a circular or
cyclic structure.
In a regular linked list, the last node typically points to null, indicating
the end of the list.
2. Traversal:
Traversal in a circular linked list can start from any node, and you can
continue traversing the entire list by following the next pointers until
you reach the starting node again.
This circular structure eliminates the need to check for null pointers
during traversal.
3. Insertion and Deletion:
Insertion and deletion operations are generally more straightforward in
a circular linked list compared to a singly linked list. Adding or
removing a node involves adjusting the next pointers of neighboring
nodes.
4. Applications:
Circular linked lists are used in applications where a continuous cycle of
operations is required, or when you need to efficiently rotate elements
in a circular fashion.
Examples include scheduling algorithms, where a set of tasks need to
be executed in a circular manner, or in gaming applications for
managing player turns.
5. Advantages:
Circular linked lists offer constant time complexity for insertion and
deletion at both the beginning and end of the list compared to singly
linked lists, which require traversal to the end for certain operations.
6. Disadvantages:
Traversing a circular linked list requires careful handling to avoid an
infinite loop, as there is no null pointer to indicate the end of the list.
16
In this example, the next pointer of the last node (with value 3) points back to the
first node (with value 1), forming a circular structure.
Circular linked lists, singly linked lists, and doubly linked lists are different types of
linked list structures, each with its own set of advantages and disadvantages. Let's
compare Circular Linked Lists with Singly Linked Lists and Doubly Linked Lists:
1. Complex Traversal:
Traversing a circular linked list requires careful handling to avoid an
infinite loop, as there is no null pointer to indicate the end of the list.
Extra caution is needed during traversal.
2. More Memory Overhead:
Circular linked lists may have slightly more memory overhead
compared to singly linked lists because each node contains an
additional next pointer, and there is an extra link from the last node
back to the first.
1. Simpler Implementation:
17
In summary, the choice between circular linked lists, singly linked lists, and doubly
linked lists depends on the specific requirements of the application. Circular linked
lists are advantageous when constant-time operations at both ends are crucial, and
simplicity in implementation is preferred. However, careful handling is required
during traversal to avoid infinite loops. Doubly linked lists provide bidirectional
traversal but come with increased complexity and potentially higher memory
overhead. Singly linked lists are a more straightforward alternative but may not offer
the same efficiency in certain operations.
Linked lists and arrays are both data structures used for organizing and storing
collections of elements, but they have different characteristics. Here are some
advantages of linked lists over arrays:
1. Dynamic Size:
Linked lists can easily grow or shrink in size during program execution.
Memory can be dynamically allocated or deallocated, allowing for
efficient memory usage based on the actual number of elements in the
list. In contrast, arrays typically have a fixed size when declared.
2. Constant-Time Insertion and Deletion:
Inserting or deleting elements in a linked list, particularly in the middle,
can be done in constant time, provided the reference to the node is
available. In arrays, these operations may require shifting elements,
resulting in a time complexity of O(n), where n is the number of
elements.
18
While linked lists have these advantages, it's important to note that they also come
with trade-offs, such as increased memory overhead due to the storage of pointers
and the lack of constant-time random access. The choice between a linked list and an
array depends on the specific requirements of the application and the nature of the
operations performed on the data.
Inorder: D H B EAFCIGJ
Ans: Tree traversal algorithms are used to visit and process all the nodes of a binary
tree. Here are the algorithms for Inorder, Preorder, and Postorder Traversal of a
binary tree:
class TreeNode:
self.key = key
self.left = None
self.right = None
In Inorder traversal, we visit the left subtree, then the current node, and finally the
right subtree.
Algorithm InorderTraversal(node):
if node is not null:
InorderTraversal(node.left)
Process(node)
InorderTraversal(node.right)
In Preorder traversal, we visit the current node first, followed by the left subtree, and
then the right subtree.
Algorithm PreorderTraversal(node):
Process(node)
PreorderTraversal(node.left)
PreorderTraversal(node.right)
20
In Postorder traversal, we visit the left subtree, the right subtree, and finally the
current node.
Algorithm PostorderTraversal(node):
PostorderTraversal(node.left)
PostorderTraversal(node.right)
Process(node)
Example Usage
Here's an example of creating a binary tree and using the above traversal algorithms:
def Process(node):
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
root.right.left = TreeNode(6)
root.right.right = TreeNode(7)
# Inorder Traversal
print("Inorder Traversal:")
InorderTraversal(root)
print()
21
# Preorder Traversal
print("Preorder Traversal:")
PreorderTraversal(root)
print()
# Postorder Traversal
print("Postorder Traversal:")
PostorderTraversal(root)
yaml
Inorder Traversal:
4251637
Preorder Traversal:
1245367
Postorder Traversal:
4526731
These traversal algorithms are fundamental for exploring and processing binary trees
in various applications, such as expression tree evaluation, tree-based searches, and
more.
To construct a binary tree from its preorder and inorder traversals, you can follow
these steps recursively. The idea is to pick the root node from the preorder traversal,
then find its position in the inorder traversal to determine the left and right subtrees.
Repeat this process for each subtree until the entire tree is constructed.
Let's construct the binary tree for the given preorder and inorder traversals:
Preorder: A B D H E C F G I J Inorder: D H B E A F C I G J
In the inorder traversal, find the position of A. The elements to the left
of A (D H B E) are the left subtree, and the elements to the right (F C I G
J) are the right subtree.
3. Repeat for Left and Right Subtrees:
For the left subtree, the next element in the preorder traversal is B. In
the left subtree's inorder traversal (D H B E), B is the root.
For the right subtree, the next element in the preorder traversal is C. In
the right subtree's inorder traversal (F C I G J), C is the root.
4. Repeat for Each Subtree:
Continue this process recursively for each subtree until all nodes are
placed.
class TreeNode:
self.key = key
self.left = None
self.right = None
return None
root_val = preorder[0]
root = TreeNode(root_val)
root_index = inorder.index(root_val)
return root
23
def print_inorder(node):
if node:
print_inorder(node.left)
print_inorder(node.right)
# Given traversals
preorder_traversal = ['A', 'B', 'D', 'H', 'E', 'C', 'F', 'G', 'I', 'J']
inorder_traversal = ['D', 'H', 'B', 'E', 'A', 'F', 'C', 'I', 'G', 'J']
print_inorder(root_node)
DHBEAFCIGJ
The height of a tree is the length of the longest path from the root to a leaf node. It
represents the depth or level of the tree. The height is usually measured as the
number of edges on the longest path, so a tree with a single node has a height of 0.
Balanced Trees: Trees with nearly equal heights for left and right subtrees are
considered balanced. Examples include AVL trees and Red-Black trees.
Unbalanced Trees: Trees where one subtree is significantly deeper than the
other are unbalanced. Unbalanced trees can lead to poor performance in
terms of search and retrieval.
A complete binary tree is a binary tree in which every level is completely filled, except
possibly the last level, which is filled from left to right. In other words, all nodes are as
left as possible.
Properties:
The last level is filled from left to right.
If a node has a left child, it must have a right child.
The height of a complete binary tree is logarithmic with respect to the
number of nodes.
Complete binary trees are efficient for array-based representations and heap data
structures.
An expression tree is a binary tree used to represent expressions in a natural way that
reflects the structure of the expression. Each node in the tree represents an operator
or operand, and the leaves are the operands.
/\
2 +
/\
3 4
(iv) Sibling:
In the context of trees, siblings are nodes that share the same parent. If two nodes
are siblings, they are at the same level in the tree and have the same parent.
Example: In the tree below, nodes B and C are siblings, as are nodes D and E.
A
/\
B C
/\
D E
Sibling Relationship: Nodes that are not siblings are either ancestors or
descendants of each other.
A full binary tree (sometimes called a proper or 2-tree) is a binary tree in which every
node has either 0 or 2 children. In other words, every level is completely filled.
Properties:
The number of leaf nodes in a full binary tree is equal to the number of
internal nodes plus one.
The height of a full binary tree with n nodes is log₂(n + 1) - 1.
Example: The following is an example of a full binary tree:
A
/\
B C
/\
D E
26
Full binary trees are often encountered in computer science, especially in the analysis
of algorithms and data structures. They have efficient array-based representations
and are used in applications like Huffman coding and binary heaps.
27
Ans:
An abstract data type (ADT) is a high-level description of a set of operations that can
be performed on a data structure, along with the properties of these operations. It
defines a logical model for the data and the operations that can be performed on
that data, without specifying the internal details of how the data is represented or
how the operations are implemented.
In other words, an abstract data type encapsulates the behavior of a data structure,
focusing on what operations can be performed on it and what properties these
operations should have, rather than how those operations are implemented. This
separation of concerns allows for flexibility in choosing different implementations
while maintaining a consistent interface.
1. Encapsulation: ADTs encapsulate the data and operations into a single unit,
hiding the internal details from the user. Users interact with the data structure
through a well-defined interface.
2. Abstraction: ADTs provide a level of abstraction by specifying what
operations can be performed and their expected behavior without detailing
how these operations are carried out. This abstraction allows users to work
with the data structure without needing to understand its internal workings.
3. Flexibility: ADTs allow for multiple implementations. As long as the specified
operations and their properties are satisfied, different underlying data
structures can be used to achieve the same abstract behavior.
4. Data Hiding: ADTs hide the internal representation of data from the users.
Users only need to be aware of the interface and not the implementation
details.
5. Reusability: ADTs promote code reusability. Once an ADT is defined, it can be
reused in various applications without modification, as long as the interface
remains consistent.
Stack: An abstract data type that supports operations like push, pop, and
peek.
Queue: An abstract data type that supports operations like enqueue and
dequeue.
28
Languages like Java and Python often provide standard libraries that include
implementations of various abstract data types, allowing developers to use these
data structures without worrying about their internal details.
In an Abstract Data Type (ADT), the focus is on specifying the logical behavior of the
data and the operations that can be performed on it, while intentionally ignoring
certain details related to the internal representation and implementation. Here are
aspects that are typically not concerned in an ADT:
1. Implementation Details:
ADTs do not specify how the data and operations are implemented
internally. The actual algorithm, data structures, or programming details
are hidden from users.
For example, the implementation of a stack could use an array, a linked
list, or some other structure, but the ADT only defines the push, pop,
and peek operations without specifying how these are carried out.
2. Data Representation:
The ADT does not dictate how the data is stored or represented in
memory. It abstracts away the details of the internal representation.
For instance, a set ADT may describe operations like adding, removing,
and checking membership of elements, but it doesn't specify whether a
hash table, a balanced tree, or another structure is used to implement
the set.
3. Algorithmic Complexity:
ADTs do not prescribe the time or space complexity of the operations.
The efficiency of the operations is not part of the ADT definition.
For instance, a queue ADT may define enqueue and dequeue
operations, but it doesn't specify whether these operations should have
constant, logarithmic, or linear time complexity.
4. Concurrency and Synchronization:
ADTs do not address issues related to concurrent access and
synchronization. How the data structure behaves in a multi-threaded
environment is not specified.
For example, a list ADT might define operations for adding and
removing elements, but it doesn't detail how these operations should
behave when accessed concurrently by multiple threads.
5. Error Handling:
29
By abstracting away these details, ADTs provide a high-level interface for users,
allowing them to work with the data structure based on its logical behavior without
needing to be concerned about the underlying implementation complexities. This
abstraction promotes flexibility, reusability, and modularity in software design.
The use of Abstract Data Types (ADTs) offers several advantages in software
development. Here are some key benefits:
Ans: In C, memory can be allocated in two primary ways: stack allocation and heap
allocation.
1. Stack Allocation:
Description:
Stack memory is a region of memory where local variables and function
call information are stored.
Memory is automatically allocated and deallocated as functions are
called and return.
It follows a Last In, First Out (LIFO) order, meaning the most recently
allocated memory is the first to be deallocated.
Example:
#include <stdio.h>
int main() {
return 0;
2. Heap Allocation:
Description:
Heap memory is a region of memory used for dynamic memory
allocation.
Memory must be managed explicitly, and functions like malloc(),
calloc(), and realloc() are used for allocation.
Memory allocated on the heap persists until explicitly deallocated using
free().
Heap memory provides more flexibility in terms of memory size and
lifetime compared to stack memory.
32
#include <stdio.h>
#include <stdlib.h>
int main() {
if (p != NULL) {
*p = 10;
} else {
return 0;
#include <stdio.h>
#include <stdlib.h>
int main() {
if (p != NULL) {
*p = 10;
} else {
return 0;
1. malloc():
Allocates a specified number of bytes of memory.
int *p = (int *)malloc(5 * sizeof(int));
calloc():
Allocates memory for an array of specified elements, initializing them to zero.
int *p = (int *)calloc(5, sizeof(int));
realloc():
Changes the size of the previously allocated memory.
int *p = (int *)malloc(5 * sizeof(int));
free():
Deallocates the memory previously allocated by malloc(), calloc(), or
realloc().
free(p);
Scope:
Stack memory is limited to the scope of the function or block in which
it is allocated.
34
Heap memory has a broader scope and can persist beyond the function
or block.
Automatic vs. Manual Management:
Stack memory is managed automatically by the compiler.
Heap memory requires manual management, and it's the responsibility
of the programmer to allocate and deallocate memory appropriately.
Size and Flexibility:
Stack memory is typically limited, and the size needs to be known at
compile time.
Heap memory provides more flexibility, and the size can be determined
at runtime.
Lifetime:
Stack memory is deallocated automatically when the function or block
exits.
Heap memory persists until explicitly deallocated, making it suitable for
long-term storage.
Choosing between stack and heap allocation depends on factors such as the desired
scope, lifetime, and flexibility of memory usage in a given program. Careful
management of heap-allocated memory is crucial to prevent memory leaks and
other issues.
35
1. Prefix Expression:
The operators are written before their operands.
Evaluation starts from the leftmost side.
Examples: +AB (Infix: A + B), *-AB/CD (Infix: (A - B) * (C / D)).
2. Postfix Expression:
The operators are written after their operands.
Evaluation starts from the leftmost side.
Examples: AB+ (Infix: A + B), AB-CD/* (Infix: (A - B) * (C / D)).
Example:
Infix Expression: A + B * C
Prefix Expression: +A*BC
Postfix Expression: ABC*+
1. Iteration:
Description:
A repetitive execution of a set of statements using loops or
other control flow constructs.
It is based on the concept of repeating a block of code until a
specified condition is met.
Advantages:
Often more memory-efficient.
Generally faster due to lower overhead.
2. Recursion:
Description:
A function calls itself directly or indirectly to solve a smaller
instance of the same problem.
It is based on the concept of breaking down a problem into
smaller subproblems.
36
Advantages:
Can lead to more concise and readable code.
Suitable for solving problems with inherent recursive structures.
Example:
1. Array:
Description:
A data structure that stores elements of the same type in
contiguous memory locations.
The size of the array is fixed during declaration.
Access to elements is done using indices.
Advantages:
Constant time access to elements using indices.
Memory-efficient for a fixed-size collection.
2. Linked List:
Description:
A data structure that consists of nodes, where each node
contains data and a reference (link) to the next node in the
sequence.
The size of the linked list can change dynamically during
program execution.
Access to elements is done sequentially, starting from the head
or tail.
Advantages:
Dynamic size adjustment (no need to declare size in advance).
Efficient insertion and deletion of elements.
Example:
In summary, the distinctions between prefix and postfix expressions, iteration and
recursion, and arrays and linked lists lie in their representations, usage patterns, and
characteristics, each serving different needs in programming and problem-solving.
37
Ans: In computer science and algorithm analysis, there are two main types of
complexities: time complexity and space complexity.
1. Time Complexity:
Definition:
Key Points:
It describes how the runtime of an algorithm grows as the size of the input
increases.
Time complexity is expressed using big O notation (e.g., O(n), O(log n),
O(n^2)), representing an upper bound on the growth rate of the algorithm's
runtime.
Example:
if element == target:
return True
return False
2. Space Complexity:
Definition:
Key Points:
Example:
def sumOfElements(arr):
total = 0
total += element
return total
Note: