Dsc-Module II
Dsc-Module II
Queue is a linear data structure that follows FIFO (First In First Out) Principle, so the
first element inserted is the first to be popped out.
FIFO Principle in Queue:
FIFO Principle states that the first element added to the Queue will be the first one to be
removed or processed. So, Queue is like a line of people waiting to purchase tickets, where
the first person in line is the first person served. (i.e. First Come First Serve).
Representation of Queue
Operations on Queue
1. Enqueue: Enqueue operation adds (or stores) an element to the end of the queue.
Steps:
1. Check if the queue is full. If so, return an overflow error and exit.
2. If the queue is not full, increment the rear pointer to the next available position.
3. Insert the element at the rear.
2.Dequeue: Dequeue operation removes the element at the front of the queue. The
following steps are taken to perform the dequeue operation:
1. Check if the queue is empty. If so, return an underflow error.
2. Remove the element at the front.
3. Increment the front pointer to the next element.
5. isEmpty Operation:
This operation returns a boolean value that indicates whether the queue is empty or not.
6. isFull Operation:
This operation returns a boolean value that indicates whether the queue is full or not.
Types of Queues
1. Simple Queue: Simple Queue simply follows FIFO Structure. We can only insert the
element at the back and remove the element from the front of the queue.
3. Circular Queue: This is a special type of queue where the last position is connected
back to the first position. Here also the operations are performed in FIFO order.
4. Priority Queue: A priority queue is a special queue where the elements are accessed
based on the priority assigned to them. They are of two types:
Ascending Priority Queue: In Ascending Priority Queue, the elements are
arranged in increasing order of their priority values. Element with smallest priority
value is popped first.
Descending Priority Queue: In Descending Priority Queue, the elements are
arranged in decreasing order of their priority values. Element with largest priority is
popped first.
return 0;
}
struct Queue
{
struct Node* front;
struct Node* rear;
};
displayQueue(&q);
dequeue(&q);
dequeue(&q);
displayQueue(&q);
printf("Front element is: %d\n", front(&q));
return 0;
}
LISTS
In data structures, a list is an ordered collection of elements, where each element is referred
to as a node. Lists can be used to store multiple items in a specific order, and they allow
various operations like insertion, deletion, and traversal.
Characteristics:
Fixed Size: Once an array is created, its size cannot be changed.
Random Access: Allows access to any element in constant time using the index.
Contiguous Memory Allocation: The elements are stored next to each other in
memory.
Operations:
Insert: Inserting elements may require shifting existing elements if inserting in the
middle or at the beginning.
Delete: Deleting an element may also require shifting the remaining elements.
Access: Accessing elements by index is done in constant time, O(1).
#include <stdio.h>
#define MAX 5
struct ArrayList {
int arr[MAX];
int size; // Tracks the current number of elements
};
void init(struct ArrayList* list)
{
list->size = 0;
}
void insert(struct ArrayList* list, int value)
{
if (list->size < MAX)
{
list->arr[list->size] = value;
list->size++;
} else
{
printf("List is full!\n");
}
}
void display(struct ArrayList* list)
{
for (int i = 0; i < list->size; i++)
{
printf("%d ", list->arr[i]);
}
printf("\n");
}
int main() {
struct ArrayList list;
init(&list);
insert(&list, 10);
insert(&list, 20);
insert(&list, 30);
insert(&list, 40);
insert(&list, 50);
display(&list); // Output: 10 20 30 40 50
return 0;
Characteristics:
Dynamic Size: The size of a linked list can grow or shrink dynamically, making it
efficient for memory usage.
Sequential Access: To access an element, you need to traverse the list from the head
to the desired node.
No Random Access: Unlike arrays, linked lists do not allow direct access to any
element using an index. You must traverse the list to access a node.
Operations:
Insert: You can insert elements at the beginning, middle, or end of the list.
Delete: You can delete an element from the beginning, middle, or end.
Search: You must traverse the list to find an element.
Traverse: You can traverse the list starting from the head node.
Applications of Lists:
Array-based Lists:
o Good for situations where you know the number of elements ahead of time
and need fast access to elements.
o Used in applications like database indexing, heaps, and static buffers.
Linked Lists:
o Ideal for applications where the size of the list is not known in advance, or the
list will be frequently modified (e.g., insertions and deletions).
o Commonly used in implementing queues, stacks, adjacency lists for graphs,
and various dynamic data structures.
The key property of a singly linked list is that it allows traversal in only one direction,
starting from the head (first node) and moving toward the tail (last node). The last node's
next pointer is NULL, indicating the end of the list.
Operations:
Insert at the beginning, middle, or end.
Delete from the beginning, middle, or end.
Search for an element.
Example:
#include <stdio.h>
#include <stdlib.h>
// Define the Node structure
struct Node
{
int data;
struct Node* next;
};
// Function to create a new node
struct Node* createNode(int value)
{
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = value;
newNode->next = NULL;
return newNode;
}
// Function to insert at the end
void insertAtEnd(struct Node** head, int value)
{
struct Node* newNode = createNode(value);
if (*head == NULL)
{
*head = newNode;
return;
}
struct Node* temp = *head;
while (temp->next != NULL)
{
temp = temp->next;
}
temp->next = newNode;
}
int main()
{
struct Node* head = NULL;
insertAtEnd(&head, 10);
insertAtEnd(&head, 20);
insertAtEnd(&head, 30);
displayList(head); // Output: 10 -> 20 -> 30 -> NULL
return 0;
}
A doubly linked list is similar to a singly linked list, but with a key difference: each node has
two pointers:
Operations:
Insert and Delete at the beginning, middle, or end.
Traverse both forwards and backwards.
Example:
#include <stdio.h>
#include <stdlib.h>
temp->next = newNode;
newNode->prev = temp;
}
if (temp == NULL) {
printf("Value %d not found in the list.\n", value);
return; // Node with value not found
}
free(temp);
printf("Node with value %d deleted.\n", value);
}
if (temp == NULL) {
printf("List is empty.\n");
return;
}
printf("Doubly Linked List (Head to Tail): ");
while (temp != NULL) {
printf("%d <-> ", temp->data);
temp = temp->next;
}
printf("NULL\n");
}
if (temp == NULL) {
printf("List is empty.\n");
return;
}
int main() {
struct Node* head = NULL; // Initialize the head of the list
return 0;
}
3. Circular Linked List
A circular linked list is a variation of either a singly linked list or a doubly linked list where
the last node's pointer (next in singly, both next and prev in doubly) points back to the first
node instead of NULL. This creates a circular structure.
Singly Circular Linked List: The next pointer of the last node points to the first
node.
Doubly Circular Linked List: The next pointer of the last node points to the first
node, and the prev pointer of the first node points to the last node.
A circular singly linked list allows continuous traversal of the list without needing to check
for a NULL value at the end of the list.
Operations:
Insert at the beginning, middle, or end.
Delete from the beginning, middle, or end.
Traverse continuously.
Example
#include <stdio.h>
#include <stdlib.h>
if (*head == NULL)
{
*head = newNode; // If the list is empty, the new node becomes the head
} else {
struct Node* temp = *head;
// Traverse to the last node (which points to the head)
while (temp->next != *head)
{
temp = temp->next;
}
temp->next = newNode;
newNode->next = *head; // The last node points to the head, forming a circle
}
}
if (temp->data == value) {
prev->next = temp->next;
free(temp);
} else {
printf("Node with value %d not found.\n", value);
}
}
// Deleting a node
deleteNode(&head, 20);
printf("\nCircular Linked List after deleting 20:\n");
display(head);
return 0;
}
A circular doubly linked list is a combination of a doubly linked list and a circular linked
list. The next pointer of the last node points to the first node, and the prev pointer of the first
node points to the last node. This allows for continuous two-way traversal.
Operations:
Example:
struct Node
{
int data;
struct Node* next;
struct Node* prev;
};
Both directions (head to tail Extra memory for prev pointer. Allows
Doubly Linked List
and tail to head) easy backward traversal.
Circular Singly One direction (head to tail, The last node points to the first node.
Linked List then back to head) Continuous traversal.
Circular Doubly Both directions (head to tail Both next and prev pointers link the first
Linked List and tail to head) and last nodes.
Advantages:
1. Searching
Searching algorithms are used to find specific elements within a dataset. The most common
searching algorithms include:
Linear Search:
o How it works: Iterates through each element in the list and compares it with
the target element.
o Time Complexity: O(n), where n is the number of elements in the list.
o Best for: Small or unsorted datasets.
Binary Search:
o How it works: Works on sorted datasets by repeatedly dividing the search
interval in half. It compares the target element to the middle element and
eliminates half of the remaining elements from consideration.
o Time Complexity: O(log n), where n is the number of elements in the list.
o Best for: Large, sorted datasets.
2. Sorting
Bubble Sort:
o How it works: Compares adjacent elements and swaps them if they are in the
wrong order. This process repeats until the list is sorted.
o Time Complexity: O(n²).
o Best for: Small datasets or when simplicity is needed.
Selection Sort:
o How it works: Divides the list into a sorted and unsorted region. It repeatedly
selects the smallest (or largest) element from the unsorted region and swaps it
with the first unsorted element.
o Time Complexity: O(n²).
o Best for: Simple and small datasets.
Insertion Sort:
o How it works: Builds the final sorted array one element at a time by inserting
elements into their correct position.
o Time Complexity: O(n²) in the worst case, but O(n) in the best case (if the list
is already sorted).
o Best for: Small datasets or nearly sorted datasets.
Merge Sort:
o How it works: Divides the list into two halves, sorts each half recursively, and
then merges the sorted halves back together.
o Time Complexity: O(n log n).
o Best for: Large datasets that need stable sorting.
Quick Sort:
o How it works: Selects a pivot element, partitions the list around the pivot, and
recursively sorts the sublists.
o Time Complexity: O(n log n) on average, O(n²) in the worst case (if the pivot
selection is poor).
o Best for: Large datasets with a good average-case performance.
Heap Sort:
o How it works: Builds a binary heap (either max-heap or min-heap) and
repeatedly extracts the maximum (or minimum) element to build a sorted list.
o Time Complexity: O(n log n).
o Best for: When constant time for finding the maximum/minimum is
important.
Sorting:
o Use insertion sort, selection sort, or bubble sort for small datasets or when
simplicity is preferred.
o Use merge sort, quick sort, or heap sort for large datasets requiring efficient
sorting.
#include <stdio.h>
int linearSearchRecursive(int arr[], int n, int target, int index)
{
// Base case: If the index reaches the size of the array, the target is not found
if (index == n)
{
return -1;
}
// If the element at the current index matches the target, return the index
if (arr[index] == target)
{
return index;
}
// Recur for the next index
return linearSearchRecursive(arr, n, target, index + 1);
}
int main()
{
int n, target, index;
// Input the size of the array
printf("Enter the number of elements: ");
scanf("%d", &n);
// Declare the array
int arr[n];
// Input the elements of the array
printf("Enter the elements:\n");
for (int i = 0; i < n; i++) {
scanf("%d", &arr[i]);
}
return 0;
}
#include <stdio.h>
int linearSearch(int arr[], int n, int target) {
// Loop through the array
for (int i = 0; i < n; i++) {
// If the element matches the target, return the index
if (arr[i] == target) {
return i;
}
}
// If element is not found, return -1
return -1;
}
int main() {
int n, target, index;
// Input the size of the array
printf("Enter the number of elements: ");
scanf("%d", &n);
// Declare the array
int arr[n];
// Input the elements of the array
printf("Enter the elements:\n");
for (int i = 0; i < n; i++) {
scanf("%d", &arr[i]);
}
// Input the target element to search for
printf("Enter the element to search for: ");
scanf("%d", &target);
// Perform non-recursive linear search
index = linearSearch(arr, n, target);
// Output the result
if (index != -1) {
printf("Element %d found at index %d.\n", target, index);
} else {
printf("Element %d not found in the array.\n", target);
}
return 0;
}
#include <stdio.h>
int binarySearchRecursive(int arr[], int left, int right, int target) {
// Base case: If the right index is greater than or equal to the left index
if (left <= right) {
// Find the middle index
int mid = left + (right - left) / 2;
// If the target is smaller than the middle element, search in the left half
if (arr[mid] > target) {
return binarySearchRecursive(arr, left, mid - 1, target);
}
int main() {
int n, target, index;
return 0;
}
// Loop while the left index is less than or equal to the right index
while (left <= right) {
int mid = left + (right - left) / 2; // Find the middle index
// If the target is smaller than the element at mid, search the left half
if (arr[mid] > target) {
right = mid - 1;
}
// Otherwise, search the right half
else {
left = mid + 1;
}
}
int main() {
int n, target, index;
return 0;
}
//Both recursive and non recursive function for linear search in a single program
#include <stdio.h>
// Non-recursive function to perform linear search
int linearSearchNonRecursive(int arr[], int n, int target) {
for (int i = 0; i < n; i++) {
if (arr[i] == target) {
return i; // Return index if element is found
}
}
return -1; // Return -1 if element is not found
}
// Recursive function to perform linear search
int linearSearchRecursive(int arr[], int n, int target, int index) {
// Base case: If the index reaches the size of the array, the target is not found
if (index == n) {
return -1;
}
int main() {
int n, target, index, choice;
//Both recursive and non recursive function for binary search in a single program
#include <stdio.h>
// Non-recursive function to perform binary search
int binarySearchNonRecursive(int arr[], int n, int target) {
int left = 0;
int right = n - 1;
// If the target is smaller than the element at mid, search in the left half
if (arr[mid] > target) {
right = mid - 1;
}
// Otherwise, search in the right half
else {
left = mid + 1;
}
}
// If the target is not found, return -1
return -1;
}
// If the target is smaller than the element at mid, search in the left half
if (arr[mid] > target) {
return binarySearchRecursive(arr, left, mid - 1, target);
}
int main() {
int n, target, index, choice;
// Input the elements of the array (must be sorted for binary search)
printf("Enter the elements in sorted order:\n");
for (int i = 0; i < n; i++) {
scanf("%d", &arr[i]);
}
if (choice == 1) {
// Perform non-recursive binary search
index = binarySearchNonRecursive(arr, n, target);
if (index != -1) {
printf("Element %d found at index %d (Non-recursive search).\n", target, index);
} else {
printf("Element %d not found in the array (Non-recursive search).\n", target);
}
} else if (choice == 2) {
// Perform recursive binary search
index = binarySearchRecursive(arr, 0, n - 1, target);
if (index != -1) {
printf("Element %d found at index %d (Recursive search).\n", target, index);
} else {
printf("Element %d not found in the array (Recursive search).\n", target);
}
} else {
printf("Invalid choice!\n");
}
return 0;
}
Insertion sort
#include <stdio.h>
// Function to perform Insertion Sort
void insertionSort(int arr[], int n) {
int i, key, j;
// Traverse the array from the second element
for (i = 1; i < n; i++) {
key = arr[i]; // Store the current element to be inserted
j = i - 1;
// Move elements of arr[0..i-1] that are greater than key, one position ahead
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j = j - 1;
}
// Place the key after the last moved element
arr[j + 1] = key;
}
}
// Function to print the array
void printArray(int arr[], int n) {
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int n;
// Input the size of the array
printf("Enter the number of elements: ");
scanf("%d", &n);
int arr[n];
// Input the elements of the array
printf("Enter the elements:\n");
for (int i = 0; i < n; i++) {
scanf("%d", &arr[i]);
}
// Perform insertion sort
insertionSort(arr, n);
// Print the sorted array
printf("Sorted array: ");
printArray(arr, n);
return 0;
}
//Selection sort
#include <stdio.h>
// Function to perform Selection Sort
void selectionSort(int arr[], int n) {
int i, j, minIdx, temp;
// Traverse through all array elements
for (i = 0; i < n - 1; i++) {
// Find the minimum element in unsorted array
minIdx = i;
for (j = i + 1; j < n; j++) {
if (arr[j] < arr[minIdx]) {
minIdx = j;
}
}
// Swap the found minimum element with the element at i
temp = arr[i];
arr[i] = arr[minIdx];
arr[minIdx] = temp;
}
}
int main() {
int n;
// Input the size of the array
printf("Enter the number of elements: ");
scanf("%d", &n);
int arr[n];
// Input the elements of the array
printf("Enter the elements:\n");
for (int i = 0; i < n; i++) {
scanf("%d", &arr[i]);
}
//Bubble sort
#include <stdio.h>
// Function to perform Bubble Sort
void bubbleSort(int arr[], int n) {
int i, j, temp;