DSA Cheatsheet Capgemini
DSA Cheatsheet Capgemini
Linked List:
- Definition : A collection of nodes where each node contains data and a
reference to the next node.
Real-World Use Case : Browser history and undo/redo operations in text editors
work based on stacks.
Queue :
- Definition : A linear data structure that follows a First In, First Out (FIFO)
approach.
Real-World Use Case : Used in scenarios like ticket booking systems, customer
service systems, or task scheduling in operating systems.
Graph :
Definition : A collection of nodes (vertices) connected by edges, which can be
directed or undirected.
Real-World Use Case : Social networks where users (nodes) are connected by
friendships (edges) or route planning apps like Google Maps.
Hash Table :
Definition : A data structure that maps keys to values using a hash function to
compute an index into an array of buckets.
Real-World Use Case : Used in caching systems like a web browser’s cache or
database indexing.
### 1. Insertion
Definition : Adding an element to the data structure.
▪ Arrays : Adding an element to a specific index.
▪ Linked Lists : Inserting a node at the beginning, end, or between two nodes.
▪ Stacks : Push operation adds an element to the top of the stack.
▪ Queues : Enqueue operation adds an element to the rear of the queue.
▪ Trees : Adding a node at the appropriate location based on the tree's
properties (e.g., Binary Search Tree).
▪ Graphs : Adding an edge between vertices or adding new vertices.
Real-World Example :
➢ Inserting a new product into a catalog (Array).
➢ Adding a webpage to the browsing history (Stack).
➢ Adding a person to the customer support queue (Queue).
### 2. Deletion
Definition : Removing an element from the data structure.
▪ Arrays : Removing an element requires shifting all subsequent
elements.
▪ Linked Lists : Removing a node can be done from the beginning, end, or
in the middle by updating pointers.
▪ Stacks : Pop operation removes the top element.
▪ Queues : Dequeue operation removes the front element.
▪ Trees : Removing a node while maintaining tree properties.
▪ Graphs : Removing an edge between vertices or removing a vertex.
Real-World Example :
➢ Deleting a file from a folder (Tree).
➢ Closing the most recent application window (Stack).
➢ Removing a task from a to-do list (Linked List).
### 3. Traversal
Definition : Visiting each element of the data structure in a systematic way.
▪ Arrays : Iterate through all elements.
▪ Linked Lists : Traverse through nodes from head to tail.
▪ Stacks/Queues : Traverse elements without modifying their order.
▪ Trees : Preorder, inorder, or postorder traversal.
▪ Graphs : Depth-First Search (DFS) or Breadth-First Search (BFS)
algorithms.
Real-World Example :
➢ Going through your photo album (Array).
➢ Checking customer queue to see who is next (Queue).
➢ Navigating through folders and subfolders on your computer (Tree).
### 4. Searching
Definition : Finding the location of a particular element in the data structure.
▪ Arrays : Linear search or Binary search.
▪ Linked Lists : Traverse nodes to find the required data.
▪ Trees : Binary search is often used in Binary Search Trees (BST).
▪ Graphs : Searching for a node or path using BFS or DFS.
Real-World Example :
➢ Searching for a contact in your phone's address book (Array).
➢ Finding a file in a directory (Tree).
➢ Searching for a friend in a social network (Graph).
### 5. Sorting
Definition : Arranging the elements of the data structure in a specific order
(ascending or descending).
▪ Arrays/Lists : Bubble sort, Quick sort, Merge sort, etc.
▪ Linked Lists : Similar sorting algorithms, but modifications are needed
due to pointers.
▪ Trees : In Binary Search Trees, inorder traversal gives sorted elements.
▪ Graphs : Topological sorting for Directed Acyclic Graphs (DAG).
Real-World Example :
➢ Sorting customer data by their last names (Array).
➢ Ranking students based on their exam scores (Array/Linked List).
---
### 6. Merging
Example (C++) :
```cpp
int arr1[] = {1, 3, 5};
int arr2[] = {2, 4, 6};
int arr3[6];
merge(arr1, arr1+3, arr2, arr2+3, arr3); // Merging two arrays
```
Real-World Example :
- Merging two sorted lists of employees from two departments.
- Combining data from two databases.
---
### 7. Updation
Definition : Modifying the value of an element at a specific location.
Example (Java) :
```java
arr[2] = 50; // Updating value at index 2
```
Real-World Example :
- Updating the price of a product in a shopping cart.
- Changing the priority of a task in a to-do list.
---
### 8. Accessing
Example (C++) :
```cpp
int value = arr[2]; // Accessing value at index 2
```
Real-World Example :
- Checking the status of an order in an e-commerce app (Access data from Array).
- Accessing a specific message in a conversation history.
---
### Conclusion:
Understanding these operations helps in choosing the right data structure for
specific tasks. Efficient handling of these operations can significantly enhance
the performance of applications, especially when working with large datasets or
real-time systems.
Each data structure offers unique advantages that make them suitable for
specific use cases and scenarios. Understanding these advantages can help in
choosing the most efficient data structure for a given problem. Below are the
advantages of various data structures:
---
### 1. Array
Advantages :
- Random Access : Elements can be accessed directly using the index, allowing
constant time complexity \(O(1)\) for accessing elements.
- Memory Efficiency : If the size of the array is known, it uses memory efficiently.
- Cache-Friendly : Elements are stored in contiguous memory locations, making
array traversal faster due to better cache performance.
Use Case : Storing small, fixed-size datasets like a list of months or temperature
readings for the week.
---
Advantages :
- Dynamic Size : Linked lists grow and shrink dynamically as needed without
wasting memory.
- Efficient Insertions/Deletions : Inserting or deleting elements at any position
(especially at the beginning or middle) is more efficient compared to arrays (no
need for shifting elements).
- No Memory Wastage : Memory is allocated only when needed, unlike arrays
where you need to allocate memory upfront.
### 3. Stack
Advantages :
- Simple and Fast Operations : Operations like push and pop are performed in
constant time \(O(1)\).
- Reversing Nature (LIFO) : Ideal for problems where the last added element is
needed first, like undo operations or browser history.
- Memory Efficiency : Stacks use only the memory required by the current data.
Use Case : Useful in recursion, reversing strings, and evaluating expressions (e.g.,
postfix notation).
---
### 4. Queue
Advantages :
- FIFO Nature : It ensures that the first element added is the first to be removed,
making it suitable for tasks like scheduling.
- Constant Time for Insertions and Deletions : Enqueue and dequeue operations
are performed in \(O(1)\) time.
- Fair Process Handling : Useful in systems that require fair handling of processes,
such as print queues or task scheduling in operating systems.
Advantages :
- Efficient Search and Sort : In Binary Search Trees (BST), searching, insertion,
and deletion operations can be done in \(O(\log n)\), making it efficient for
dynamic datasets.
- Hierarchical Structure : Ideal for representing hierarchical data like folder
structures or organizational charts.
- Balancing Capabilities : Advanced trees like AVL or Red-Black Trees are self-
balancing, which ensures that operations remain efficient.
---
### 6. Graph
Advantages :
- Modeling Complex Relationships : Graphs can represent complex real-world
systems with various relationships, such as social networks, transport systems, or
recommendation systems.
- Flexible Representation : Vertices (nodes) and edges can represent any entity
and their relationships, making graphs versatile.
- Efficient Traversal : Algorithms like DFS and BFS enable efficient traversal and
searching in large datasets.
Use Case : Social networks (e.g., Facebook friend connections), navigation
systems (e.g., Google Maps), or recommendation systems.
---
Advantages :
- Fast Access : With a good hash function, accessing elements in a hash table
takes constant time \(O(1)\), making it extremely fast for lookups.
- Efficient Key-Value Pairing : Hash tables allow efficient storage and retrieval of
key-value pairs, making them ideal for situations like database indexing.
- Flexible Storage : Hash tables can handle large datasets dynamically.
---
Advantages :
- Efficient Priority Management : Heaps are used for priority queues where the
smallest or largest element needs to be quickly accessed.
- Efficient Insertions and Deletions : Operations like insertions and deletions
maintain the heap property and take \(O(\log n)\) time.
- Dynamic Data Handling : Like binary trees, heaps grow dynamically without
wasting space.
Use Case : Implementing priority queues for scheduling, Dijkstra’s algorithm for
shortest path finding, and heapsort algorithm.
---
### 9. Trie
Advantages :
- Efficient Searching : Tries offer fast searching and auto-completion features,
especially for strings or dictionaries.
- Prefix-Based Searches : Tries allow searching for a prefix efficiently, making
them ideal for auto-suggestions.
- Memory Efficiency : Although they take more memory upfront, tries are space-
efficient when representing large sets of strings with common prefixes.
---
Choosing the right data structure depends on the specific use case, the size of the
dataset, and the performance requirements for operations like searching,
inserting, or deleting elements.
Definition :
An algorithm is a step-by-step procedure or set of rules designed to perform a
specific task or solve a problem. It takes an input, processes it through a series of
well-defined instructions, and produces an output. Algorithms are the backbone
of computer programs and software applications, enabling machines to carry out
tasks automatically and efficiently.
In simpler terms, an algorithm is like a recipe with detailed steps for solving a
problem or completing a task.
---
1. Input :
- The algorithm should have well-defined inputs, whether one or more, or even
zero inputs in some cases.
2. Output :
- The algorithm must produce a result or output. The output should be related to
the given inputs and be well-defined.
Example : The sorted array after applying the sorting algorithm is the output.
3. Finiteness :
- The algorithm must terminate after a finite number of steps. It should not go
into an infinite loop.
Example : If an algorithm is designed to find the largest number in a list, it
should eventually stop after going through the list.
4. Definiteness :
- Every step of the algorithm should be clear and unambiguous. Each operation
must be well-defined so that it can be executed without confusion.
Example : "Add 2 to the variable x" is a definite instruction, while "Do something
with x" is ambiguous.
5. Effectiveness :
- An algorithm should be efficient enough to solve the problem using a
reasonable amount of resources (time, memory, etc.). It should provide a solution
using basic operations within the system's capabilities.
6. Generality :
- A good algorithm should be applicable to a wide range of problems, not just for
a specific set of inputs. It should be adaptable for different situations.
Example : A general search algorithm should work for arrays of any length and
with various types of data (integers, strings, etc.).
---
1. Input :
- Data is fed into the algorithm as the starting point. This could be a set of
numbers, strings, or any other type of data.
2. Processing (Steps/Instructions) :
- The input data goes through a series of operations based on the defined steps
of the algorithm. This is where the core logic takes place.
Example : In a merge sort, the algorithm first breaks down the array into smaller
sub-arrays and then merges them back.
4. Output :
- Once the algorithm completes its process, it produces an output that is the
result of the input data after applying the algorithm's steps.
Example : The sorted list of numbers is the final output of the sorting algorithm.
5. Termination :
- The algorithm finishes its execution and stops, ensuring it does not run
indefinitely.
---
1. Efficiency :
- Algorithms allow us to solve problems faster and more efficiently. For example,
searching for an element in a sorted array can be done much quicker using an
algorithm like binary search than by checking each element one by one.
2. Automation :
- Algorithms automate complex and repetitive tasks. Computers can follow
these algorithms to execute processes without human intervention, increasing
productivity.
3. Problem-Solving :
- Algorithms are essential for solving problems in computer science,
mathematics, and many other fields. They break down complicated problems into
manageable steps.
Example : The Dijkstra algorithm is used to find the shortest path between two
points on a map.
4. Optimization :
- Algorithms help optimize systems to perform tasks using fewer resources
(time, memory, etc.). For example, an algorithm can minimize the number of
comparisons or operations needed to achieve the desired outcome.
Example : The QuickSort algorithm is often faster than other sorting methods,
especially for large datasets.
5. Consistency :
- Since algorithms follow a fixed sequence of steps, they consistently produce
the same output for the same input, ensuring accuracy and reliability.
6. Scalability :
- Well-designed algorithms can handle larger datasets efficiently, making them
scalable for systems that grow over time.
Example : Social media platforms use algorithms to manage and sort massive
amounts of user data in real-time.
---
---
### Conclusion
---
Advantages :
- Simple to understand and implement.
- Guaranteed to find the solution if one exists.
Disadvantages :
- Inefficient for large problems due to high time complexity.
Example :
- String Matching : In a brute-force string matching algorithm, you compare the
pattern with every substring in the text.
```cpp
// C++ example of brute-force string matching
bool bruteForceSearch(string text, string pattern) {
for (int i = 0; i <= text.length() - pattern.length(); i++) {
int j = 0;
while (j < pattern.length() && text[i + j] == pattern[j]) {
j++;
}
if (j == pattern.length()) {
return true; // Pattern found
}
}
return false; // Pattern not found
}
```
---
Description :
- This approach divides a problem into smaller sub-problems, solves them
independently, and then combines their solutions to get the final result.
Advantages :
- Efficient for large problems.
- Often reduces the time complexity significantly.
Disadvantages :
- Requires recursion, which can increase memory usage due to function calls.
Example :
- Merge Sort : Divide the array into halves, sort each half, and then merge them.
```cpp
// C++ example of Merge Sort using divide and conquer
void merge(int arr[], int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) arr[k++] = L[i++];
else arr[k++] = R[j++];
}
---
Description :
- Dynamic programming breaks a problem down into smaller overlapping sub-
problems and solves each sub-problem just once, storing the result for future use
(memoization).
Advantages :
- Avoids recalculating solutions to sub-problems, reducing time complexity.
- Efficient for problems with overlapping sub-problems like optimization tasks.
Disadvantages :
- Can use a lot of memory if the problem space is large (due to storing sub-
problems).
Example :
- Fibonacci Sequence : Using dynamic programming to store previously
calculated Fibonacci numbers.
```cpp
// C++ example of Fibonacci using dynamic programming
int fib(int n) {
int f[n + 2]; // Create an array to store Fibonacci numbers
f[0] = 0;
f[1] = 1;
for (int i = 2; i <= n; i++) {
f[i] = f[i - 1] + f[i - 2];
}
return f[n];
}
```
---
Description :
- A greedy algorithm builds a solution step by step by choosing the locally optimal
choice at each step, hoping to find a global optimum.
Advantages :
- Simple and efficient in certain problems, offering fast solutions.
- Requires fewer resources like memory.
Disadvantages :
- Doesn’t always produce the optimal solution for all problems.
Example :
- Coin Change Problem : Choose the largest denomination first to minimize the
number of coins.
```cpp
// C++ example of greedy algorithm for coin change
int minCoins(int coins[], int n, int amount) {
sort(coins, coins + n, greater<int>()); // Sort coins in decreasing order
int count = 0;
for (int i = 0; i < n; i++) {
while (amount >= coins[i]) {
amount -= coins[i];
count++;
}
}
return count; // Minimum number of coins
}
```
---
### 5. Backtracking
Description :
- Backtracking tries to build a solution incrementally by exploring all possible
options. If a solution fails, it backtracks and tries a different option.
Advantages :
- Useful for constraint satisfaction problems like puzzles.
- Can find all possible solutions, not just one.
Disadvantages :
- Can be slow due to its exhaustive search of all possibilities.
Example :
- N-Queens Problem : Place N queens on an NxN chessboard so no two queens
threaten each other.
```cpp
// C++ example of N-Queens problem using backtracking
bool isSafe(int board[][10], int row, int col, int n) {
for (int i = 0; i < col; i++) if (board[row][i]) return false;
for (int i = row, j = col; i >= 0 && j >= 0; i--, j--) if (board[i][j]) return false;
for (int i = row, j = col; i < n && j >= 0; i++, j--) if (board[i][j]) return false;
return true;
}
---
Description :
- In recursion, a problem is solved by breaking it down into smaller instances of
the same problem. Each smaller instance is solved recursively until a base case is
reached.
Advantages :
- Provides elegant solutions for problems that can be divided into smaller sub-
problems.
- Simplifies complex problems by breaking them down.
Disadvantages :
- Recursive solutions can lead to high memory usage due to function call stacks.
- Not always efficient, especially when overlapping sub-problems exist.
Example :
- Factorial Calculation : Recursive algorithm to calculate the factorial of a
number.
```cpp
// C++ example of factorial using recursion
int factorial(int n) {
if (n == 0) return 1;
return n * factorial(n - 1);
}
```
---
Description :
- Randomized algorithms use random numbers at some point during their process
to make decisions. These algorithms can be used to improve performance or for
probabilistic solutions.
Advantages :
- Can be faster in some cases compared to deterministic algorithms.
- Helps solve problems where deterministic approaches might be too slow.
Disadvantages :
- The result might not always be the same, and sometimes the solution is only
approximate.
Example :
- QuickSort (Randomized Pivot Selection) : Choosing a random pivot to avoid
worst-case performance.
```cpp
// C++ example of randomized quicksort
int partition(int arr[], int low, int high) {
int pivot = arr[high]; // Pivot
int i = low - 1;
for (int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
swap(arr[i], arr[j]);
}
}
swap(arr[i + 1], arr[high]);
return i + 1;
}
---
### Conclusion
Each algorithmic approach has its own advantages and is suited for specific types
of problems. By understanding the characteristics and application of each
approach, you can select the most efficient method for solving a particular
problem.
Algorithms can be classified into various types based on their functionality, design
methodology, and application area. Here are some of the most common types of
algorithms:
---
Examples :
- Bubble Sort : Simple comparison-based algorithm.
- Quick Sort : Efficient divide-and-conquer sorting algorithm.
- Merge Sort : Sorts by dividing the array and merging the sorted sub-arrays.
Real-World Use Case : Sorting data for display in applications like e-commerce
websites to show products based on price or rating.
```cpp
// C++ example of Bubble Sort
void bubbleSort(int arr[], int n) {
for (int i = 0; i < n-1; i++) {
for (int j = 0; j < n-i-1; j++) {
if (arr[j] > arr[j+1]) {
swap(arr[j], arr[j+1]);
}
}
}
}
```
---
Examples :
- Linear Search : Checks each element sequentially until the target is found.
- Binary Search : Efficiently finds an element in a sorted array by repeatedly
dividing the search interval in half.
---
Description : Graph algorithms are used to solve problems related to graph data
structures, where data is represented as nodes and edges.
Examples :
- Dijkstra's Algorithm : Finds the shortest path between nodes in a weighted
graph.
- Depth-First Search (DFS) : Explores as far as possible along each branch before
backtracking.
- Breadth-First Search (BFS) : Explores all neighbors at the present depth prior to
moving on to nodes at the next depth level.
Real-World Use Case : Used in network routing protocols to find the shortest path
for data transmission.
```cpp
// C++ example of Depth-First Search
void DFS(int v, vector<bool>& visited, const vector<vector<int>>& adj) {
visited[v] = true;
cout << v << " ";
for (int i : adj[v]) {
if (!visited[i]) DFS(i, visited, adj);
}
}
```
---
Examples :
- Fibonacci Sequence : Calculates Fibonacci numbers using memoization.
- Knapsack Problem : Solves optimization problems by selecting items to
maximize value without exceeding capacity.
```cpp
// C++ example of the Fibonacci sequence using dynamic programming
int fib(int n) {
int f[n + 1];
f[0] = 0; f[1] = 1;
for (int i = 2; i <= n; i++) {
f[i] = f[i - 1] + f[i - 2];
}
return f[n];
}
```
---
Examples :
- Prim's Algorithm : Finds the minimum spanning tree for a graph.
- Huffman Coding : Constructs an optimal prefix code for data compression.
Real-World Use Case : Used in optimization problems like job scheduling and
network routing.
```cpp
// C++ example of Prim's Algorithm (simplified)
void primsAlgorithm(int graph[V][V]) {
// Implementation to find minimum spanning tree using a greedy approach
}
```
---
Examples :
- N-Queens Problem : Places N queens on an NxN chessboard.
- Sudoku Solver : Solves a Sudoku puzzle.
Real-World Use Case : Used in puzzle solving, game playing, and constraint
satisfaction problems.
```cpp
// C++ example of backtracking in the N-Queens problem
bool solveNQueens(int board[N][N], int col) {
// Recursive function to solve the N-Queens problem
}
```
---
Examples :
- Randomized QuickSort : Chooses a random pivot to improve performance.
- Monte Carlo Methods : Uses randomness to solve deterministic problems.
```cpp
// C++ example of randomized QuickSort
int randomPartition(int arr[], int low, int high) {
int randomIndex = low + rand() % (high - low);
swap(arr[randomIndex], arr[high]);
return partition(arr, low, high);
}
```
---
Description : Divide and conquer algorithms break a problem into smaller sub-
problems, solve each sub-problem recursively, and combine the results.
Examples :
- Merge Sort : Divides the array, sorts the halves, and merges them.
- Binary Search : Recursively narrows down the search interval.
```cpp
// C++ example of Merge Sort (divide and conquer)
void mergeSort(int arr[], int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
}
```
---
### Conclusion
Understanding the various types of algorithms allows you to choose the most
appropriate one for solving specific problems efficiently. Each algorithm type has
its strengths, weaknesses, and applications in real-world scenarios.
1. Time Complexity
2. Space Complexity
---
Example :
```cpp
// C++ example of time complexity O(n) - Linear Time
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
cout << arr[i] << " "; // O(n)
}
}
```
---
### 2. Space Complexity
Example :
```cpp
// C++ example of space complexity O(n) - Linear Space
void createArray(int size) {
int* arr = new int[size]; // Memory usage grows linearly with size
// Array operations
delete[] arr; // Free allocated memory
}
```
---
### 3. Big O Notation
Examples :
- O(1) : The algorithm runs in constant time, regardless of input size.
- O(n) : The runtime or memory usage grows linearly with input size.
- O(n^2) : The runtime or memory usage grows quadratically with input size.
- O(log n) : The runtime or memory usage grows logarithmically with input size.
Example :
```cpp
// C++ example of Big O Notation
void constantTimeOperation() {
int x = 10; // O(1) - Constant Time
}
void linearTimeOperation(int n) {
for (int i = 0; i < n; i++) {
// O(n) - Linear Time
}
}
void quadraticTimeOperation(int n) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
// O(n^2) - Quadratic Time
}
}
}
void logarithmicTimeOperation(int n) {
while (n > 1) {
n /= 2; // O(log n) - Logarithmic Time
}
}
```
---
Example :
- Dynamic Array : Appending to a dynamic array is an O(1) operation on average,
even though resizing the array may take O(n) time occasionally. The average cost
per operation is amortized to O(1).
```cpp
// C++ example of amortized analysis with dynamic array
void append(int* &arr, int &size, int &capacity, int value) {
if (size == capacity) {
// Resize array
capacity *= 2;
int* newArr = new int[capacity];
for (int i = 0; i < size; i++) newArr[i] = arr[i];
delete[] arr;
arr = newArr;
}
arr[size++] = value; // O(1) on average
}
```
---
### Conclusion
---
Example in C++ :
```cpp
// C++ example of Big O Notation O(n)
void printArray(int arr[], int n) {
for (int i = 0; i < n; i++) {
cout << arr[i] << " "; // O(n) - Linear Time
}
}
```
Example in Java :
```java
// Java example of Big Ω Notation Ω(n)
public void printArray(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " "); // Ω(n) - Linear Time
}
}
```
Example in C++ :
```cpp
// C++ example of Big Θ Notation Θ(n)
void printArray(int arr[], int n) {
for (int i = 0; i < n; i++) {
cout << arr[i] << " "; // Θ(n) - Linear Time
}
}
```
---
### Examples and Applications
```cpp
// C++ example of O(1) - Constant Time
int getElement(int arr[], int index) {
return arr[index]; // O(1) - Constant Time
}
```
```cpp
// C++ example of O(n) - Linear Time
int findMax(int arr[], int n) {
int max = arr[0];
for (int i = 1; i < n; i++) {
if (arr[i] > max) max = arr[i];
}
return max; // O(n) - Linear Time
}
```
```cpp
// C++ example of O(n^2) - Quadratic Time
void bubbleSort(int arr[], int n) {
for (int i = 0; i < n-1; i++) {
for (int j = 0; j < n-i-1; j++) {
if (arr[j] > arr[j+1]) {
swap(arr[j], arr[j+1]);
}
}
}
}
```
```cpp
// C++ example of O(log n) - Logarithmic Time
int binarySearch(int arr[], int left, int right, int x) {
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == x) return mid;
if (arr[mid] < x) left = mid + 1;
else right = mid - 1;
}
return -1; // Element not found
}
```
---
### Conclusion
### Arrays
1. One-Dimensional Array :
- Description : A simple list of elements where each element is accessed by a
single index.
- Example : A list of integers.
```cpp
// C++ example of a one-dimensional array
int arr[5] = {1, 2, 3, 4, 5};
```
2. Two-Dimensional Array :
- Description : An array of arrays, where elements are accessed by two indices
(row and column).
- Example : A matrix.
```cpp
// C++ example of a two-dimensional array
int matrix[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
```
3. Multi-Dimensional Array :
- Description : Arrays with more than two dimensions. Often used in complex
data representations.
- Example : A 3D matrix.
```cpp
// C++ example of a three-dimensional array
int cube[2][2][2] = {
{
{1, 2},
{3, 4}
},
{
{5, 6},
{7, 8}
}
};
```
4. Dynamic Array :
- Description : An array whose size can be changed during runtime.
Implemented using pointers and dynamic memory allocation.
- Example : Using `new` in C++.
```cpp
// C++ example of a dynamic array
int* dynamicArr = new int[5]; // Create an array of size 5
// Remember to free memory
delete[] dynamicArr;
```
---
---
### Notation
1. Accessing Elements :
- Description : Retrieve the value of an element using its index.
- Example :
```cpp
// C++ example of accessing an array element
int arr[5] = {1, 2, 3, 4, 5};
int value = arr[2]; // Accesses the element at index 2 (value is 3)
```
2. Inserting Elements :
- Description : Add an element at a specific index (for static arrays, this requires
shifting elements).
- Example : Inserting into a dynamic array.
```cpp
// C++ example of inserting into a dynamic array
int* dynamicArr = new int[6]; // New size 6
// Copy old array to new array
for (int i = 0; i < 5; i++) dynamicArr[i] = arr[i];
dynamicArr[5] = 6; // Insert new element
delete[] arr; // Free old array memory
```
3. Deleting Elements :
- Description : Remove an element and shift the remaining elements to fill the
gap.
- Example : Deleting from a dynamic array.
```cpp
// C++ example of deleting from a dynamic array
int* newArr = new int[4]; // New size 4
for (int i = 0; i < 4; i++) newArr[i] = dynamicArr[i];
delete[] dynamicArr; // Free old array memory
```
4. Updating Elements :
- Description : Modify the value of an element at a specific index.
- Example :
```cpp
// C++ example of updating an array element
int arr[5] = {1, 2, 3, 4, 5};
arr[2] = 10; // Update element at index 2 to 10
```
5. Traversing :
- Description : Iterate through each element of the array to perform operations
like printing or summing.
- Example :
```cpp
// C++ example of traversing an array
int arr[5] = {1, 2, 3, 4, 5};
for (int i = 0; i < 5; i++) {
cout << arr[i] << " "; // Print each element
}
```
---
### Conclusion
---
4. Indexing :
- Description : Arrays allow direct indexing, meaning you can access any
element directly by its index, which is useful for operations requiring random
access.
- Example : For an array `arr` with size `n`, accessing `arr[i]` is direct and fast.
5. Memory Efficiency :
- Description : Since arrays use contiguous memory locations, there’s minimal
overhead compared to more complex data structures.
- Example : In contrast to linked lists, arrays do not require additional memory
for storing pointers.
---
1. Fixed Size :
- Description : The size of an array must be defined at compile time for static
arrays, or it requires manual resizing for dynamic arrays. This can lead to
inefficient memory usage if the size is not well-estimated.
- Example : If you allocate an array of size 100, but only use 50 elements, you
waste memory.
3. Memory Waste :
- Description : If the array is too large and not fully utilized, it results in memory
waste. Conversely, if the array is too small, you may need to create a new larger
array and copy the elements over.
- Example : A large array with many unused slots consumes more memory than
necessary.
5. Limited Flexibility :
- Description : Arrays lack flexibility in terms of resizing and adding elements
dynamically compared to other data structures like linked lists.
- Example : You cannot directly append an element to a fixed-size array without
creating a new array.
---
### Summary
Arrays are a versatile and efficient data structure for situations where the number
of elements is known in advance and doesn't change frequently. They offer fast
access and efficient memory usage but come with limitations such as fixed size
and costly operations for insertion and deletion. Choosing the right data structure
often depends on the specific requirements of your application, including how
dynamic your data needs to be.
---
### Properties of 2D Arrays
1. Rectangular Layout :
- Description : Elements are stored in a rectangular grid with a fixed number of
rows and columns.
- Example : A 3x3 matrix has 3 rows and 3 columns.
3. Indexing :
- Description : Accessing elements requires two indices: one for the row and
one for the column.
- Example : `matrix[i][j]` where `i` is the row index and `j` is the column index.
4. Fixed Size :
- Description : The size of the matrix (number of rows and columns) is typically
fixed at the time of declaration.
- Example : A matrix declared as `int matrix[3][3]` cannot change its size
dynamically.
---
### Notation
- Matrix Representation : A matrix is often represented in mathematical notation
as:
\[
\begin{bmatrix}
a_{11} & a_{12} & a_{13} \\
a_{21} & a_{22} & a_{23} \\
a_{31} & a_{32} & a_{33}
\end{bmatrix}
\]
where \(a_{ij}\) represents the element at row \(i\) and column \(j\).
---
1. Accessing Elements :
- Description : Retrieve the value of an element using its row and column
indices.
- Example :
```cpp
// C++ example of accessing an element in a 2D array
int matrix[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
int value = matrix[1][2]; // Accesses the element at row 1, column 2 (value is 6)
```
2. Inserting Elements :
- Description : Insert a new element at a specific location in the matrix.
- Example :
```cpp
// C++ example of inserting an element in a 2D array
int matrix[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
matrix[2][1] = 10; // Update element at row 2, column 1 to 10
```
3. Deleting Elements :
- Description : Removing an element involves setting it to a default value (e.g., 0)
since matrix size is fixed.
- Example :
```cpp
// C++ example of deleting an element in a 2D array
int matrix[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
matrix[1][1] = 0; // Set element at row 1, column 1 to 0
```
4. Traversing :
- Description : Iterate over each element in the matrix to perform operations
such as printing or summing.
- Example :
```cpp
// C++ example of traversing a 2D array
int matrix[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
cout << matrix[i][j] << " "; // Print each element
}
cout << endl;
}
```
---
1. Mathematical Operations :
- Example : Used in linear algebra for operations such as matrix multiplication,
determinant calculation, and solving systems of linear equations.
```cpp
// C++ example of matrix addition
void addMatrices(int A[3][3], int B[3][3], int result[3][3]) {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
result[i][j] = A[i][j] + B[i][j];
}
}
}
```
2. Image Processing :
- Example : Images are represented as matrices of pixel values, where each
pixel's color is represented by values in a 2D array.
3. Game Development :
- Example : Used to represent game boards, such as in chess or tic-tac-toe,
where each cell in the grid can hold game state information.
4. Data Tables :
- Example : 2D arrays can represent tables of data, such as spreadsheets or
databases, where rows and columns represent different data attributes.
5. Dynamic Programming :
- Example : 2D arrays are often used in dynamic programming algorithms for
problems like matrix chain multiplication or longest common subsequence.
---
### Conclusion
2D arrays (matrices) are versatile data structures useful for a wide range of
applications, from mathematical computations to image processing. They offer
efficient access and straightforward implementation but come with limitations
like fixed size and lack of flexibility compared to more advanced data structures.
Understanding how to use and manipulate 2D arrays effectively can help solve
many practical problems.
### Strings
---
### Properties of Strings
1. Immutable or Mutable :
- Description : In some languages (e.g., Java), strings are immutable, meaning
they cannot be changed after creation. In others (e.g., C++), strings are mutable.
- Example : In Java, modifying a string creates a new string, whereas in C++, you
can directly modify characters in a `char` array.
2. Indexed Access :
- Description : Characters in a string can be accessed using indices, typically
starting from 0.
- Example : In the string `"Hello"`, `'H'` is at index 0, `'e'` at index 1, and so on.
3. Variable Length :
- Description : Strings can vary in length. The length is usually managed by the
language or data structure.
- Example : The length of `"Hello"` is 5, while `"Hi"` has a length of 2.
---
1. Concatenation :
- Description : Joining two or more strings together.
- Example :
```cpp
// C++ example of concatenation
std::string str1 = "Hello, ";
std::string str2 = "World!";
std::string result = str1 + str2; // "Hello, World!"
```
```java
// Java example of concatenation
String str1 = "Hello, ";
String str2 = "World!";
String result = str1 + str2; // "Hello, World!"
```
2. Substring Extraction :
- Description : Extracting a portion of the string.
- Example :
```cpp
// C++ example of substring extraction
std::string str = "Hello, World!";
std::string sub = str.substr(0, 5); // "Hello"
```
```java
// Java example of substring extraction
String str = "Hello, World!";
String sub = str.substring(0, 5); // "Hello"
```
3. Searching :
- Description : Finding the position of a substring or character.
- Example :
```cpp
// C++ example of searching
std::string str = "Hello, World!";
size_t pos = str.find("World"); // 7 (position of "World")
```
```java
// Java example of searching
String str = "Hello, World!";
int pos = str.indexOf("World"); // 7 (position of "World")
```
4. Replacing :
- Description : Replacing occurrences of a substring with another substring.
- Example :
```cpp
// C++ example of replacing
std::string str = "Hello, World!";
std::string newStr = std::regex_replace(str, std::regex("World"), "Universe"); //
"Hello, Universe!"
```
```java
// Java example of replacing
String str = "Hello, World!";
String newStr = str.replace("World", "Universe"); // "Hello, Universe!"
```
5. Splitting :
- Description : Dividing a string into substrings based on a delimiter.
- Example :
```cpp
// C++ example of splitting using stringstream
#include <sstream>
#include <vector>
```java
// Java example of splitting
String str = "Hello,World,Universe";
String[] tokens = str.split(","); // ["Hello", "World", "Universe"]
```
6. Trimming :
- Description : Removing whitespace from the beginning and end of a string.
- Example :
```cpp
// C++ example of trimming (requires external library like Boost)
std::string str = " Hello, World! ";
str.erase(0, str.find_first_not_of(' ')); // "Hello, World! "
str.erase(str.find_last_not_of(' ') + 1); // "Hello, World!"
```
```java
// Java example of trimming
String str = " Hello, World! ";
String trimmed = str.trim(); // "Hello, World!"
```
---
1. String Builder/Buffer :
- Description : Used to efficiently manipulate strings, especially when frequent
modifications are needed.
- Example :
```cpp
// C++ example using std::ostringstream
#include <sstream>
std::ostringstream oss;
oss << "Hello" << ", " << "World!";
std::string result = oss.str(); // "Hello, World!"
```
```java
// Java example using StringBuilder
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(", ");
sb.append("World!");
String result = sb.toString(); // "Hello, World!"
```
2. String Formatting :
- Description : Formatting strings using placeholders or formatting functions.
- Example :
```cpp
// C++ example of string formatting using std::sprintf
char buffer[50];
std::sprintf(buffer, "Hello, %s!", "World");
std::string result = buffer; // "Hello, World!"
```
```java
// Java example of string formatting
String result = String.format("Hello, %s!", "World"); // "Hello, World!"
```
3. Regular Expressions :
- Description : Used for pattern matching and text manipulation.
- Example :
```cpp
// C++ example of using regular expressions
#include <regex>
std::string str = "Hello, World!";
std::regex reg("World");
bool found = std::regex_search(str, reg); // true
```
```java
// Java example of using regular expressions
import java.util.regex.*;
---
1. Text Processing :
- Example : Used in text editors, word processors, and any application requiring
text manipulation.
2. Data Serialization :
- Example : Strings are used to serialize data for storage or transmission, such
as in JSON or XML formats.
3. User Input :
- Example : Handling and validating user input from forms or command-line
interfaces.
4. Communication Protocols :
- Example : Strings are used in network protocols to encode and decode
messages.
5. Search Engines :
- Example : String manipulation and pattern matching are essential for search
and indexing algorithms.
---
### Conclusion
Strings are a crucial data type in programming used for handling text. They offer
various operations and manipulations, from basic concatenation to complex
regular expression processing. Understanding how to work with strings efficiently
can greatly enhance text processing capabilities in software development.
Definition : A linked list is a linear data structure where each element (node)
points to the next element in the sequence. Unlike arrays, linked lists are not
stored in contiguous memory locations, which allows for dynamic memory
allocation.
---
---
1. Insertion :
- Description : Adding a new node at the beginning, end, or a specific position in
the list.
- Code Example :
```cpp
// C++ example of inserting at the beginning of a singly linked list
struct Node {
int data;
Node* next;
Node(int val) : data(val), next(NULL) {}
};
```java
// Java example of inserting at the beginning of a singly linked list
class Node {
int data;
Node next;
Node(int val) {
data = val;
next = null;
}
}
2. Deletion :
- Description : Removing a node from the list by updating pointers.
- Code Example :
```cpp
// C++ example of deleting a node in a singly linked list
void deleteNode(Node*& head, int key) {
Node* temp = head;
Node* prev = NULL;
prev->next = temp->next;
delete temp;
}
```
```java
// Java example of deleting a node in a singly linked list
void deleteNode(Node head, int key) {
Node temp = head;
Node prev = null;
prev.next = temp.next;
}
```
3. Traversal :
- Description : Visiting each node in the list to perform an operation, such as
printing.
- Code Example :
```cpp
// C++ example of traversing a singly linked list
void traverseList(Node* head) {
Node* temp = head;
while (temp != NULL) {
std::cout << temp->data << " ";
temp = temp->next;
}
std::cout << std::endl;
}
```
```java
// Java example of traversing a singly linked list
void traverseList(Node head) {
Node temp = head;
while (temp != null) {
System.out.print(temp.data + " ");
temp = temp.next;
}
System.out.println();
}
```
4. Searching :
- Description : Finding a node with a specific value.
- Code Example :
```cpp
// C++ example of searching in a singly linked list
bool search(Node* head, int key) {
Node* temp = head;
while (temp != NULL) {
if (temp->data == key) return true;
temp = temp->next;
}
return false;
}
```
```java
// Java example of searching in a singly linked list
boolean search(Node head, int key) {
Node temp = head;
while (temp != null) {
if (temp.data == key) return true;
temp = temp.next;
}
return false;
}
```
---
1. Dynamic Size :
- Description : Linked lists can easily grow or shrink in size by adding or
removing nodes, which is not possible with arrays.
- Example : Linked lists can handle variable-sized data structures efficiently.
2. Ease of Insertions/Deletions :
- Description : Insertion and deletion operations are more efficient compared to
arrays, especially for operations at the beginning or middle of the list.
- Example : Adding an element to the front of a linked list is an O(1) operation.
3. No Wasted Memory :
- Description : Linked lists use only as much memory as needed, unlike arrays
that may waste space if the allocated size is not fully used.
- Example : A linked list only allocates memory for the nodes it contains.
---
1. Memory Overhead :
- Description : Each node requires extra memory for storing pointers
(next/previous), which can lead to overhead compared to arrays.
- Example : Each node in a linked list has additional space for storing pointers.
2. Sequential Access :
- Description : Linked lists do not support direct access to elements; traversal is
required to access a specific node.
- Example : To access the 10th element, you must traverse from the head to the
10th node.
3. Pointer Complexity :
- Description : Managing pointers can be complex and error-prone, leading to
potential issues such as memory leaks or dangling pointers.
- Example : Incorrect pointer manipulation can lead to segmentation faults or
memory corruption.
---
3. Real-time Systems :
- Example : Suitable for real-time systems where predictable memory usage and
frequent insertions/deletions are required.
4. Memory Efficient Data Structures :
- Example : Used in environments with limited memory where efficient use of
space is critical, such as embedded systems.
---
### Conclusion
Linked lists are a versatile data structure suitable for applications requiring
dynamic memory allocation and frequent insertions or deletions. They offer
advantages in flexibility and ease of modification but come with trade-offs in
terms of memory usage and access time. Understanding how to implement and
use linked lists effectively can enhance your ability to solve various programming
problems efficiently.
### Stack
Definition : A stack is a linear data structure that follows the Last In, First Out
(LIFO) principle. This means that the most recently added element is the first one
to be removed. It can be visualized as a collection of items stacked on top of each
other, where only the top item is accessible for operations.
---
1. Push :
- Description : Adds an element to the top of the stack.
- Code Example :
```cpp
// C++ example of push operation
#include <stack>
std::stack<int> s;
s.push(10); // Pushes 10 onto the stack
s.push(20); // Pushes 20 onto the stack
```
```java
// Java example of push operation
import java.util.Stack;
2. Pop :
- Description : Removes and returns the top element of the stack. If the stack is
empty, it may cause an error or exception.
- Code Example :
```cpp
// C++ example of pop operation
if (!s.empty()) {
int top = s.top(); // Gets the top element
s.pop(); // Removes the top element
}
```
```java
// Java example of pop operation
if (!stack.isEmpty()) {
int top = stack.peek(); // Gets the top element
stack.pop(); // Removes the top element
}
```
```cpp
// C++ example of peek operation
if (!s.empty()) {
int top = s.top(); // Gets the top element
}
```
```java
// Java example of peek operation
if (!stack.isEmpty()) {
int top = stack.peek(); // Gets the top element
}
```
4. IsEmpty :
- Description : Checks if the stack is empty.
- Code Example :
```cpp
// C++ example of isEmpty operation
bool isEmpty = s.empty(); // Returns true if the stack is empty
```
```java
// Java example of isEmpty operation
boolean isEmpty = stack.isEmpty(); // Returns true if the stack is empty
```
---
1. Simple Operations :
- Description : Stack operations (push, pop, and peek) are straightforward and
efficient.
- Example : Implementing undo functionality in software.
3. Supports Recursion :
- Description : Stacks are used to implement recursive function calls,
maintaining the state of each function call.
- Example : Function calls and returns in programming languages.
4. Easy to Implement :
- Description : Stacks can be easily implemented using arrays or linked lists.
- Example : Stack data structures are used in various algorithms and
applications.
---
1. Limited Access :
- Description : Only the top element is accessible; other elements cannot be
accessed directly.
- Example : Cannot retrieve elements below the top without removing them first.
2. Fixed Size (for Array-based Implementation) :
- Description : In array-based stacks, the size of the stack is fixed and cannot
grow beyond its allocated size.
- Example : Stack overflow may occur if the stack exceeds its allocated size.
3. Stack Overflow :
- Description : If the stack exceeds its maximum capacity (in array-based
implementations), it may cause a stack overflow.
- Example : Recursion depth exceeding stack size can cause runtime errors.
---
2. Expression Evaluation :
- Description : Stacks are used to evaluate expressions and manage operators in
infix, prefix, and postfix notations.
- Example : Expression evaluation in calculators and compilers.
3. Undo Mechanism :
- Description : Stacks can be used to implement undo functionality in software
applications.
- Example : Text editors use stacks to undo previous actions.
4. Backtracking Algorithms :
- Description : Stacks are used in algorithms that require backtracking to explore
all possible solutions.
- Example : Depth-first search (DFS) in graphs and solving puzzles like Sudoku.
5. Parsing Syntax :
- Description : Stacks are used in parsing algorithms to validate and process
syntax in programming languages.
- Example : Checking for balanced parentheses and parsing expressions in
compilers.
---
### Conclusion
Stacks are fundamental data structures that support LIFO operations, making
them ideal for tasks involving sequential processing, recursion, and managing
function calls. Their simplicity and efficiency make them widely used in various
applications, from basic programming tasks to complex algorithms.
Understanding stacks and their operations can enhance your ability to implement
and solve a range of computational problems effectively.
### Queue
Definition : A queue is a linear data structure that follows the First In, First Out
(FIFO) principle. This means that the first element added to the queue will be the
first one to be removed. It can be visualized as a collection of items arranged in a
sequence, where elements are added at the rear and removed from the front.
---
1. Enqueue :
- Description : Adds an element to the rear (end) of the queue.
- Code Example :
```cpp
// C++ example of enqueue operation using std::queue
#include <queue>
std::queue<int> q;
q.push(10); // Adds 10 to the rear of the queue
q.push(20); // Adds 20 to the rear of the queue
```
```java
// Java example of enqueue operation using java.util.Queue
import java.util.LinkedList;
import java.util.Queue;
Queue<Integer> queue = new LinkedList<>();
queue.add(10); // Adds 10 to the rear of the queue
queue.add(20); // Adds 20 to the rear of the queue
```
2. Dequeue :
- Description : Removes and returns the element from the front of the queue. If
the queue is empty, it may cause an error or exception.
- Code Example :
```cpp
// C++ example of dequeue operation
if (!q.empty()) {
int front = q.front(); // Gets the front element
q.pop(); // Removes the front element
}
```
```java
// Java example of dequeue operation
if (!queue.isEmpty()) {
int front = queue.poll(); // Gets and removes the front element
}
```
```cpp
// C++ example of peek operation
if (!q.empty()) {
int front = q.front(); // Gets the front element
}
```
```java
// Java example of peek operation
if (!queue.isEmpty()) {
int front = queue.peek(); // Gets the front element
}
```
4. IsEmpty :
- Description : Checks if the queue is empty.
- Code Example :
```cpp
// C++ example of isEmpty operation
bool isEmpty = q.empty(); // Returns true if the queue is empty
```
```java
// Java example of isEmpty operation
boolean isEmpty = queue.isEmpty(); // Returns true if the queue is empty
```
---
1. FIFO Order :
- Description : Maintains the order of elements, ensuring that elements are
processed in the order they are added.
- Example : Task scheduling and job processing where tasks are handled in the
order they arrive.
1. Limited Access :
- Description : Access to elements is restricted to the front (for removal) and
rear (for addition). Direct access to other elements is not possible.
- Example : Cannot retrieve elements from the middle of the queue without
removing them.
---
1. Task Scheduling :
- Description : Queues are used to manage tasks or processes that need to be
executed in the order they arrive.
- Example : Operating systems use queues to schedule processes and manage
CPU execution.
2. Buffering :
- Description : Queues are used to buffer data between producers and
consumers, allowing smooth data transfer.
- Example : Print spooling where print jobs are queued and processed in order.
4. Event Handling :
- Description : Queues are used to manage events and handle them in the order
they occur.
- Example : Event-driven programming and handling user inputs in GUI
applications.
5. Message Queuing :
- Description : Used in distributed systems to handle messages between
different components or services.
- Example : Messaging systems like RabbitMQ and Apache Kafka.
---
### Conclusion
Queues are fundamental data structures that support FIFO operations, making
them ideal for tasks involving sequential processing and buffering. Their simplicity
and efficiency in managing ordered data make them widely used in various
applications, from task scheduling to real-time event handling. Understanding
queues and their operations is essential for implementing and solving problems
that require orderly data processing and management.
Stack : A stack follows the Last In, First Out (LIFO) principle. You can implement a
stack using an array with the following operations:
C++ Implementation:
```cpp
#include <iostream>
#define MAX 100 // Maximum size of the stack
class Stack {
private:
int top;
int arr[MAX];
public:
Stack() : top(-1) {} // Constructor initializes top to -1
int pop() {
if (top < 0) {
std::cout << "Stack Underflow\n";
return -1;
}
return arr[top--]; // Return top element and decrement top
}
int peek() {
if (top < 0) {
std::cout << "Stack is Empty\n";
return -1;
}
return arr[top]; // Return top element
}
bool isEmpty() {
return top < 0; // Check if top is less than 0
}
};
int main() {
Stack s;
s.push(10);
s.push(20);
std::cout << "Top element: " << s.peek() << std::endl; // Outputs 20
std::cout << "Popped element: " << s.pop() << std::endl; // Outputs 20
std::cout << "Top element: " << s.peek() << std::endl; // Outputs 10
return 0;
}
```
Java Implementation:
```java
public class Stack {
private static final int MAX = 100; // Maximum size of the stack
private int top;
private int[] arr;
public Stack() {
arr = new int[MAX];
top = -1; // Constructor initializes top to -1
}
Queue : A queue follows the First In, First Out (FIFO) principle. You can
implement a queue using an array with the following operations:
C++ Implementation:
```cpp
#include <iostream>
#define MAX 100 // Maximum size of the queue
class Queue {
private:
int front, rear, size;
int arr[MAX];
public:
Queue() : front(0), rear(-1), size(0) {} // Constructor initializes front, rear, and
size
int dequeue() {
if (size <= 0) {
std::cout << "Queue Underflow\n";
return -1;
}
int value = arr[front];
front = (front + 1) % MAX; // Increment front in a circular manner
size--;
return value;
}
int peek() {
if (size <= 0) {
std::cout << "Queue is Empty\n";
return -1;
}
return arr[front]; // Return front element
}
bool isEmpty() {
return size <= 0; // Check if size is less than or equal to 0
}
};
int main() {
Queue q;
q.enqueue(10);
q.enqueue(20);
std::cout << "Front element: " << q.peek() << std::endl; // Outputs 10
std::cout << "Dequeued element: " << q.dequeue() << std::endl; // Outputs 10
std::cout << "Front element: " << q.peek() << std::endl; // Outputs 20
return 0;
}
```
Java Implementation:
```java
public class Queue {
private static final int MAX = 100; // Maximum size of the queue
private int front, rear, size;
private int[] arr;
public Queue() {
arr = new int[MAX];
front = 0;
rear = -1;
size = 0; // Constructor initializes front, rear, and size
}
public void enqueue(int value) {
if (size >= MAX) {
System.out.println("Queue Overflow");
return;
}
rear = (rear + 1) % MAX; // Increment rear in a circular manner
arr[rear] = value;
size++;
}
Note: For both stacks and queues, array-based implementations can be simple
but may face limitations such as fixed size and potential overflow. For more
dynamic behavior, linked list-based implementations or other data structures
might be more suitable.
Key Terms :
- Root : The topmost node of the tree.
- Node : An element of the tree that contains a value and pointers to its child
nodes.
- Edge : A connection between two nodes.
- Leaf : A node with no children.
- Subtree : A tree consisting of a node and its descendants.
- Height : The length of the longest path from a node to a leaf.
---
1. Binary Tree
- Definition : A tree in which each node has at most two children, referred to as
the left child and the right child.
- Characteristics :
- Complete Binary Tree : All levels, except possibly the last, are completely
filled. The last level is filled from left to right.
- Full Binary Tree : Every node has either 0 or 2 children.
- Perfect Binary Tree : All internal nodes have exactly two children, and all leaf
nodes are at the same level.
- Code Example (C++):
```cpp
#include <iostream>
struct Node {
int data;
Node* left;
Node* right;
int main() {
Node* root = new Node(1);
root->left = new Node(2);
root->right = new Node(3);
root->left->left = new Node(4);
root->left->right = new Node(5);
return 0;
}
```
```java
class TreeNode {
int data;
TreeNode left, right;
TreeNode(int value) {
data = value;
left = right = null;
}
}
```cpp
#include <iostream>
struct BSTNode {
int data;
BSTNode* left;
BSTNode* right;
int main() {
BSTNode* root = nullptr;
root = insert(root, 50);
insert(root, 30);
insert(root, 70);
insert(root, 20);
insert(root, 40);
insert(root, 60);
insert(root, 80);
return 0;
}
```
BSTNode(int value) {
data = value;
left = right = null;
}
}
3. Balanced Tree
- Definition : A binary tree where the height of the two subtrees of any node
differs by at most one. Common examples include AVL trees and Red-Black trees.
- Characteristics :
- AVL Tree : Self-balancing binary search tree with O(log n) time complexity for
insertion, deletion, and lookup.
- Red-Black Tree : A balanced binary search tree with an additional color
property to ensure balanced height.
```cpp
#include <iostream>
#include <algorithm>
struct AVLNode {
int data;
AVLNode* left;
AVLNode* right;
int height;
AVLNode* rotateRight(AVLNode* y) {
AVLNode* x = y->left;
AVLNode* T2 = x->right;
x->right = y;
y->left = T2;
updateHeight(y);
updateHeight(x);
return x;
}
AVLNode* rotateLeft(AVLNode* x) {
AVLNode* y = x->right;
AVLNode* T2 = y->left;
y->left = x;
x->right = T2;
updateHeight(x);
updateHeight(y);
return y;
}
updateHeight(node);
return node;
}
int main() {
AVLNode* root = nullptr;
root = insert(root, 30);
root = insert(root, 20);
root = insert(root, 10);
root = insert(root, 5);
return 0;
}
```
```java
class AVLNode {
int data;
AVLNode left, right;
int height;
AVLNode(int value) {
data = value;
left = right = null;
height = 1;
}
}
return node;
}
4. Heap
- Definition : A special tree-based structure that satisfies the heap property. In a
max-heap, each parent node is greater than or equal to its child nodes. In a min-
heap, each parent node is less than or equal to its child nodes.
- Characteristics :
- Max-Heap : The largest element is at the root.
- Min-Heap : The smallest element is at the root.
```cpp
#include <iostream>
#include <vector>
class MaxHeap {
private:
std::vector<int> heap;
if (largest != index) {
std::swap(heap[index], heap[largest]);
heapify(largest);
}
}
public:
void insert(int value) {
heap.push_back(value);
int index = heap.size() - 1;
while (index != 0 && heap[(index - 1) / 2] < heap[index]) {
std::swap(heap[index], heap[(index - 1) / 2]);
index = (index - 1) / 2;
}
}
int extractMax() {
if (heap.empty()) return -1;
if (heap.size() == 1) {
int root = heap[0];
heap.pop_back();
return root;
}
return root;
}
void printHeap() {
for (int value : heap)
std::cout << value << " ";
std::cout << std::endl;
}
};
int main() {
MaxHeap mh;
mh.insert(10);
mh.insert(20);
mh.insert(15);
mh.insert(30);
mh.insert(40);
return 0;
}
```
public MaxHeap() {
heap = new ArrayList<>();
}
return root;
}
if (largest != index) {
Collections.swap(heap, index, largest);
heapify(largest);
}
}
System.out.print("Heap: ");
mh.printHeap();
5. Trie
- Definition : A tree-like data structure used for efficient retrieval of keys in a
dataset of strings, such as dictionaries or word lists.
- Characteristics :
- Each node represents a common prefix of some strings.
- The root represents an empty string.
- Each edge represents a single character.
```cpp
#include <iostream>
#include <unordered_map>
class TrieNode {
public:
std::unordered_map<char, TrieNode*> children;
bool isEndOfWord;
TrieNode() : isEndOfWord(false) {}
};
class Trie {
private:
TrieNode* root;
public:
Trie() {
root = new TrieNode();
}
void insert(const std::string& word) {
TrieNode* node = root;
for (char ch : word) {
if (node->children.find(ch) == node->children.end()) {
node->children[ch] = new TrieNode();
}
node = node->children[ch];
}
node->isEndOfWord = true;
}
int main() {
Trie trie;
trie.insert("hello");
trie.insert("world");
std::cout << "Search hello: " << (trie.search("hello") ? "Found" : "Not Found")
<< std::endl;
std::cout << "Search world: " << (trie.search("world") ? "Found
return 0;
}
```
```java
import java.util.HashMap;
class TrieNode {
HashMap<Character, TrieNode> children;
boolean isEndOfWord;
TrieNode() {
children = new HashMap<>();
isEndOfWord = false;
}
}
public Trie() {
root = new TrieNode();
}
---
Definition :
A binary tree is a hierarchical data structure in which each node has at most two
children, referred to as the left child and the right child. It is a special case of a
tree data structure with the following properties.
---
---
1. Minimum Height :
- Definition : The minimum height of a binary tree is computed for the most
compact (full) binary tree where nodes are filled from left to right.
- Formula : For a complete binary tree with \( n \) nodes, the minimum height \( h
\) can be computed as:
\[
h = \left\lfloor \log_2(n+1) \right\rfloor
\]
- Example : For a binary tree with 7 nodes:
\[
h = \left\lfloor \log_2(7+1) \right\rfloor = \left\lfloor \log_2(8) \right\rfloor = 3
\]
2. Maximum Height :
- Definition : The maximum height of a binary tree occurs in a degenerate (or
pathological) tree where each node has only one child, resembling a linked list.
- Formula : For a binary tree with \( n \) nodes, the maximum height \( h \) is:
\[
h=n-1
\]
- Example : For a binary tree with 7 nodes (in a degenerate form):
\[
h=7-1=6
\]
---
Code Examples :
```cpp
#include <iostream>
#include <algorithm> // For std::max
class TreeNode {
public:
int data;
TreeNode* left;
TreeNode* right;
TreeNode(int value) : data(value), left(nullptr), right(nullptr) {}
};
class BinaryTree {
private:
TreeNode* root;
public:
BinaryTree() : root(nullptr) {}
int getHeight() {
return height(root);
}
};
int main() {
BinaryTree tree;
tree.insert(10);
tree.insert(5);
tree.insert(15);
tree.insert(2);
tree.insert(7);
tree.insert(12);
tree.insert(20);
std::cout << "Height of the tree: " << tree.getHeight() << std::endl;
return 0;
}
```
2. Binary Tree Implementation (Java) :
```java
import java.util.*;
class TreeNode {
int data;
TreeNode left, right;
TreeNode(int value) {
data = value;
left = right = null;
}
}
BinaryTree() {
root = null;
}
int getHeight() {
return height(root);
}
---
Definition :
A Binary Search Tree (BST) is a type of binary tree where each node has at most
two children, and the nodes are arranged in such a way that for each node:
- The left child’s key is less than the parent’s key.
- The right child’s key is greater than the parent’s key.
This property makes BSTs useful for searching, insertion, and deletion operations
with average time complexities of O(log n) for balanced trees.
---
1. Node : Each node contains a key, a left child, and a right child.
2. Root : The topmost node of the tree is the root.
3. Left Subtree : All nodes in the left subtree of a node have keys less than the
node's key.
4. Right Subtree : All nodes in the right subtree of a node have keys greater than
the node's key.
5. Inorder Traversal : Inorder traversal of a BST yields keys in non-decreasing
order.
---
1. Minimum Height :
- Definition : The minimum height of a BST occurs in a perfectly balanced BST
where the number of nodes is optimal for the height.
- Formula : For a balanced BST with \( n \) nodes, the minimum height \( h \) is:
\[
h = \left\lfloor \log_2(n+1) \right\rfloor
\]
- Example : For a BST with 7 nodes:
\[
h = \left\lfloor \log_2(7+1) \right\rfloor = \left\lfloor \log_2(8) \right\rfloor = 3
\]
2. Maximum Height :
- Definition : The maximum height of a BST occurs in a degenerate (or
pathological) tree where each node has only one child, making it similar to a
linked list.
- Formula : For a BST with \( n \) nodes, the maximum height \( h \) is:
\[
h=n-1
\]
- Example : For a BST with 7 nodes (in a degenerate form):
\[
h=7-1=6
\]
---
Code Examples :
```cpp
#include <iostream>
#include <algorithm> // For std::max
class TreeNode {
public:
int data;
TreeNode* left;
TreeNode* right;
class BST {
private:
TreeNode* root;
public:
BST() : root(nullptr) {}
int getHeight() {
return height(root);
}
};
int main() {
BST tree;
tree.insert(10);
tree.insert(5);
tree.insert(15);
tree.insert(2);
tree.insert(7);
tree.insert(12);
tree.insert(20);
std::cout << "Height of the BST: " << tree.getHeight() << std::endl;
std::cout << "Search 15: " << (tree.search(15) ? "Found" : "Not Found") <<
std::endl;
std::cout << "Search 8: " << (tree.search(8) ? "Found" : "Not Found") <<
std::endl;
return 0;
}
```
```java
class TreeNode {
int data;
TreeNode left, right;
TreeNode(int value) {
data = value;
left = right = null;
}
}
BST() {
root = null;
}
int getHeight() {
return height(root);
}
---
Definition :
An AVL tree is a self-balancing binary search tree where the difference in height
between the left and right subtrees of any node (called the balance factor) is at
most one. Named after its inventors Adelson-Velsky and Landis, the AVL tree
ensures that the tree remains balanced, which provides logarithmic time
complexity for search, insert, and delete operations.
---
1. Balance Factor : For every node in the tree, the height difference between the
left and right subtrees (balance factor) is -1, 0, or 1.
2. Height : The height of an AVL tree with \( n \) nodes is \( O(\log n) \), ensuring
efficient operations.
3. Rotations : AVL trees use rotations to maintain balance after insertions and
deletions. There are four types of rotations:
- Right Rotation (RR)
- Left Rotation (LL)
- Left-Right Rotation (LR)
- Right-Left Rotation (RL)
---
Rotations :
1. Right Rotation (RR) :
- Scenario : Used when a node’s left subtree is taller than its right subtree.
- Example :
```
30
/
20
/
10
```
After right rotation:
```
20
/\
10 30
```
---
Code Examples :
```cpp
#include <iostream>
#include <algorithm>
class TreeNode {
public:
int data;
TreeNode* left;
TreeNode* right;
int height;
class AVLTree {
private:
TreeNode* root;
TreeNode* rightRotate(TreeNode* y) {
TreeNode* x = y->left;
TreeNode* T2 = x->right;
x->right = y;
y->left = T2;
y->height = 1 + std::max(height(y->left), height(y->right));
x->height = 1 + std::max(height(x->left), height(x->right));
return x;
}
TreeNode* leftRotate(TreeNode* x) {
TreeNode* y = x->right;
TreeNode* T2 = y->left;
y->left = x;
x->right = T2;
x->height = 1 + std::max(height(x->left), height(x->right));
y->height = 1 + std::max(height(y->left), height(y->right));
return y;
}
public:
AVLTree() : root(nullptr) {}
int main() {
AVLTree tree;
tree.insert(10);
tree.insert(20);
tree.insert(30);
tree.insert(40);
tree.insert(50);
tree.insert(25);
return 0;
}
```
```java
class TreeNode {
int data;
TreeNode left, right;
int height;
TreeNode(int value) {
data = value;
left = right = null;
height = 1;
}
}
TreeNode rightRotate(TreeNode y) {
TreeNode x = y.left;
TreeNode T2 = x.right;
x.right = y;
y.left = T2;
y.height = 1 + Math.max(height(y.left), height(y.right));
x.height = 1 + Math.max(height(x.left), height(x.right));
return x;
}
TreeNode leftRotate(TreeNode x) {
TreeNode y = x.right;
TreeNode T2 = y.left;
y.left = x;
x.right = T2;
x.height = 1 + Math.max(height(x.left), height(x.right));
y.height = 1 + Math.max(height(y.left), height(y.right));
return y;
}
void printInOrder() {
inOrder(root);
System.out.println();
}
---
Feel free to ask if you have more questions or need additional topics covered!