C Programming and Data Structures 41394658 2025 06-20-08 20
C Programming and Data Structures 41394658 2025 06-20-08 20
1. Searching Algorithms
Searching algorithms are used to find the position of a specific element within a data
structure.
1.1 Linear Search
• Description: Checks each element in the list sequentially until the desired element is
found or the list ends.
• Time Complexity: O(n) - where nnn is the number of elements in the list.
• Use Case: Suitable for unsorted or small datasets.
• Example Code:
def linear_search(arr, target):
for index, value in enumerate(arr):
if value == target:
return index
return -1
1.2 Binary Search
• Description: Efficiently finds an element in a sorted list by repeatedly dividing the
search interval in half.
• Time Complexity: O(log n) - where nnn is the number of elements in the list.
• Use Case: Requires the list to be sorted beforehand.
• Example Code:
def binary_search(arr, target):
low, high = 0, len(arr) - 1
while low <= high:
mid = (low + high) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
low = mid + 1
else:
high = mid - 1
return -1
2. Sorting Algorithms
Sorting algorithms are used to arrange elements in a specific order, typically ascending or
descending.
2.1 Bubble Sort
• Description: Repeatedly steps through the list, compares adjacent elements, and
swaps them if they are in the wrong order.
• Time Complexity: O(n^2) - where nnn is the number of elements in the list.
• Use Case: Simple to implement but inefficient for large lists.
• Example Code:
def bubble_sort(arr):
n = len(arr)
for i in range(n):
for j in range(0, n-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
2.2 Selection Sort
• Description: Divides the list into a sorted and an unsorted region. Repeatedly selects
the smallest (or largest) element from the unsorted region and moves it to the sorted
region.
• Time Complexity: O(n^2) - where nnn is the number of elements in the list.
• Use Case: Simple and intuitive, but inefficient for large datasets.
• Example Code:
def selection_sort(arr):
n = len(arr)
for i in range(n):
min_index = i
for j in range(i+1, n):
if arr[j] < arr[min_index]:
min_index = j
arr[i], arr[min_index] = arr[min_index], arr[i]
2.3 Insertion Sort
• Description: Builds the final sorted list one item at a time. Takes each element from
the input and finds the location it belongs in the sorted list, and inserts it there.
• Time Complexity: O(n^2) - where nnn is the number of elements in the list.
• Use Case: Efficient for small datasets or nearly sorted lists.
• Example Code:
def insertion_sort(arr):
for i in range(1, len(arr)):
key = arr[i]
j=i-1
while j >= 0 and key < arr[j]:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
2.4 Merge Sort
• Description: Divides the list into halves, recursively sorts each half, and then merges
the sorted halves.
• Time Complexity: O(n log n) - where nnn is the number of elements in the list.
• Use Case: Efficient for large datasets and stable sort.
• Example Code:
def merge_sort(arr):
if len(arr) > 1:
mid = len(arr) // 2
left_half = arr[:mid]
right_half = arr[mid:]
merge_sort(left_half)
merge_sort(right_half)
i=j=k=0
while i < len(left_half) and j < len(right_half):
if left_half[i] < right_half[j]:
arr[k] = left_half[i]
i += 1
else:
arr[k] = right_half[j]
j += 1
k += 1
Linked Storage Representation is a method of organizing data in which elements are stored in
nodes that are connected through links. This approach is often used in various data structures
like linked lists, trees, and graphs. Unlike arrays, where elements are stored in contiguous
memory locations, linked storage allows for more flexible data management by dynamically
allocating memory.
Key Concepts of Linked Storage Representation
1. Nodes and Links
• Node: The basic unit of storage in a linked structure. Each node typically contains:
o Data: The value or information the node holds.
o Link (Pointer): A reference to the next node in the sequence (in singly linked
lists) or both the next and previous nodes (in doubly linked lists).
2. Types of Linked Lists
1. Singly Linked List
o Description: Each node contains a single link pointing to the next node in the
sequence.
o Structure: Node -> Next Node -> Next Node -> NULL
o Operations:
▪ Insertion: Add nodes at the beginning, end, or between existing nodes.
▪ Deletion: Remove nodes from the beginning, end, or a specific
position.
▪ Traversal: Access nodes sequentially from the head to the end.
o Example Code:
class Node:
def __init__(self, data=None):
self.data = data
self.next = None
class SinglyLinkedList:
def __init__(self):
self.head = None
def append(self, data):
new_node = Node(data)
if self.head is None:
self.head = new_node
return
last = self.head
while last.next:
last = last.next
last.next = new_node
def print_list(self):
current = self.head
while current:
print(current.data, end=" -> ")
current = current.next
print("NULL")
2. Doubly Linked List
o Description: Each node contains two links, one pointing to the next node and
one pointing to the previous node.
o Structure: NULL <- Prev Node <-> Current Node <-> Next Node -> NULL
o Operations: Similar to singly linked lists but allows bidirectional traversal.
o Example Code:
class DoublyNode:
def __init__(self, data=None):
self.data = data
self.next = None
self.prev = None
class DoublyLinkedList:
def __init__(self):
self.head = None
def print_list(self):
current = self.head
while current:
print(current.data, end=" <-> ")
current = current.next
print("NULL")
class CircularLinkedList:
def __init__(self):
self.head = None
def print_list(self):
current = self.head
if self.head is None:
return
while True:
print(current.data, end=" -> ")
current = current.next
if current == self.head:
break
print("NULL")
LINEAR DATA STRUCTURE -STACKS
Stack Data Structure
A stack is a fundamental linear data structure that follows 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 a stack of plates where you can only take the top plate off or add a new plate on
top.
Key Operations:
1. Push: Add an element to the top of the stack.
2. Pop: Remove the element from the top of the stack.
3. Peek (or Top): Retrieve the element at the top of the stack without removing it.
4. IsEmpty: Check if the stack is empty.
Common Applications:
• Function Call Management: Keeping track of function calls and local variables.
• Expression Evaluation: Evaluating expressions in postfix notation (Reverse Polish
notation).
• Undo Mechanism: Implementing undo operations in software.
Implementation
Stacks can be implemented using arrays or linked lists. Below are implementations for both
methods:
1. Stack Implementation Using Array
Description:
• Uses a fixed-size list (array) to store stack elements.
• Provides constant time complexity for push and pop operations.
Example Code (Python):
class StackArray:
def __init__(self):
self.stack = []
def pop(self):
if not self.is_empty():
return self.stack.pop()
raise IndexError("Pop from empty stack")
def peek(self):
if not self.is_empty():
return self.stack[-1]
raise IndexError("Peek from empty stack")
def is_empty(self):
return len(self.stack) == 0
def print_stack(self):
print(self.stack)
# Example Usage
stack = StackArray()
stack.push(1)
stack.push(2)
print(stack.peek()) # Output: 2
stack.pop()
stack.print_stack() # Output: [1]
class StackLinkedList:
def __init__(self):
self.top = None
def pop(self):
if not self.is_empty():
pop_value = self.top.data
self.top = self.top.next
return pop_value
raise IndexError("Pop from empty stack")
def peek(self):
if not self.is_empty():
return self.top.data
raise IndexError("Peek from empty stack")
def is_empty(self):
return self.top is None
def print_stack(self):
current = self.top
while current:
print(current.data, end=" -> ")
current = current.next
print("NULL")
# Example Usage
stack = StackLinkedList()
stack.push(1)
stack.push(2)
print(stack.peek()) # Output: 2
stack.pop()
stack.print_stack() # Output: 1 -> NULL
Complexity Analysis
• Time Complexity:
o Push: O(1) — Adding an element to the stack.
o Pop: O(1) — Removing the top element from the stack.
o Peek: O(1) — Accessing the top element of the stack.
o IsEmpty: O(1) — Checking if the stack is empty.
• Space Complexity:
o Array Implementation: O(n) — Space used is proportional to the number of
elements.
o Linked List Implementation: O(n) — Space used is proportional to the
number of nodes.
Key Operations:
1. Enqueue: Add an element to the rear (end) of the queue.
2. Dequeue: Remove an element from the front of the queue.
3. Peek (or Front): Retrieve the element at the front of the queue without removing it.
4. IsEmpty: Check if the queue is empty.
Common Applications:
• Task Scheduling: Managing tasks in operating systems or print spooling.
• Breadth-First Search (BFS): Traversal algorithm for graphs and trees.
• Buffer Management: Implementing buffers like in IO devices, network packets, etc.
Implementation
Queues can be implemented using arrays, linked lists, or using specialized data structures
such as deque (double-ended queue). Here are implementations for both array and linked list
based queues:
1. Queue Implementation Using Array
Description:
• Uses a fixed-size list (array) to store queue elements.
• Often requires handling of index wrap-around using circular buffer techniques.
Example Code (Python):
class QueueArray:
def __init__(self, size):
self.size = size
self.queue = [None] * size
self.front = 0
self.rear = -1
self.count = 0
def dequeue(self):
if self.is_empty():
raise IndexError("Dequeue from empty queue")
item = self.queue[self.front]
self.queue[self.front] = None
self.front = (self.front + 1) % self.size
self.count -= 1
return item
def peek(self):
if self.is_empty():
raise IndexError("Peek from empty queue")
return self.queue[self.front]
def is_empty(self):
return self.count == 0
def is_full(self):
return self.count == self.size
def print_queue(self):
if self.is_empty():
print("Queue is empty")
return
i = self.front
while True:
print(self.queue[i], end=" -> ")
if i == self.rear:
break
i = (i + 1) % self.size
print("NULL")
# Example Usage
queue = QueueArray(5)
queue.enqueue(1)
queue.enqueue(2)
print(queue.peek()) # Output: 1
queue.dequeue()
queue.print_queue() # Output: 2 -> NULL
2. Queue Implementation Using Linked List
Description:
• Uses nodes linked together to form the queue.
• Each node contains data and a reference to the next node.
• Supports dynamic size.
Example Code (Python):
class Node:
def __init__(self, data):
self.data = data
self.next = None
class QueueLinkedList:
def __init__(self):
self.front = None
self.rear = None
self.size = 0
def dequeue(self):
if self.is_empty():
raise IndexError("Dequeue from empty queue")
item = self.front.data
self.front = self.front.next
if self.front is None:
self.rear = None
self.size -= 1
return item
def peek(self):
if self.is_empty():
raise IndexError("Peek from empty queue")
return self.front.data
def is_empty(self):
return self.front is None
def print_queue(self):
current = self.front
while current:
print(current.data, end=" -> ")
current = current.next
print("NULL")
# Example Usage
queue = QueueLinkedList()
queue.enqueue(1)
queue.enqueue(2)
print(queue.peek()) # Output: 1
queue.dequeue()
queue.print_queue() # Output: 2 -> NULL
Complexity Analysis
• Time Complexity:
o Enqueue: O(1) — Adding an element to the rear.
o Dequeue: O(1) — Removing the element from the front.
o Peek: O(1) — Accessing the front element.
o IsEmpty: O(1) — Checking if the queue is empty.
• Space Complexity:
o Array Implementation: O(n) — Space used is proportional to the number of
elements.
o Linked List Implementation: O(n) — Space used is proportional to the number
of nodes.
Advantages and Disadvantages
Advantages:
• Efficient Operations: All primary operations (enqueue, dequeue, peek) are O(1).
• Simple Implementation: Straightforward to implement and use.
Disadvantages:
• Fixed Size (Array): For the array-based implementation, the size of the queue is fixed
and may require resizing.
• Memory Overhead (Linked List): The linked list implementation uses extra memory
for storing node pointers.
Properties:
• Full Binary Tree: Every node has either 0 or 2 children.
• Complete Binary Tree: All levels, except possibly the last, are fully filled, and all
nodes are as far left as possible.
• Perfect Binary Tree: All internal nodes have two children, and all leaves are at the
same level.
Example Code (Python):
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
class BinaryTree:
def __init__(self):
self.root = None
def inorder(self):
return self._inorder(self.root)
# Example Usage
tree = BinaryTree()
tree.insert(5)
tree.insert(3)
tree.insert(7)
print(tree.inorder()) # Output: [3, 5, 7]
2. Binary Search Tree (BST)
A binary search tree is a binary tree where each node follows the property that all nodes in the
left subtree are less than the root node, and all nodes in the right subtree are greater.
Operations:
• Search: O(log n) on average.
• Insertion/Deletion: O(log n) on average.
Example Code (Python):
The BinaryTree class above can be used as a Binary Search Tree with minor modifications.
3. AVL Tree
An AVL tree is a self-balancing binary search tree where the difference in heights of left and
right subtrees (balance factor) of any node is at most 1.
Operations:
• Insertion/Deletion: O(log n) due to balancing.
Example Code (Python):
Implementation of AVL Tree requires rotations to maintain balance, which is more complex.
Here's a high-level outline:
class AVLNode:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
self.height = 1
class Trie:
def __init__(self):
self.root = TrieNode()
# Example Usage
trie = Trie()
trie.insert("hello")
print(trie.search("hello")) # Output: True
print(trie.search("hell")) # Output: False
Complexity Analysis
• Time Complexity for Balanced Trees (e.g., AVL, Red-Black):
o Search/Insert/Delete: O(log n), where n is the number of nodes.
• Time Complexity for Tries:
o Search/Insert: O(m), where m is the length of the key (string).
• Space Complexity:
o Binary Trees: O(n), where n is the number of nodes.
o Tries: O(n * m), where n is the number of words and m is the length of the
average word.
Advantages and Disadvantages
Advantages:
• Efficient Searching/Insertion: Balanced trees ensure logarithmic time complexity for
operations.
• Hierarchical Representation: Natural fit for hierarchical data.
Disadvantages:
• Complexity: Self-balancing trees and tries have complex implementation details.
• Space Overhead: Tries may use significant space due to storing nodes for all prefixes.
IMPORTANT MCQS
1. What is the primary characteristic of a tree data structure?
o A) Linear arrangement
o B) Hierarchical arrangement
o C) Random arrangement
o D) Circular arrangement
Answer: B) Hierarchical arrangement
2. In a binary tree, each node can have at most how many children?
o A) 1
o B) 2
o C) 3
o D) 4
Answer: B) 2
3. What is the maximum number of nodes at level 'L' in a binary tree?
o A) 2^L
o B) 2^(L-1)
o C) 2^(L+1)
o D) 2^(L-2)
Answer: A) 2^L
4. Which of the following is true about a binary search tree (BST)?
o A) All nodes have exactly two children
o B) Left subtree nodes are greater than the root node
o C) Right subtree nodes are smaller than the root node
o D) Left subtree nodes are smaller than the root node, and right subtree nodes
are greater
Answer: D) Left subtree nodes are smaller than the root node, and right subtree nodes are
greater
2. Binary Trees
5. What is the depth of a tree?
o A) The number of nodes in the tree
o B) The length of the path from the root to the deepest leaf
o C) The number of edges from the root to the deepest leaf
o D) The height of the root node
Answer: C) The number of edges from the root to the deepest leaf
6. Which traversal method visits nodes in the order: left subtree, root, right
subtree?
o A) Pre-order
o B) In-order
o C) Post-order
o D) Level-order
Answer: B) In-order
7. What type of binary tree has all levels fully filled except possibly the last one?
o A) Full Binary Tree
o B) Complete Binary Tree
o C) Perfect Binary Tree
o D) Balanced Binary Tree
Answer: B) Complete Binary Tree
8. What is the height of a perfect binary tree with 'n' nodes?
o A) log2(n+1)
o B) log2(n)
o C) log2(n) - 1
o D) log2(n) + 1
Answer: C) log2(n) - 1
3. AVL and Red-Black Trees
9. Which property is maintained in an AVL tree?
o A) All nodes have two children
o B) Height of the left and right subtrees of any node differs by at most 1
o C) All nodes in the right subtree are less than the root node
o D) All nodes in the left subtree are greater than the root node
Answer: B) Height of the left and right subtrees of any node differs by at most 1
10. Which of the following is a key characteristic of a Red-Black tree?
o A) Nodes are colored red or black to ensure balance
o B) Every node has exactly two children
o C) All nodes are always balanced
o D) It’s a self-balancing tree but does not use colors
Answer: A) Nodes are colored red or black to ensure balance
4. Queues
11. In a queue, which operation removes an element from the front?
o A) Enqueue
o B) Dequeue
o C) Peek
o D) Push
Answer: B) Dequeue
12. What is the main advantage of using a circular queue over a linear queue?
o A) It has more space complexity
o B) It can efficiently utilize the available space
o C) It requires more memory
o D) It has slower operations
Answer: B) It can efficiently utilize the available space
13. Which data structure can be implemented using two stacks to create a queue?
o A) Priority Queue
o B) Circular Queue
o C) Deque
o D) FIFO Queue
Answer: D) FIFO Queue
14. What is the time complexity of the enqueue operation in a queue implemented
with a linked list?
o A) O(1)
o B) O(n)
o C) O(log n)
o D) O(n^2)
Answer: A) O(1)
5. Trie and Advanced Trees
15. In a Trie, what does each node represent?
o A) A unique character or prefix
o B) A complete string
o C) A number
o D) A list of strings
Answer: A) A unique character or prefix
16. Which type of tree is used for balancing file directories in a file system?
o A) Binary Tree
o B) AVL Tree
o C) Red-Black Tree
o D) B-Tree
Answer: D) B-Tree
17. What is the main advantage of a B-Tree over a binary search tree?
o A) It has a higher height
o B) It has a larger branching factor, leading to fewer levels
o C) It supports more complex operations
o D) It requires more memory
Answer: B) It has a larger branching factor, leading to fewer levels
18. What operation is used to insert a word into a Trie data structure?
o A) Add
o B) Insert
o C) Push
o D) Store
Answer: B) Insert
6. General Knowledge
19. In which of the following scenarios is a queue most appropriate?
o A) Function call management
o B) Task scheduling
o C) Evaluating mathematical expressions
o D) Implementing undo operations
Answer: B) Task scheduling
20. What does a node's height represent in a tree?
o A) The number of nodes in the subtree
o B) The number of edges from the node to its deepest leaf
o C) The total number of children of the node
o D) The depth of the node in the tree
Answer: B) The number of edges from the node to its deepest leaf