Programming
Programming
1. Data Types:
Data types define the type of data a variable can store, and they determine the operations that can be
performed on those variables. There are two main categories of data types:
These are the most basic types that serve as the building blocks for more complex data types:
• Float (float): Represents decimal numbers or real numbers, e.g., 3.14, -7.5.
• String (string): Represents a sequence of characters (often used for text), e.g., "hello", "1234".
• Array: A collection of variables of the same type, e.g., int arr[5] to store 5 integers.
• Structure (struct): A user-defined data type that groups different data types together, e.g., to
represent a student with name, age, and marks.
• Union: Similar to structure, but it allows multiple data types to share the same memory
location.
These are higher-level data types defined by the programmer that are not built into the language but
are implemented via classes and operations:
• List
• Queue
• Stack
• Tree
• Graph
2. Operators:
Operators perform operations on variables and values. They can be categorized into several types:
Arithmetic Operators:
• + (addition)
• - (subtraction)
• * (multiplication)
• / (division)
• % (modulus, remainder)
Relational Operators:
• == (equal to)
Logical Operators:
• && (AND)
• || (OR)
• ! (NOT)
Assignment Operators:
• = (simple assignment)
Bitwise Operators:
• & (AND)
• | (OR)
• ^ (XOR)
• ~ (NOT)
Increment/Decrement Operators:
• ++ (increment)
• -- (decrement)
Comma Operator:
Used to separate expressions, often for executing multiple statements in a single line:
• ,
Sizeof Operator:
• sizeof(type)
Pointer Operators:
These are the core concepts related to data types and operators in programming. Let me know if you'd
like to dive deeper into any specific area!
1. Data Types
In programming, data types are important because they define the nature of the data that a variable can
hold. Data types allow you to understand how much memory the variable will occupy and the operations
that can be performed on it.
1. Integer (int):
o Size: Typically 4 bytes (32 bits), but can vary based on the system (16-bit or 64-bit).
CopyEdit
o Description: Used for representing real numbers (i.e., numbers with a fractional part).
CopyEdit
3. Character (char):
o Operations: You can perform arithmetic with characters (since they are often
represented by ASCII values).
CopyEdit
CopyEdit
5. String (string):
o Description: Represents a sequence of characters, often used for text data. In languages
like C, strings are arrays of char. In languages like Python or Java, strings are objects.
CopyEdit
Derived types are built using primitive data types. They allow for more complex data structures.
1. Array:
o Description: A collection of elements, all of the same data type. The elements are stored
in contiguous memory locations.
CopyEdit
2. Pointer:
o Description: A variable that stores the memory address of another variable. Pointers are
used for dynamic memory allocation, arrays, and function arguments.
CopyEdit
int x = 10;
3. Structure (struct):
o Description: A user-defined data type that groups different types of data (variables)
together.
o Examples: A struct could represent a student with name, age, and marks.
CopyEdit
struct Student {
char name[50];
int age;
float marks;
};
4. Union:
o Description: A data structure similar to struct, but all members share the same memory
location. This means only one member can hold a value at any given time, saving
memory.
CopyEdit
union Data {
int i;
float f;
char str[20];
};
ADTs are higher-level types that are typically implemented using structures, classes, or functions. They
describe operations on the data, and users don’t need to worry about the underlying implementation.
1. List: A collection of items where each item is ordered. You can access elements by index.
2. Stack: A collection where the last element added is the first one removed (LIFO: Last In First
Out).
3. Queue: A collection where the first element added is the first one removed (FIFO: First In First
Out).
4. Tree: A hierarchical structure consisting of nodes. Common in representing data with hierarchical
relationships.
2. Operators
Operators are symbols that perform operations on variables and values. They help manipulate data and
variables.
A. Arithmetic Operators:
• + : Addition (e.g., a + b)
• - : Subtraction (e.g., a - b)
• * : Multiplication (e.g., a * b)
• / : Division (e.g., a / b)
CopyEdit
int a = 10, b = 3;
B. Relational Operators:
• == : Equal to
• != : Not equal to
CopyEdit
// do something
C. Logical Operators:
CopyEdit
// do something
D. Assignment Operators:
• = : Simple assignment
CopyEdit
a += 5; // a = a + 5
b *= 2; // b = b * 2
E. Increment and Decrement Operators:
CopyEdit
a++; // increment a by 1
b--; // decrement b by 1
F. Bitwise Operators:
• & : AND
• | : OR
CopyEdit
CopyEdit
H. Sizeof Operator:
c
CopyEdit
I. Pointer Operators:
CopyEdit
int a = 5;
These detailed explanations cover all essential data types and operators in programming. Let me know if
you need more information or examples!
Control Flow Statements are constructs that dictate the order in which instructions in a program are
executed. They enable decision-making, looping, and branching, allowing programs to handle different
situations dynamically.
1. Decision-Making Statements
3. Jump Statements
1. Decision-Making Statements
These statements allow the program to execute certain parts of the code based on conditions.
A. if Statement
Syntax:
c
CopyEdit
if (condition) {
Example:
CopyEdit
int x = 10;
if (x > 5) {
B. if-else Statement
• Executes one block of code if the condition is true and another block if the condition is false.
Syntax:
CopyEdit
if (condition) {
} else {
Example:
CopyEdit
int x = 10;
if (x % 2 == 0) {
printf("x is even\n");
} else {
printf("x is odd\n");
C. if-else-if Ladder
• Tests multiple conditions sequentially. The first true condition's block is executed.
Syntax:
CopyEdit
if (condition1) {
} else if (condition2) {
} else {
Example:
CopyEdit
printf("Grade: A\n");
printf("Grade: B\n");
} else {
printf("Grade: C\n");
D. Nested if
Example:
c
CopyEdit
if (a > 5) {
if (b > 15) {
E. switch Statement
• Allows multiple conditional branches based on the value of a variable. Ideal for checking discrete
values.
Syntax:
CopyEdit
switch (expression) {
case value1:
break;
case value2:
break;
default:
Example:
CopyEdit
int day = 3;
switch (day) {
case 1:
printf("Monday\n");
break;
case 2:
printf("Tuesday\n");
break;
case 3:
printf("Wednesday\n");
break;
default:
printf("Invalid day\n");
A. for Loop
Syntax:
CopyEdit
// code to execute
Example:
CopyEdit
B. while Loop
• Repeats a block of code while a condition is true. The condition is checked before the loop
executes.
Syntax:
CopyEdit
while (condition) {
// code to execute
Example:
CopyEdit
int i = 0;
while (i < 5) {
i++;
C. do-while Loop
• Similar to while, but the loop executes at least once because the condition is checked after
execution.
Syntax:
CopyEdit
do {
// code to execute
} while (condition);
Example:
CopyEdit
int i = 0;
do {
i++;
D. Nested Loops
Example:
CopyEdit
3. Jump Statements
These statements alter the normal flow of execution by transferring control to another part of the code.
A. break Statement
Example:
CopyEdit
if (i == 3) {
break;
B. continue Statement
• Skips the current iteration of the loop and proceeds to the next iteration.
Example:
CopyEdit
if (i == 3) {
continue;
C. return Statement
Example:
CopyEdit
return a + b;
D. goto Statement
• Transfers control to a labeled statement. Use is generally discouraged due to readability and
maintainability concerns.
Syntax:
CopyEdit
goto label;
label:
// code to execute
Example:
c
CopyEdit
int i = 0;
loop:
if (i < 5) {
i++;
goto loop;
Comparison of Loops
Condition Check Before loop execution Before loop execution After loop execution
Example Iterating over arrays Waiting for user input Menu-driven programs
Control flow statements are essential for designing dynamic and interactive programs. Let me know if
you'd like deeper examples or specific applications of any of these!
Here is a more detailed explanation of functions and storage classes, tailored for GATE-level
preparation. This version expands on concepts, examples, and GATE-related insights.
Functions
1. Components of a Function
1. Return Type
o Specifies the type of value the function will return (e.g., int, float, void for no return
value).
Example:
CopyEdit
2. Function Name
3. Parameters (Arguments)
4. Function Body
5. Return Statement
o If the return type is void, the return statement can be omitted or written as return;.
2. Types of Functions
2. User-Defined Functions
Example:
CopyEdit
return x * y;
}
• Ensures the compiler knows about the function's name, return type, and parameters.
• Helps in cases where the function definition appears after the main() function.
Syntax:
CopyEdit
return_type function_name(parameters);
Example:
CopyEdit
4. Function Definition
Syntax:
CopyEdit
return_type function_name(parameters) {
// Function body
Example:
CopyEdit
return a + b;
}
5. Function Call
Syntax:
CopyEdit
function_name(arguments);
Example:
CopyEdit
1. Call by Value
o Changes made inside the function do not affect the original variable.
Example:
CopyEdit
void change(int x) {
x = 10;
int main() {
int a = 5;
change(a);
2. Call by Reference
Example:
CopyEdit
*x = 10;
int main() {
int a = 5;
change(&a);
7. Recursion
• Recursive Case: The condition under which the function calls itself.
Advantages:
Disadvantages:
Example:
CopyEdit
int factorial(int n) {
if (n == 0)
}
GATE Focus:
8. Inline Functions
• Inline functions suggest to the compiler to replace the function call with the function body,
improving performance for small functions.
Storage Classes
Storage classes define scope, visibility, lifetime, and default initialization of variables.
A. Automatic (auto)
Example:
CopyEdit
void test() {
printf("%d\n", x);
B. External (extern)
• Scope: Global.
• Initialization: Default to 0.
Example:
CopyEdit
C. Static
• Initialization: Default to 0.
Example:
CopyEdit
void counter() {
count++;
printf("%d\n", count);
D. Register
• Hints the compiler to store the variable in a CPU register for faster access.
Example:
CopyEdit
void test() {
register int i = 5; // Stored in register if possible
printf("%d\n", i);
GATE-Specific Focus
1. Function-Related Topics:
2. Storage Classes:
c
CopyEdit
#include <stdio.h>
if (y == 0)
return 0;
else
int main() {
printf("%d\n", result);
return 0;
Solution:
• Calculation:
o compute(5, 3) → 5 + compute(5, 2)
o compute(5, 2) → 5 + compute(5, 1)
o compute(5, 1) → 5 + compute(5, 0)
o compute(5, 0) → 0
Output: 15
CopyEdit
#include <stdio.h>
void recursiveFunction(int n) {
if (n > 0) {
recursiveFunction(n - 1);
int main() {
recursiveFunction(3);
return 0;
Solution:
• Execution:
Output: 3 2 1 1 2 3
CopyEdit
#include <stdio.h>
int add(int a, int b) {
return a + b;
void main() {
printf("%d\n", result);
Solution:
Correct Code:
CopyEdit
int main() {
printf("%d\n", result);
return 0;
CopyEdit
#include <stdio.h>
int fun(int n) {
if (n == 0)
return 1;
int main() {
printf("%d\n", fun(10));
return 0;
Solution:
• The recursion calculates the product of n and the result of fun(n/2) until n becomes 0.
o fun(10) → 10 * fun(5)
o fun(5) → 5 * fun(2)
o fun(2) → 2 * fun(1)
o fun(1) → 1 * fun(0)
Output: 10 * 5 * 2 * 1 = 100
CopyEdit
#include <stdio.h>
void counter() {
count++;
}
int main() {
counter();
counter();
counter();
return 0;
Solution:
• The static variable count retains its value across function calls.
• Execution:
Output: 1 2 3
CopyEdit
#include <stdio.h>
void display() {
int main() {
display();
printf("%d\n", x);
return 0;
Solution:
• Execution:
Output: 20 10
CopyEdit
#include <stdio.h>
void modify() {
extern int x;
int main() {
modify();
printf("%d\n", x);
return 0;
Solution:
• The extern keyword in modify() refers to the global variable x.
• Execution:
o Before modify(): x = 5
o After modify(): x = 10
Output: 5 10
CopyEdit
#include <stdio.h>
void test() {
printf("%d\n", i);
int main() {
test();
return 0;
Solution:
• The variable i is declared as register. While the compiler attempts to store it in a register, this
doesn’t affect program behavior.
Output: 10
Pointers
1. Pointer Basics
A pointer is a variable that stores the memory address of another variable. It enables efficient
manipulation of arrays, dynamic memory, and function arguments.
Declaration Syntax:
CopyEdit
data_type *pointer_name;
Example:
CopyEdit
int x = 10;
2. Pointer Operators
CopyEdit
int x = 10;
• Dereference operator (*): Accesses the value stored at the memory address held by the pointer.
CopyEdit
3. Types of Pointers
CopyEdit
int *p = NULL;
2. Dangling Pointer: A pointer that references memory which has been deallocated.
3. Void Pointer: A generic pointer that can store the address of any type.
c
CopyEdit
void *ptr;
int x = 10;
ptr = &x;
CopyEdit
4. Pointer Arithmetic
Pointer arithmetic involves operations on pointers, which are relative to the size of the data type.
Example:
CopyEdit
Operations:
Pointers are closely related to arrays; the array name itself acts as a pointer to its first element.
Example:
CopyEdit
Memory can be allocated and managed at runtime using malloc, calloc, realloc, and free:
CopyEdit
CopyEdit
CopyEdit
free(p);
CopyEdit
7. Pointers to Pointers
Example:
CopyEdit
int x = 10;
int *p = &x;
int **q = &p; // 'q' points to the address of 'p'
Example:
CopyEdit
*p = 20;
int main() {
int x = 10;
modify(&x);
return 0;
Strings
1. String Declaration
1. Character Array:
CopyEdit
2. Pointer to String:
CopyEdit
char *str = "Hello"; // Pointer to string literal
CopyEdit
char str[50];
printf("%s\n", str);
CopyEdit
char str[50];
strcpy(dest,
Copies src into dest. strcpy(dest, "World")
src)
strcat(dest,
Appends src to dest. strcat(dest, "World")
src)
strchr(str, ch) Finds the first occurrence of ch in str. strchr("Hello", 'e') → Pointer to 'e'
Example:
CopyEdit
char *p = str;
printf("%c", *p);
p++;
5. Common Operations
1. Reversing a String:
CopyEdit
str[len - i - 1] = temp;
2. Checking Palindrome:
CopyEdit
return 0;
return 1;
3. Counting Vowels:
CopyEdit
int count = 0;
if (*str == 'a' || *str == 'e' || *str == 'i' || *str == 'o' || *str == 'u')
count++;
str++;
return count;
1. Pointer-to-Pointer Operations
Pointers to pointers are commonly used when dealing with multi-dimensional arrays, linked lists, or
dynamic memory allocation.
Example:
CopyEdit
#include <stdio.h>
int main() {
int x = 100;
return 0;
A 2D array is treated as an array of arrays, and pointers can access its elements.
Example:
CopyEdit
#include <stdio.h>
int main() {
return 0;
3. Function Pointers
Function pointers store the address of a function and allow dynamic invocation.
Example:
c
CopyEdit
#include <stdio.h>
void greet() {
int main() {
return 0;
Using pointers with dynamically allocated strings allows efficient memory use.
Example:
CopyEdit
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
CopyEdit
int length = 0;
length++;
str++;
return length;
CopyEdit
CopyEdit
s1++;
s2++;
}
return *(unsigned char *)s1 - *(unsigned char *)s2;
CopyEdit
#include <stdio.h>
int main() {
int *p = arr;
return 0;
Solution:
Output: 10 40
CopyEdit
#include <stdio.h>
int *getAddress() {
int x = 10;
return &x;
}
int main() {
int *p = getAddress();
return 0;
Solution:
The function returns the address of a local variable (x), which gets destroyed when the function exits.
This results in a dangling pointer.
CopyEdit
#include <stdio.h>
#include <string.h>
int main() {
strcat(str1, str2);
printf("%s\n", str1);
return 0;
Solution:
Output: GATE2025
CopyEdit
#include <stdio.h>
#include <string.h>
char temp;
temp = *start;
*start = *end;
*end = temp;
start++;
end--;
int main() {
reverseString(str);
return 0;
CopyEdit
#include <stdio.h>
#include <string.h>
if (*start != *end) {
start++;
end--;
return 1; // Palindrome
int main() {
if (isPalindrome(str)) {
printf("Palindrome\n");
} else {
printf("Not a Palindrome\n");
return 0;
}
Problem 6: Function Pointers
Solution:
CopyEdit
#include <stdio.h>
int main() {
operation = add;
operation = multiply;
return 0;
Solution:
CopyEdit
#include <stdio.h>
#include <stdlib.h>
int main() {
int rows = 3, cols = 3;
arr[i][j] = i + j;
printf("\n");
// Free memory
free(arr[i]);
free(arr);
return 0;
Dynamic memory allocation allows for the creation of memory blocks at runtime. Here are some
common examples and problems using malloc, calloc, realloc, and free.
Solution:
CopyEdit
#include <stdio.h>
#include <stdlib.h>
int main() {
int n;
scanf("%d", &n);
if (arr == NULL) {
return 1;
// Input elements
scanf("%d", &arr[i]);
// Print elements
free(arr);
return 0;
Write a program that dynamically allocates memory for a 2D array (matrix) and fills it with user input.
Then, print the matrix.
Solution:
CopyEdit
#include <stdio.h>
#include <stdlib.h>
int main() {
}
// Input elements
scanf("%d", &matrix[i][j]);
printf("Matrix is:\n");
printf("\n");
free(matrix[i]);
free(matrix);
return 0;
Write a program that resizes a dynamically allocated array using realloc and then prints the elements.
Solution:
CopyEdit
#include <stdio.h>
#include <stdlib.h>
int main() {
int n;
scanf("%d", &n);
if (arr == NULL) {
return 1;
// Input elements
scanf("%d", &arr[i]);
scanf("%d", &n);
return 1;
scanf("%d", &arr[i]);
free(arr);
return 0;
Solution:
CopyEdit
#include <stdio.h>
int main() {
return 0;
Solution:
CopyEdit
#include <stdio.h>
int main() {
printf("\n");
}
return 0;
Write a function my_strcat that concatenates two strings and appends the result to the first string.
Solution:
CopyEdit
#include <stdio.h>
dest++;
src++;
int main() {
my_strcat(str1, str2);
printf("Concatenated String: %s\n", str1); // Outputs: Hello, World!
return 0;
Write a function my_strchr that finds the first occurrence of a character in a string.
Solution:
CopyEdit
#include <stdio.h>
if (*str == ch) {
str++;
int main() {
if (result != NULL) {
} else {
}
return 0;
Write a program that checks if two strings are anagrams of each other.
Solution:
CopyEdit
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
if (strlen(str1) != strlen(str2)) {
return 0;
count[str1[i]]++;
count[str2[i]]--;
if (count[i] != 0) {
return 0;
return 1;
int main() {
if (areAnagrams(str1, str2)) {
} else {
return 0;
These problems and examples should help you master dynamic memory, multi-dimensional arrays, and
custom string operations in C, all of which are crucial for the GATE exam.
1. Arrays
An array is a collection of elements stored in contiguous memory locations. Arrays allow random access
using indices.
1. Traversal
CopyEdit
#include <stdio.h>
int main() {
return 0;
2. Insertion
CopyEdit
#include <stdio.h>
int main() {
n++;
return 0;
3. Deletion
CopyEdit
#include <stdio.h>
int main() {
int n = 5, pos = 3;
n--;
return 0;
4. Searching
CopyEdit
#include <stdio.h>
int main() {
int n = 5, x = 30;
if (arr[i] == x) {
printf("Element %d found at index %d\n", x, i);
return 0;
return 0;
2. Linked Lists
A linked list is a dynamic data structure where elements (nodes) are linked using pointers.
1. Singly Linked List: Each node has data and a pointer to the next node.
2. Doubly Linked List: Each node has data, a pointer to the next node, and a pointer to the previous
node.
3. Circular Linked List: The last node points back to the head.
2. Insertion:
o At the beginning.
o At the end.
3. Deletion:
o From the beginning.
o A specific node.
CopyEdit
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
};
newNode->data = newData;
newNode->next = *head;
*head = newNode;
head = head->next;
printf("NULL\n");
int main() {
insertAtBeginning(&head, 10);
insertAtBeginning(&head, 20);
insertAtBeginning(&head, 30);
printList(head);
return 0;
CopyEdit
if (*head == NULL) {
printf("List is empty\n");
return;
*head = (*head)->next;
free(temp);
CopyEdit
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
};
newNode->data = newData;
newNode->next = NULL;
if (*head == NULL) {
newNode->prev = NULL;
*head = newNode;
return;
temp = temp->next;
temp->next = newNode;
newNode->prev = temp;
last = head;
head = head->next;
printf("NULL\n");
int main() {
insertAtEnd(&head, 10);
insertAtEnd(&head, 20);
insertAtEnd(&head, 30);
printList(head);
return 0;
}
3. Circular Linked List
Traversal
CopyEdit
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
};
do {
temp = temp->next;
printf("HEAD\n");
newNode->data = data;
if (*head == NULL) {
*head = newNode;
newNode->next = *head;
} else {
temp = temp->next;
temp->next = newNode;
newNode->next = *head;
int main() {
createCircularList(&head, 10);
createCircularList(&head, 20);
createCircularList(&head, 30);
printList(head);
return 0;
Arrays
1. Key Characteristics
2. Operations on Arrays
Access
Access element at an index. O(1)O(1)O(1)
(Read)
O(n)O(n)O(n) or O(logn)O(\log
Search Find an element's index (Linear or Binary Search).
n)O(logn)
Sorting Arrange elements (e.g., Quick Sort, Merge Sort). O(nlogn)O(n \log n)O(nlogn)
3. Types of Arrays
o Access: arr[i]
2. Multi-Dimensional Arrays:
▪ Access: arr[i][j]
4. Examples
a. Binary Search
CopyEdit
#include <stdio.h>
return -1;
int main() {
return 0;
CopyEdit
#include <stdio.h>
#define ROW 2
#define COL 2
C[i][j] = 0;
int main() {
int C[ROW][COL];
multiplyMatrices(A, B, C);
printf("Resultant Matrix:\n");
printf("\n");
}
return 0;
Linked Lists
1. Key Characteristics
• Nodes: Composed of data and a pointer to the next node (or previous node in doubly linked
lists).
• In a singly or doubly circular linked list, the last node points back to the head.
4. Examples
CopyEdit
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
};
newNode->data = newData;
newNode->next = NULL;
if (*head == NULL) {
*head = newNode;
return;
}
struct Node* temp = *head;
temp = temp->next;
temp->next = newNode;
head = head->next;
printf("NULL\n");
int main() {
insertAtEnd(&head, 10);
insertAtEnd(&head, 20);
insertAtEnd(&head, 30);
printList(head);
return 0;
CopyEdit
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
};
if (*head == NULL) {
printf("List is empty\n");
return;
temp = temp->next;
free(temp);
}
void printList(struct Node* head) {
head = head->next;
printf("NULL\n");
Advanced Topics
a. Traversal
Traversal in a circular linked list requires stopping when we return to the head node.
CopyEdit
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
};
do {
temp = temp->next;
printf("HEAD\n");
newNode->data = newData;
if (*head == NULL) {
*head = newNode;
return;
temp = temp->next;
temp->next = newNode;
newNode->next = *head;
}
int main() {
insertAtEnd(&head, 10);
insertAtEnd(&head, 20);
insertAtEnd(&head, 30);
printList(head);
return 0;
b. Deletion
Deleting a node requires updating the last node’s pointer if deleting the head.
CopyEdit
if ((*head)->next == *head) {
*head = NULL;
free(temp);
return;
}
struct Node* last = *head;
last = last->next;
last->next = (*head)->next;
*head = (*head)->next;
free(temp);
This is a commonly asked GATE question where two sorted linked lists are merged into a single sorted
list.
Approach
Implementation
CopyEdit
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
};
result = l1;
} else {
result = l2;
return result;
newNode->data = newData;
newNode->next = NULL;
if (*head == NULL) {
*head = newNode;
return;
temp = temp->next;
}
temp->next = newNode;
head = head->next;
printf("NULL\n");
int main() {
insertAtEnd(&list1, 10);
insertAtEnd(&list1, 20);
insertAtEnd(&list1, 30);
insertAtEnd(&list2, 15);
insertAtEnd(&list2, 25);
insertAtEnd(&list2, 35);
printf("List 1: ");
printList(list1);
printf("List 2: ");
printList(list2);
struct Node* mergedList = mergeSortedLists(list1, list2);
printList(mergedList);
return 0;
A loop in a linked list occurs when a node points back to a previous node, forming a cycle.
Approach
• To remove the loop, fix the next pointer of the node causing the loop.
Implementation
CopyEdit
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
};
slow = slow->next;
fast = fast->next->next;
if (slow == fast) {
// If a loop is found
if (slow == fast) {
slow = head;
slow = slow->next;
fast = fast->next;
int count = 1;
while (temp->next != NULL) {
temp = temp->next;
count++;
temp = temp->next;
printf("NULL\n");
// Main Function
int main() {
insertAtEnd(&head, 1);
insertAtEnd(&head, 2);
insertAtEnd(&head, 3);
insertAtEnd(&head, 4);
insertAtEnd(&head, 5);
printList(head);
return 0;
Implementation
CopyEdit
next = curr->next;
curr->next = prev;
prev = curr;
curr = next;
}
1. Stacks
How It Works:
A stack is a linear data structure that allows operations only at one end called the top. This makes it
simple but restricted in how elements can be accessed.
Implementation Approaches:
1. Using Arrays/Lists:
o push and pop can be implemented using .append() and .pop() respectively.
python
CopyEdit
stack = []
stack.append(10) # Push 10
stack.append(20) # Push 20
python
CopyEdit
class Node:
self.data = data
self.next = None
class Stack:
def __init__(self):
self.top = None
def push(self, data):
new_node = Node(data)
new_node.next = self.top
self.top = new_node
def pop(self):
if not self.top:
data = self.top.data
self.top = self.top.next
return data
stack = Stack()
stack.push(10)
stack.push(20)
Applications in Detail:
o Example: When a recursive function is called, its state is saved in the call stack.
• Backtracking:
o Problems like maze solving or navigating a file directory rely on stack-based backtracking.
2. Queues
How It Works:
A queue uses FIFO logic, where elements are added to the rear and removed from the front. This
ensures that the order of arrival is maintained.
Implementation Approaches:
1. Using Arrays/Lists:
o Inefficient for large-scale use because removing from the front involves shifting
elements.
python
CopyEdit
queue = []
queue.append(10) # Enqueue 10
queue.append(20) # Enqueue 20
python
CopyEdit
queue = deque()
queue.append(10) # Enqueue 10
queue.append(20) # Enqueue 20
python
CopyEdit
class Node:
self.data = data
self.next = None
class Queue:
def __init__(self):
new_node = Node(data)
if not self.rear:
return
self.rear.next = new_node
self.rear = new_node
def dequeue(self):
if not self.front:
data = self.front.data
self.front = self.front.next
if not self.front:
self.rear = None
return data
queue = Queue()
queue.enqueue(10)
queue.enqueue(20)
print(queue.dequeue()) # Dequeue -> 10
Applications in Detail:
• Task Scheduling:
• Producer-Consumer Model:
o Used in real-time systems where producers add tasks to a queue and consumers process
them.
Advanced Variants
1. Circular Queue:
• Enqueue and dequeue operations are handled modulo the size of the queue.
3. Priority Queue:
Comparisons
Infix Expression: (A + B) * (C - D)
Solution:
Steps:
Problem: Implement a stack data structure using two queues, with operations push(x) and pop().
Solution:
Use two queues, queue1 and queue2, to simulate the stack's LIFO behavior.
• Push Operation:
• Pop Operation:
python
CopyEdit
class StackUsingTwoQueues:
def __init__(self):
self.queue1 = deque()
self.queue2 = deque()
self.queue2.append(x)
while self.queue1:
self.queue2.append(self.queue1.popleft())
def pop(self):
if not self.queue1:
return self.queue1.popleft()
stack = StackUsingTwoQueues()
stack.push(10)
stack.push(20)
stack.push(30)
print(stack.pop()) # 30
Problem: Given an array of integers, find the next greater element for every element in the array. The
next greater element for an element is the first element on the right side that is greater than it.
Example:
Solution:
Use a stack to keep track of elements and find the next greater element.
Steps:
2. For each element, pop elements from the stack that are smaller.
3. If the stack is not empty, the element at the top of the stack is the next greater element.
python
CopyEdit
def nextGreaterElement(nums):
stack = []
stack.pop()
if stack:
result[i] = stack[-1]
stack.append(nums[i])
return result
Problem: Implement a queue using two stacks, with operations enqueue(x) and dequeue().
Solution:
Use two stacks, stack1 and stack2, to simulate the FIFO behavior of a queue.
• Enqueue Operation:
• Dequeue Operation:
o If stack2 is empty, move all elements from stack1 to stack2 (by popping from stack1 and
pushing to stack2).
python
CopyEdit
class QueueUsingTwoStacks:
def __init__(self):
self.stack1 = []
self.stack2 = []
self.stack1.append(x)
def dequeue(self):
if not self.stack2:
if not self.stack1:
self.stack2.append(self.stack1.pop())
return self.stack2.pop()
queue = QueueUsingTwoStacks()
queue.enqueue(10)
queue.enqueue(20)
queue.enqueue(30)
print(queue.dequeue()) # 10
print(queue.dequeue()) # 20
Problem: Given a graph, perform Breadth-First Search (BFS) and Depth-First Search (DFS) starting from
a given node.
Solution:
• DFS: Uses a stack (or recursion, which internally uses the call stack) to explore the graph as
deeply as possible before backtracking.
python
CopyEdit
graph = {
0: [1, 2],
1: [0, 3, 4],
2: [0, 5],
3: [1],
4: [1],
5: [2]
# BFS
visited = set()
queue = deque([start])
visited.add(start)
while queue:
node = queue.popleft()
visited.add(neighbor)
queue.append(neighbor)
# DFS
visited = set()
stack = [start]
while stack:
node = stack.pop()
visited.add(node)
stack.append(neighbor)
bfs(graph, 0)
dfs(graph, 0)
Output:
makefile
CopyEdit
BFS: 0 1 2 3 4 5
DFS: 0 2 5 1 4 3
These practice questions cover a wide range of stack and queue applications commonly tested in GATE.
Keep practicing problems like these to strengthen your understanding and improve your coding skills.
Problem:
Solution Approach:
1. Initialize an empty stack for operators and an empty list for the output.
o Right Parenthesis ) causes the stack to be popped to the output until a left parenthesis is
encountered.
o Operators are pushed onto the stack based on precedence. If the current operator has
lower or equal precedence than the operator on top of the stack, pop from the stack to
the output.
Steps:
11. Read ) → Pop - from the stack and add it to the output, then pop * from the stack and add it to
the output.
Postfix Expression:
AB+CD-*
This is a standard problem in GATE, testing both understanding and implementation skills.
Problem:
Simulate a queue using two stacks with operations enqueue(x) and dequeue().
Solution Approach:
You can use two stacks (stack1 and stack2) to simulate a queue. The idea is to reverse the order of
elements by using the stacks:
• Enqueue Operation:
o Simply push elements onto stack1.
• Dequeue Operation:
o If stack2 is empty, move all elements from stack1 to stack2 (this reverses the order).
1. If stack2 is empty:
o Pop all elements from stack1 and push them onto stack2.
Code Example:
python
CopyEdit
class QueueUsingTwoStacks:
def __init__(self):
self.stack1 = []
self.stack2 = []
self.stack1.append(x)
def dequeue(self):
if not self.stack2:
if not self.stack1:
while self.stack1:
self.stack2.append(self.stack1.pop())
return self.stack2.pop()
# Example usage
queue = QueueUsingTwoStacks()
queue.enqueue(10)
queue.enqueue(20)
queue.enqueue(30)
print(queue.dequeue()) # 10
print(queue.dequeue()) # 20
This problem tests your ability to manage two stacks and understand the reversal mechanism that makes
a queue possible.
Problem:
Given an array, find the next greater element for every element. If no greater element exists, return -1.
Approach:
• For each element, pop all elements from the stack that are less than or equal to the current
element (because they can’t be the next greater element).
• If the stack is not empty after this, the element on top of the stack is the next greater element.
Steps:
o While the stack is not empty and the top of the stack is less than or equal to the current
element, pop the stack.
Code Example:
python
CopyEdit
def nextGreaterElement(nums):
stack = []
# Pop elements from the stack while they are less than or equal to the current element
stack.pop()
if stack:
result[i] = stack[-1]
stack.append(nums[i])
return result
This approach has a time complexity of O(n) because each element is pushed and popped from the stack
at most once.
• Start from the source node, mark it as visited, and enqueue it.
• Dequeue nodes, explore their neighbors, and enqueue unvisited neighbors.
• Stack-based algorithm (or recursion) for exploring a graph by diving deep into each branch
before backtracking.
• Start from the source node, mark it as visited, and recursively explore its neighbors.
python
CopyEdit
graph = {
0: [1, 2],
1: [0, 3, 4],
2: [0, 5],
3: [1],
4: [1],
5: [2]
# BFS
visited = set()
queue = deque([start])
visited.add(start)
while queue:
node = queue.popleft()
visited.add(neighbor)
queue.append(neighbor)
# DFS
visited = set()
stack = [start]
while stack:
node = stack.pop()
visited.add(node)
stack.append(neighbor)
bfs(graph, 0)
dfs(graph, 0)
Output:
makefile
CopyEdit
BFS: 0 1 2 3 4 5
DFS: 0 2 5 1 4 3
BFS and DFS are both fundamental graph traversal techniques that often appear in GATE exam questions.
Conclusion:
By thoroughly understanding these problems and implementing them, you'll be well-prepared for the
GATE exam. Practice coding these problems and tweak your approach to handle various edge cases.
These topics are central to algorithms and data structures, and mastering them will boost your problem-
solving efficiency.
Problem:
Solution Approach:
1. Initialize an empty stack for operators and an empty list for the output.
o Right Parenthesis ) causes the stack to be popped to the output until a left parenthesis is
encountered.
o Operators are pushed onto the stack based on precedence. If the current operator has
lower or equal precedence than the operator on top of the stack, pop from the stack to
the output.
Steps:
11. Read ) → Pop - from the stack and add it to the output, then pop * from the stack and add it to
the output.
Postfix Expression:
AB+CD-*
This is a standard problem in GATE, testing both understanding and implementation skills.
Problem:
Simulate a queue using two stacks with operations enqueue(x) and dequeue().
Solution Approach:
You can use two stacks (stack1 and stack2) to simulate a queue. The idea is to reverse the order of
elements by using the stacks:
• Enqueue Operation:
• Dequeue Operation:
o If stack2 is empty, move all elements from stack1 to stack2 (this reverses the order).
1. If stack2 is empty:
o Pop all elements from stack1 and push them onto stack2.
Code Example:
python
CopyEdit
class QueueUsingTwoStacks:
def __init__(self):
self.stack1 = []
self.stack2 = []
self.stack1.append(x)
def dequeue(self):
if not self.stack2:
if not self.stack1:
while self.stack1:
self.stack2.append(self.stack1.pop())
return self.stack2.pop()
# Example usage
queue = QueueUsingTwoStacks()
queue.enqueue(10)
queue.enqueue(20)
queue.enqueue(30)
print(queue.dequeue()) # 10
print(queue.dequeue()) # 20
This problem tests your ability to manage two stacks and understand the reversal mechanism that makes
a queue possible.
3. Find Next Greater Element Using Stack (Detailed Explanation)
Problem:
Given an array, find the next greater element for every element. If no greater element exists, return -1.
Approach:
• For each element, pop all elements from the stack that are less than or equal to the current
element (because they can’t be the next greater element).
• If the stack is not empty after this, the element on top of the stack is the next greater element.
Steps:
o While the stack is not empty and the top of the stack is less than or equal to the current
element, pop the stack.
Code Example:
python
CopyEdit
def nextGreaterElement(nums):
stack = []
# Pop elements from the stack while they are less than or equal to the current element
stack.pop()
# If stack is not empty, top element is the next greater element
if stack:
result[i] = stack[-1]
stack.append(nums[i])
return result
This approach has a time complexity of O(n) because each element is pushed and popped from the stack
at most once.
• Start from the source node, mark it as visited, and enqueue it.
• Stack-based algorithm (or recursion) for exploring a graph by diving deep into each branch
before backtracking.
• Start from the source node, mark it as visited, and recursively explore its neighbors.
python
CopyEdit
0: [1, 2],
1: [0, 3, 4],
2: [0, 5],
3: [1],
4: [1],
5: [2]
# BFS
visited = set()
queue = deque([start])
visited.add(start)
while queue:
node = queue.popleft()
visited.add(neighbor)
queue.append(neighbor)
# DFS
visited = set()
stack = [start]
while stack:
node = stack.pop()
visited.add(node)
stack.append(neighbor)
bfs(graph, 0)
dfs(graph, 0)
Output:
makefile
CopyEdit
BFS: 0 1 2 3 4 5
DFS: 0 2 5 1 4 3
BFS and DFS are both fundamental graph traversal techniques that often appear in GATE exam questions.
Conclusion:
By thoroughly understanding these problems and implementing them, you'll be well-prepared for the
GATE exam. Practice coding these problems and tweak your approach to handle various edge cases.
These topics are central to algorithms and data structures, and mastering them will boost your problem-
solving efficiency.
Problem:
1. Traverse the string and push each character onto the stack.
2. Pop characters from the stack and add them to the result string.
Code Example:
python
CopyEdit
def reverseString(s):
stack = []
for char in s:
stack.append(char)
reversed_str = ''
while stack:
reversed_str += stack.pop()
return reversed_str
This problem helps you understand stack operations like push and pop.
Problem:
Design a circular queue of fixed size using an array. Implement the following operations:
Solution Approach:
• Use two pointers (front and rear) to represent the front and rear of the queue.
• In a circular queue, when the rear pointer reaches the end of the array, it should wrap around to
the beginning.
Code Example:
python
CopyEdit
class CircularQueue:
self.size = size
self.front = self.rear = -1
print("Queue is full")
return
self.front = self.rear = 0
else:
self.queue[self.rear] = x
def dequeue(self):
if self.front == -1:
print("Queue is empty")
return None
elif self.front == self.rear:
element = self.queue[self.front]
self.front = self.rear = -1
return element
else:
element = self.queue[self.front]
return element
def front_element(self):
if self.front == -1:
return self.queue[self.front]
def is_empty(self):
return self.front == -1
def is_full(self):
# Example usage
cq = CircularQueue(5)
cq.enqueue(10)
cq.enqueue(20)
print(cq.dequeue()) # Output: 10
print(cq.front_element()) # Output: 20
Problem:
Implement a stack using a linked list. The stack should support the following operations:
Solution Approach:
• Use a linked list where the top of the stack is the head node.
Code Example:
python
CopyEdit
class StackNode:
self.data = data
self.next = None
class Stack:
def __init__(self):
self.top = None
new_node = StackNode(x)
new_node.next = self.top
self.top = new_node
def pop(self):
if self.top is None:
print("Stack is empty")
return None
else:
popped_node = self.top
self.top = self.top.next
return popped_node.data
def peek(self):
if self.top is None:
print("Stack is empty")
return None
return self.top.data
def is_empty(self):
# Example usage
stack = Stack()
stack.push(10)
stack.push(20)
print(stack.pop()) # Output: 20
print(stack.peek()) # Output: 10
This problem will test your understanding of linked list and stack operations.
Problem:
• A max-heap is a complete binary tree where each parent node is greater than its children.
• Use a list to represent the heap, with the parent at index i and the children at indices 2i + 1 and
2i + 2.
Code Example:
python
CopyEdit
class MaxHeap:
def __init__(self):
self.heap = []
self.heap.append(x)
self._heapify_up(len(self.heap) - 1)
def delete_max(self):
if len(self.heap) == 0:
max_element = self.heap[0]
self.heap[0] = self.heap[-1]
self.heap.pop()
self._heapify_down(0)
return max_element
def peek_max(self):
if len(self.heap) == 0:
return self.heap[0]
def _heapify_up(self, index):
parent = (index - 1) // 2
self._heapify_up(parent)
left_child = 2 * index + 1
right_child = 2 * index + 2
largest = index
largest = left_child
largest = right_child
if largest != index:
self._heapify_down(largest)
# Example usage
max_heap = MaxHeap()
max_heap.insert(10)
max_heap.insert(20)
max_heap.insert(5)
print(max_heap.delete_max()) # Output: 20
print(max_heap.peek_max()) # Output: 10
Problem:
Sort a stack using only one extra stack (without using any sorting algorithms like quicksort or mergesort).
Solution Approach:
2. While the original stack is not empty, pop the top element.
o If the auxiliary stack is empty or the top of the auxiliary stack is greater than the popped
element, push the popped element onto the auxiliary stack.
o Otherwise, pop elements from the auxiliary stack and push them back to the original
stack until the condition is met.
4. Repeat until the original stack is empty, and the auxiliary stack will contain the sorted elements.
Code Example:
python
CopyEdit
def sort_stack(stack):
aux_stack = []
while stack:
temp = stack.pop()
stack.append(aux_stack.pop())
aux_stack.append(temp)
return aux_stack
# Example usage
sorted_stack = sort_stack(stack)
print(sorted_stack) # Output: [3, 23, 31, 34, 92, 98]
• Time Complexity Analysis: Be sure to analyze the time complexity of the operations for these
data structures, as this will be useful for answering related questions in GATE.
• Practice Edge Cases: For all problems, consider edge cases such as empty structures, single-
element structures, and full structures (like a full queue or a stack with one element).
• Mock Tests: Regularly attempt GATE mock tests and try solving these problems under time
constraints.
Trees are one of the most important topics in Data Structures and Algorithms for the GATE exam. Let’s
break down the topic and provide theory, examples, algorithms, and problems to help you master trees
for GATE.
1. Types of Trees
1. Binary Tree:
o Complete Binary Tree: All levels are completely filled except possibly the last, which is
filled from left to right.
o Perfect Binary Tree: All internal nodes have two children, and all leaves are at the same
level.
o A binary tree where the left child’s value is less than the parent, and the right child’s
value is greater than the parent.
3. AVL Tree:
o A self-balancing binary search tree where the height difference (balance factor)
between the left and right subtrees of any node is at most 1.
4. B-Tree:
o A self-balancing search tree that maintains sorted data and allows searches, sequential
access, insertions, and deletions in logarithmic time. Used in databases.
5. Heap:
2. Tree Traversals
Traversal refers to visiting each node of the tree in a systematic way. There are two types:
1. Inorder Traversal:
css
CopyEdit
/ \
B C
/\ /
2. D E F
3. CopyEdit
4. Preorder Traversal:
o Example: A B D E C F.
5. Postorder Traversal:
1. Level-Order Traversal:
o Example: A B C D E F.
Traversal Code:
python
CopyEdit
class Node:
self.left = None
self.right = None
self.val = key
# Inorder Traversal
def inorder(root):
if root:
inorder(root.left)
inorder(root.right)
# Preorder Traversal
def preorder(root):
if root:
preorder(root.left)
preorder(root.right)
# Postorder Traversal
def postorder(root):
if root:
postorder(root.left)
postorder(root.right)
# Level-Order Traversal
def level_order(root):
if not root:
return
queue = deque([root])
while queue:
node = queue.popleft()
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
# Example Usage
root = Node('A')
root.left = Node('B')
root.right = Node('C')
root.left.left = Node('D')
root.left.right = Node('E')
root.right.left = Node('F')
preorder(root) # Output: A B D E C F
postorder(root) # Output: D E B F C A
level_order(root) # Output: A B C D E F
1. Insertion in BST:
o Insert into the left subtree if smaller; otherwise, insert into the right subtree.
python
CopyEdit
class BSTNode:
self.left = None
self.right = None
self.val = key
if root is None:
return BSTNode(key)
return root
def inorder(root):
if root:
inorder(root.left)
inorder(root.right)
# Example Usage
root = None
inorder(root) # Output: 20 30 40 50 60 70 80
2. Deletion in BST:
o If the node has two children, replace it with the inorder successor (minimum value in
the right subtree).
1. Height of a Tree:
o The height of a tree is the number of edges on the longest path from the root to a leaf.
python
CopyEdit
def height(root):
if root is None:
return 0
left_height = height(root.left)
right_height = height(root.right)
2. Diameter of a Tree:
o The diameter is the longest path between two nodes in the tree (may or may not pass
through the root).
python
CopyEdit
def diameter(root):
if not root:
return 0
def helper(node):
if not node:
lh, ld = helper(node.left)
rh, rd = helper(node.right)
_, dia = helper(root)
return dia
1. Question: Construct a BST from the preorder sequence [10, 5, 1, 7, 40, 50].
Solution:
• Split the array into left subtree (values smaller than root) and right subtree (values larger than
root).
2. Question: Given a binary tree, print the left view of the tree.
Hint:
• Use level-order traversal and print the first node of each level.
Hint:
• A tree is balanced if the height difference between left and right subtrees of any node is at most
1.
• Important Topics:
1. Theory Questions
Understanding the theoretical aspects of trees is crucial, as these are commonly tested in GATE. Focus
on:
• Properties of Trees:
o Height of a complete binary tree with nnn nodes: ⌊log2n⌋+1\lfloor \log_2 n \rfloor +
1⌊log2n⌋+1.
o Minimum number of nodes in a binary tree of height hhh: hhh (skewed tree).
o AVL Trees: Ensure O(logn)O(\log n)O(logn) complexity for search, insertion, and
deletion.
• Heap Properties:
2. Traversal-Based Questions
o Example: Construct a binary tree using given inorder and preorder sequences.
o Identify which traversal gives sorted output (Answer: Inorder for BST).
3. Complexity Analysis
Understand and remember the time and space complexity of operations on trees:
Binary Search
Operation Binary Tree AVL Tree Heap
Tree
• Note:
o hhh: Height of the tree (O(logn)O(\log n)O(logn) for balanced trees, O(n)O(n)O(n) for
skewed trees).
4. Heap Operations
Heap is a popular topic in GATE, especially in questions related to priority queues and heapify
operations.
• Heapify:
• Insert/Delete in Heap:
• Applications:
o Priority Queues: Implemented using heaps for efficient access to max/min element.
Example Problem:
Example:
Given Inorder = [D, B, E, A, F, C] and Preorder = [A, B, D, E, C, F], construct the binary tree.
• B-Trees:
o Questions often test splitting nodes and maintaining balance during insertions.
• Tries:
• Traversal questions: Find preorder from given inorder and postorder sequences.
• Topic-Wise Quizzes: Solve quizzes focused on AVL Trees, Heaps, and Traversals.
• Reference Books: Use "Data Structures and Algorithm Analysis in C" by Mark Allen Weiss and
"Introduction to Algorithms" by Cormen (CLRS) for in-depth study.
1. AVL Trees
An AVL Tree is a self-balancing Binary Search Tree (BST) where the height difference between the left and
right subtrees of any node (called the balance factor) is at most 1.
o Single Rotations:
o Double Rotations:
markdown
CopyEdit
/\
T1 y
/\
T2 x
markdown
CopyEdit
y
/\
z x
/\
T1 T2
markdown
CopyEdit
/\
y T3
/\
x T2
markdown
CopyEdit
/\
x z
/\
T2 T3
o Perform a left rotation on the left child, then a right rotation on the root.
o Perform a right rotation on the right child, then a left rotation on the root.
python
CopyEdit
class AVLNode:
self.key = key
self.left = None
self.right = None
self.height = 1
class AVLTree:
if not root:
return 0
return root.height
if not root:
return 0
y = z.left
T3 = y.right
# Perform rotation
y.right = z
z.left = T3
# Update heights
return y
y = z.right
T2 = y.left
# Perform rotation
y.left = z
z.right = T2
# Update heights
return y
if not root:
return AVLNode(key)
else:
return self.right_rotate(root)
return self.left_rotate(root)
root.left = self.left_rotate(root.left)
return self.right_rotate(root)
root.right = self.right_rotate(root.right)
return self.left_rotate(root)
return root
if root:
self.inorder(root.left)
self.inorder(root.right)
# Example Usage
avl = AVLTree()
root = None
avl.inorder(root)
# Output: 10 20 25 30 40 50
2. Heap Operations
A Heap is a complete binary tree used for efficient retrieval of the maximum (in a max-heap) or
minimum (in a min-heap).
1. Insert:
2. Delete Max:
python
CopyEdit
class MaxHeap:
def __init__(self):
self.heap = []
self.heap.append(key)
self._heapify_up(len(self.heap) - 1)
def delete_max(self):
if len(self.heap) == 0:
return "Heap is empty"
max_value = self.heap[0]
self.heap[0] = self.heap[-1]
self.heap.pop()
self._heapify_down(0)
return max_value
parent = (index - 1) // 2
self._heapify_up(parent)
largest = index
left_child = 2 * index + 1
right_child = 2 * index + 2
largest = left_child
largest = right_child
if largest != index:
self._heapify_down(largest)
def get_max(self):
if len(self.heap) == 0:
return "Heap is empty"
return self.heap[0]
# Example Usage
max_heap = MaxHeap()
max_heap.insert(key)
o Identify the type of imbalance and correct it using single or double rotations.
2. Heapify Questions:
3. Priority Queue:
Hashing is a technique used to map data to a fixed-size value (hash code) for efficient data retrieval. It
plays a key role in data structures like hash tables, cryptographic applications, and database indexing.
1. Hash Function
A hash function takes input data (key) and maps it to an integer value (hash code) within a fixed range.
Key Properties of a Good Hash Function:
1. Deterministic: Same key should always produce the same hash code.
3. Uniform Distribution: Should distribute keys uniformly across the hash table to avoid clustering.
4. Minimize Collisions: Should map different keys to distinct hash codes as much as possible.
2. Hash Table
A hash table is a data structure that stores key-value pairs using a hash function. It supports:
Collisions occur when two keys produce the same hash code. They are resolved using:
Techniques:
1. Linear Probing:
2. Quadratic Probing:
3. Double Hashing:
3.2 Chaining
• Use a linked list or other structures to store all keys with the same hash value.
4. Load Factor
The load factor (α\alphaα) of a hash table is the ratio of the number of elements to the table size.
• Rehashing is performed to resize the table when α\alphaα exceeds a threshold (e.g., 0.75).
5. Rehashing
Rehashing involves:
6. Hashing Applications
1. Hash Tables:
2. Cryptographic Hashing:
3. Bloom Filters:
4. Database Indexing:
8. Code Examples
python
CopyEdit
class HashTable:
self.size = size
index = self.hash_function(key)
if item[0] == key:
item[1] = value
return
self.table[index].append([key, value])
index = self.hash_function(key)
if item[0] == key:
return item[1]
return None
index = self.hash_function(key)
if item[0] == key:
self.table[index].remove(item)
return
# Example Usage
hash_table = HashTable(10)
hash_table.insert(1, "Value1")
hash_table.insert(11, "Value2")
hash_table.delete(1)
print(hash_table.search(1)) # Output: None
python
CopyEdit
class HashTableOpenAddressing:
self.size = size
index = self.hash_function(key)
for i in range(self.size):
return
index = self.hash_function(key)
for i in range(self.size):
if self.table[probe_index] is None:
return None
if self.table[probe_index][0] == key:
return self.table[probe_index][1]
return None
index = self.hash_function(key)
for i in range(self.size):
if self.table[probe_index] is None:
return
if self.table[probe_index][0] == key:
self.table[probe_index] = None
return
# Example Usage
hash_table = HashTableOpenAddressing(10)
hash_table.insert(1, "Value1")
hash_table.insert(11, "Value2")
hash_table.delete(1)
Let’s delve into cryptographic hashing and GATE-related hashing problems in detail.
1. Cryptographic Hashing
Cryptographic hashing ensures secure and unique hash codes for input data. It's widely used in
cryptography, data integrity checks, and digital signatures.
1. Deterministic:
o For the same input, the output should always be the same.
2. Pre-image Resistance:
3. Collision Resistance:
o Two different inputs should not produce the same hash value.
o Example: H(x1)≠H(x2)H(x_1) \neq H(x_2)H(x1) =H(x2) for x1≠x2x_1 \neq x_2x1 =x2.
4. Avalanche Effect:
5. Efficiency:
o Family includes SHA-1, SHA-2 (SHA-224, SHA-256, SHA-384, SHA-512), and SHA-3.
o Combines a cryptographic hash function with a secret key for message authentication.
1.3 Applications
• Data Integrity:
• Digital Signatures:
• Password Storage:
python
CopyEdit
import hashlib
# MD5 Hash
md5_hash = hashlib.md5(data).hexdigest()
# SHA-256 Hash
sha256_hash = hashlib.sha256(data).hexdigest()
# SHA-512 Hash
sha512_hash = hashlib.sha512(data).hexdigest()
Output:
mathematica
CopyEdit
1. Hash Functions:
o Division method.
o Multiplication method.
o Universal hashing.
2. Hash Collisions:
o Open addressing.
o Chaining.
4. Applications:
2. Load Factor:
3. Time Complexity:
o Worst Case: O(n)O(n)O(n) (All keys hash to the same index in chaining).
Solution:
3. GATE 2021 Question Q: A hash table of size 10 uses open addressing with linear probing. What is the
position of the key 434343 if the hash function is h(k)=kmod 10h(k) = k \mod 10h(k)=kmod10 and the
following keys are already present: [23, 43, 13, 33]?
Solution:
1. Perfect Hashing:
o Hashing with no collisions, achieved by creating an auxiliary hash function for colliding
keys.
2. Bloom Filters:
3. Consistent Hashing: