diff --git a/docs/algorithms/Recursive Algorithms/DirectRecursion.md b/docs/algorithms/Recursive Algorithms/DirectRecursion.md new file mode 100644 index 000000000..8a965f355 --- /dev/null +++ b/docs/algorithms/Recursive Algorithms/DirectRecursion.md @@ -0,0 +1,168 @@ +--- +id: direct-recursion-algo +sidebar_position: 1 +title: Direct Recursion +sidebar_label: Direct Recursion +--- + +### Definition: + +Direct recursion is when a function calls itself directly. The function keeps calling itself with a modified argument until a base case is met, which stops the recursion. Direct recursion is used to solve problems that can be broken down into smaller, similar subproblems. + +### Characteristics: + +- **Self-Calling Function:** + - A function is directly recursive if it calls itself within its own body. + +- **Base Case:** + - A base case is required to stop the recursive calls, preventing infinite recursion. + +- **Multiple Calls:** + - The function may call itself once or multiple times depending on the problem. + +### Time Complexity: + +- **Time Complexity: O(n)** + The time complexity of direct recursion depends on the problem. For simple problems like calculating a factorial, it usually takes linear time `O(n)` where `n` is the input size. + +### Space Complexity: + +- **Space Complexity: O(n)** + Each recursive call takes up space on the call stack, leading to a space complexity proportional to the depth of the recursion. + +### Example Problems: + +1. **Factorial Function:** + + The factorial of a number `n` is defined as `n! = n * (n-1) * (n-2) * ... * 1`. We can calculate the factorial using direct recursion. + + ```cpp + #include + using namespace std; + + int factorial(int n) { + if (n <= 1) return 1; // Base case + return n * factorial(n - 1); // Recursive call + } + + int main() { + int num = 5; + cout << "Factorial of " << num << " is " << factorial(num) << endl; + return 0; + } + ``` + + - In this example, the function `factorial()` calls itself until `n` becomes 1, which is the base case. + +2. **Sum of First N Natural Numbers:** + + We can use direct recursion to find the sum of the first `n` natural numbers. + + ```cpp + #include + using namespace std; + + int sum(int n) { + if (n == 0) return 0; // Base case + return n + sum(n - 1); // Recursive call + } + + int main() { + int n = 10; + cout << "Sum of first " << n << " natural numbers is " << sum(n) << endl; + return 0; + } + ``` + + - In this example, the function `sum()` calls itself, reducing the value of `n` by 1 at each step until it reaches 0, the base case. + +3. **Fibonacci Sequence:** + + The Fibonacci sequence is a series where each number is the sum of the two preceding ones, starting with 0 and 1. We can compute the nth Fibonacci number using direct recursion. + + ```cpp + #include + using namespace std; + + int fibonacci(int n) { + if (n <= 1) return n; // Base cases + return fibonacci(n - 1) + fibonacci(n - 2); // Recursive calls + } + + int main() { + int n = 5; + cout << "Fibonacci of " << n << " is " << fibonacci(n) << endl; + return 0; + } + ``` + + - Here, the function `fibonacci()` calls itself twice to compute the Fibonacci numbers for `n-1` and `n-2`. + +### Recursive Tree: + +Direct recursion often generates a recursion tree, where each function call spawns new calls, visualizing the branching structure of recursive calls. + +### Common Applications: + +- Factorial calculation +- Fibonacci sequence +- Tower of Hanoi +- Sum of a series +- Tree traversal + +### C++ Implementation: + +**Factorial Example (Direct Recursion)** +```cpp +#include +using namespace std; + +int factorial(int n) { + if (n <= 1) return 1; // Base case + return n * factorial(n - 1); // Recursive call +} + +int main() { + int num = 5; + cout << "Factorial of " << num << " is " << factorial(num) << endl; + return 0; +} +``` + +**Sum of First N Natural Numbers (Direct Recursion)** +```cpp +#include +using namespace std; + +int sum(int n) { + if (n == 0) return 0; // Base case + return n + sum(n - 1); // Recursive call +} + +int main() { + int n = 10; + cout << "Sum of first " << n << " natural numbers is " << sum(n) << endl; + return 0; +} +``` + +**Fibonacci Example (Direct Recursion)** +```cpp +#include +using namespace std; + +int fibonacci(int n) { + if (n <= 1) return n; // Base case + return fibonacci(n - 1) + fibonacci(n - 2); // Recursive call +} + +int main() { + int n = 5; + cout << "Fibonacci of " << n << " is " << fibonacci(n) << endl; + return 0; +} +``` + +### Summary: + +Direct recursion is a simple yet powerful tool for solving problems that involve repetitive or self-similar subproblems. The key challenge lies in identifying the base case to prevent infinite recursion and understanding the problem well enough to break it down recursively. diff --git a/docs/algorithms/Recursive Algorithms/IndirectRecursion.md b/docs/algorithms/Recursive Algorithms/IndirectRecursion.md new file mode 100644 index 000000000..9c1b4511c --- /dev/null +++ b/docs/algorithms/Recursive Algorithms/IndirectRecursion.md @@ -0,0 +1,184 @@ +--- +id: indirect-recursion-algo +sidebar_position: 2 +title: Indirect Recursion +sidebar_label: Indirect Recursion +--- + +### Definition: + +Indirect recursion occurs when a function calls another function, and eventually, the second function calls the first function back. This process forms a cycle of function calls between two or more functions, instead of a single function calling itself directly. + +### Characteristics: + +- **Mutual Call:** + - In indirect recursion, two or more functions call each other in a cyclic manner. + +- **Base Case:** + - Each recursive cycle must include a base case to prevent infinite loops and ensure termination. + +- **Multiple Functions:** + - Indirect recursion involves more than one function. A function calls another, which in turn calls the first one back. + +### Time Complexity: + +- **Time Complexity: O(n)** + - The time complexity of indirect recursion depends on the number of recursive calls. For simple problems, it is typically linear `O(n)`. + +### Space Complexity: + +- **Space Complexity: O(n)** + - Each recursive call uses stack space, leading to a space complexity proportional to the depth of the recursion. + +### Example Problems: + +1. **Odd and Even Numbers:** + + In this example, we use two functions: one for checking even numbers and another for odd numbers. The function `isEven()` calls `isOdd()`, and `isOdd()` calls `isEven()` until the base case is reached. + + ```cpp + #include + using namespace std; + + bool isEven(int n); + bool isOdd(int n); + + bool isEven(int n) { + if (n == 0) return true; // Base case + return isOdd(n - 1); // Call isOdd + } + + bool isOdd(int n) { + if (n == 0) return false; // Base case + return isEven(n - 1); // Call isEven + } + + int main() { + int num = 5; + if (isEven(num)) { + cout << num << " is even." << endl; + } else { + cout << num << " is odd." << endl; + } + return 0; + } + ``` + + - Here, `isEven()` calls `isOdd()`, and `isOdd()` calls `isEven()`. This continues until the base case is met. + +2. **Mutually Recursive Functions:** + + Another classic example of indirect recursion is mutually recursive functions, where one function calls another, and the second function calls the first function back in a loop. + + ```cpp + #include + using namespace std; + + void functionA(int n); + void functionB(int n); + + void functionA(int n) { + if (n > 0) { + cout << "In Function A: " << n << endl; + functionB(n - 1); // Call functionB + } + } + + void functionB(int n) { + if (n > 0) { + cout << "In Function B: " << n << endl; + functionA(n / 2); // Call functionA + } + } + + int main() { + int num = 10; + functionA(num); // Start the recursion chain + return 0; + } + ``` + + - In this example, `functionA()` calls `functionB()`, and `functionB()` calls `functionA()`. The base case for each function ensures termination. + +### Recursive Tree: + +Just like direct recursion, indirect recursion forms a recursion tree. However, the branches of this tree involve multiple functions that call each other in cycles. Each function call is part of a chain, leading back to earlier calls. + +### Applications: + +Indirect recursion is often used in: +- State machines +- Parsing algorithms +- Handling mutually exclusive conditions (like odd/even checks) + +### Common Challenges: + +- **Tracking Function Calls:** + - Indirect recursion can be more challenging to understand and debug because multiple functions are involved, and keeping track of the flow of execution can be complex. + +- **Base Case Design:** + - Designing appropriate base cases for each function in the recursion cycle is crucial to ensure that the recursion terminates. + +### C++ Implementation: + +**Odd and Even Checker (Indirect Recursion)** +```cpp +#include +using namespace std; + +bool isEven(int n); +bool isOdd(int n); + +bool isEven(int n) { + if (n == 0) return true; // Base case + return isOdd(n - 1); // Call isOdd +} + +bool isOdd(int n) { + if (n == 0) return false; // Base case + return isEven(n - 1); // Call isEven +} + +int main() { + int num = 5; + if (isEven(num)) { + cout << num << " is even." << endl; + } else { + cout << num << " is odd." << endl; + } + return 0; +} +``` + +**Mutually Recursive Functions (Indirect Recursion)** +```cpp +#include +using namespace std; + +void functionA(int n); +void functionB(int n); + +void functionA(int n) { + if (n > 0) { + cout << "In Function A: " << n << endl; + functionB(n - 1); // Call functionB + } +} + +void functionB(int n) { + if (n > 0) { + cout << "In Function B: " << n << endl; + functionA(n / 2); // Call functionA + } +} + +int main() { + int num = 10; + functionA(num); // Start the recursion chain + return 0; +} +``` + +### Summary: + +Indirect recursion occurs when two or more functions call each other in a cycle. It's an important concept in recursive algorithms, but it can be harder to follow due to the involvement of multiple functions. The base cases for each function in the cycle must be carefully designed to ensure the recursion terminates. diff --git a/docs/algorithms/Sorting Algorithms/BubbleSort.md b/docs/algorithms/Sorting Algorithms/BubbleSort.md new file mode 100644 index 000000000..ad7ba8703 --- /dev/null +++ b/docs/algorithms/Sorting Algorithms/BubbleSort.md @@ -0,0 +1,129 @@ +--- + +id: bubble-sort-algo +sidebar_position: 1 +title: Bubble Sort +sidebar_label: Bubble Sort + +--- + +### Definition: + +Bubble sort is a simple comparison-based sorting algorithm that repeatedly steps through the list, compares adjacent elements, and swaps them if they are in the wrong order. This process continues until the list is sorted. + +### Characteristics: + +- **Repeated Comparison**: + - The algorithm compares each pair of adjacent elements and swaps them if they are out of order. It continues until no more swaps are needed. + +- **In-Place Sorting**: + - Bubble sort operates directly on the input array and does not require extra space, making it an in-place sorting algorithm. + +- **Stable**: + - Since the algorithm only swaps elements when necessary, the relative order of elements with equal values remains the same, making bubble sort a stable algorithm. + +- **Simple Implementation**: + - Bubble sort is easy to implement but not very efficient for large datasets. It's mainly used for educational purposes. + +### Time Complexity: + +- **Best Case: O(n)** + In the best-case scenario, where the array is already sorted, bubble sort only makes a single pass through the array without making any swaps. + +- **Average Case: O(n²)** + On average, bubble sort compares and swaps elements multiple times, leading to quadratic time complexity. + +- **Worst Case: O(n²)** + In the worst-case scenario, where the array is sorted in reverse order, the algorithm must compare and swap every element, making n-1 comparisons and swaps in each pass. + +### Space Complexity: + +- **Space Complexity: O(1)** + Bubble sort is an in-place algorithm, meaning it requires only a constant amount of extra memory, regardless of the input size. + +### C++ Implementation: + +**Iterative Approach** +```cpp +#include +using namespace std; + +void bubbleSortIterative(int arr[], int size) { + for (int i = 0; i < size - 1; i++) { + bool swapped = false; // To check if any swap happened in the current iteration + for (int j = 0; j < size - i - 1; j++) { + if (arr[j] > arr[j + 1]) { + // Swap arr[j] and arr[j + 1] + int temp = arr[j]; + arr[j] = arr[j + 1]; + arr[j + 1] = temp; + swapped = true; + } + } + // If no swap occurred, the array is already sorted + if (!swapped) { + break; + } + } +} + +int main() { + int arr[] = {64, 34, 25, 12, 22, 11, 90}; + int size = sizeof(arr) / sizeof(arr[0]); + + bubbleSortIterative(arr, size); + + cout << "Sorted array: \n"; + for (int i = 0; i < size; i++) { + cout << arr[i] << " "; + } + cout << endl; + + return 0; +} +``` + +**Recursive Approach** +```cpp +#include +using namespace std; + +void bubbleSortRecursive(int arr[], int size) { + // Base case: If the array is of size 1, it's already sorted + if (size == 1) { + return; + } + + // Perform one pass of bubble sort + for (int i = 0; i < size - 1; i++) { + if (arr[i] > arr[i + 1]) { + // Swap arr[i] and arr[i + 1] + int temp = arr[i]; + arr[i] = arr[i + 1]; + arr[i + 1] = temp; + } + } + + // Recursively sort the remaining array + bubbleSortRecursive(arr, size - 1); +} + +int main() { + int arr[] = {64, 34, 25, 12, 22, 11, 90}; + int size = sizeof(arr) / sizeof(arr[0]); + + bubbleSortRecursive(arr, size); + + cout << "Sorted array: \n"; + for (int i = 0; i < size; i++) { + cout << arr[i] << " "; + } + cout << endl; + + return 0; +} +``` + +### Summary: + +Bubble sort is one of the simplest sorting algorithms. While it is inefficient for large datasets due to its O(n²) time complexity, it provides an easy-to-understand introduction to sorting. Both the iterative and recursive versions of bubble sort are straightforward to implement, with the iterative version being more commonly used due to its simplicity. diff --git a/docs/algorithms/Sorting Algorithms/InsertionSort.md b/docs/algorithms/Sorting Algorithms/InsertionSort.md new file mode 100644 index 000000000..fe986c8f1 --- /dev/null +++ b/docs/algorithms/Sorting Algorithms/InsertionSort.md @@ -0,0 +1,127 @@ +--- + +id: insertion-sort-algo +sidebar_position: 3 +title: Insertion Sort +sidebar_label: Insertion Sort + +--- + +### Definition: + +Insertion sort is a simple and efficient comparison-based sorting algorithm that builds the final sorted array one element at a time. It works similarly to how people sort playing cards in their hands. The algorithm starts with one element and repeatedly inserts the next element into its correct position within the already sorted portion of the array. + +### Characteristics: + +- **In-Place Sorting**: + - Insertion sort sorts the array in place, meaning it does not require extra memory for another array, making it an in-place algorithm. + +- **Stable**: + - It maintains the relative order of elements with equal values, making it a stable sorting algorithm. + +- **Efficient for Small Datasets**: + - Insertion sort is efficient for small datasets or nearly sorted datasets. It performs well in scenarios where the list is already partially sorted. + +- **Online Algorithm**: + - Insertion sort is an online algorithm, meaning it can sort a list as it receives elements one by one. + +### Time Complexity: + +- **Best Case: O(n)** + The best case occurs when the array is already sorted, so the algorithm only needs to iterate through the list once without making any swaps or shifts. + +- **Average Case: O(n²)** + On average, insertion sort must make n/2 comparisons and shifts for each element, leading to a quadratic time complexity. + +- **Worst Case: O(n²)** + The worst-case scenario occurs when the array is sorted in reverse order, requiring the algorithm to make the maximum number of comparisons and shifts. + +### Space Complexity: + +- **Space Complexity: O(1)** + Insertion sort is an in-place algorithm that requires a constant amount of extra memory for variables such as the current element being inserted. + +### C++ Implementation: + +**Iterative Approach** +```cpp +#include +using namespace std; + +void insertionSortIterative(int arr[], int size) { + for (int i = 1; i < size; i++) { + int key = arr[i]; + int j = i - 1; + + // Move elements of arr[0..i-1] that are greater than key + // to one position ahead of their current position + while (j >= 0 && arr[j] > key) { + arr[j + 1] = arr[j]; + j--; + } + arr[j + 1] = key; // Insert the key into its correct position + } +} + +int main() { + int arr[] = {12, 11, 13, 5, 6}; + int size = sizeof(arr) / sizeof(arr[0]); + + insertionSortIterative(arr, size); + + cout << "Sorted array: \n"; + for (int i = 0; i < size; i++) { + cout << arr[i] << " "; + } + cout << endl; + + return 0; +} +``` + +**Recursive Approach** +```cpp +#include +using namespace std; + +void insertionSortRecursive(int arr[], int size) { + // Base case: If the array is of size 1, it's already sorted + if (size <= 1) { + return; + } + + // Sort the first n-1 elements + insertionSortRecursive(arr, size - 1); + + // Insert the last element at its correct position + int key = arr[size - 1]; + int j = size - 2; + + // Move elements of arr[0..size-2] that are greater than key + // to one position ahead of their current position + while (j >= 0 && arr[j] > key) { + arr[j + 1] = arr[j]; + j--; + } + arr[j + 1] = key; // Insert the key into its correct position +} + +int main() { + int arr[] = {12, 11, 13, 5, 6}; + int size = sizeof(arr) / sizeof(arr[0]); + + insertionSortRecursive(arr, size); + + cout << "Sorted array: \n"; + for (int i = 0; i < size; i++) { + cout << arr[i] << " "; + } + cout << endl; + + return 0; +} +``` + +### Summary: + +Insertion sort is a simple yet efficient algorithm for small or nearly sorted datasets. It works by building a sorted array one element at a time, inserting each element into its correct position. Although it has a quadratic time complexity for average and worst-case scenarios, its linear time complexity in the best case makes it advantageous for sorting small datasets or datasets that are already nearly sorted. The iterative version is more common and easier to understand, while the recursive version provides a more elegant solution at the cost of slightly more overhead. diff --git a/docs/algorithms/Sorting Algorithms/MergeSort.md b/docs/algorithms/Sorting Algorithms/MergeSort.md new file mode 100644 index 000000000..e5bc730a7 --- /dev/null +++ b/docs/algorithms/Sorting Algorithms/MergeSort.md @@ -0,0 +1,199 @@ +--- + +id: merge-sort-algo +sidebar_position: 4 +title: Merge Sort +sidebar_label: Merge Sort + +--- + +### Definition: + +Merge sort is a **divide-and-conquer** sorting algorithm that splits the array into smaller subarrays, sorts them, and then merges them back together in the correct order. It is a stable and comparison-based algorithm, which ensures that equal elements maintain their original relative positions. + +### Characteristics: + +- **Divide and Conquer**: + - Merge sort works by dividing the array into two halves, sorting each half, and then merging them back together in sorted order. This process is repeated recursively. + +- **Stable**: + - Merge sort is a stable sorting algorithm, meaning it maintains the relative order of equal elements. + +- **External Sorting**: + - Merge sort can be used to sort large datasets that do not fit into memory (external sorting) because it works well with data on disk or in streams. + +- **Requires Additional Space**: + - Unlike in-place sorting algorithms, merge sort requires additional memory to store the subarrays during the merging process. + +### Time Complexity: + +- **Best Case: O(n log n)** + Even in the best case (when the array is already sorted), merge sort divides the array into smaller parts and performs a merging process, resulting in a time complexity of O(n log n). + +- **Average Case: O(n log n)** + On average, merge sort consistently divides the array and performs merging, leading to a time complexity of O(n log n). + +- **Worst Case: O(n log n)** + In the worst case (reverse-sorted array), merge sort must still divide and merge the array in logarithmic time with n comparisons. + +### Space Complexity: + +- **Space Complexity: O(n)** + Merge sort requires additional space to store the temporary subarrays created during the merging process. This leads to a space complexity of O(n) due to the auxiliary arrays used for merging. + +### C++ Implementation: + +**Iterative (Bottom-Up) Approach** +```cpp +#include +using namespace std; + +// Merge function to merge two halves +void merge(int arr[], int left, int mid, int right) { + int n1 = mid - left + 1; + int n2 = right - mid; + + // Create temporary arrays + int L[n1], R[n2]; + + // Copy data to temporary arrays + for (int i = 0; i < n1; i++) + L[i] = arr[left + i]; + for (int j = 0; j < n2; j++) + R[j] = arr[mid + 1 + j]; + + // Merge the temporary arrays back into arr[left..right] + int i = 0, j = 0, k = left; + while (i < n1 && j < n2) { + if (L[i] <= R[j]) { + arr[k] = L[i]; + i++; + } else { + arr[k] = R[j]; + j++; + } + k++; + } + + // Copy the remaining elements of L[], if any + while (i < n1) { + arr[k] = L[i]; + i++; + k++; + } + + // Copy the remaining elements of R[], if any + while (j < n2) { + arr[k] = R[j]; + j++; + k++; + } +} + +// Iterative merge sort function +void mergeSortIterative(int arr[], int size) { + for (int curr_size = 1; curr_size <= size - 1; curr_size = 2 * curr_size) { + for (int left = 0; left < size - 1; left += 2 * curr_size) { + int mid = min(left + curr_size - 1, size - 1); + int right = min(left + 2 * curr_size - 1, size - 1); + merge(arr, left, mid, right); + } + } +} + +int main() { + int arr[] = {12, 11, 13, 5, 6, 7}; + int size = sizeof(arr) / sizeof(arr[0]); + + mergeSortIterative(arr, size); + + cout << "Sorted array: \n"; + for (int i = 0; i < size; i++) { + cout << arr[i] << " "; + } + cout << endl; + + return 0; +} +``` + +**Recursive Approach** +```cpp +#include +using namespace std; + +// Merge function to merge two halves +void merge(int arr[], int left, int mid, int right) { + int n1 = mid - left + 1; + int n2 = right - mid; + + // Create temporary arrays + int L[n1], R[n2]; + + // Copy data to temporary arrays + for (int i = 0; i < n1; i++) + L[i] = arr[left + i]; + for (int j = 0; j < n2; j++) + R[j] = arr[mid + 1 + j]; + + // Merge the temporary arrays back into arr[left..right] + int i = 0, j = 0, k = left; + while (i < n1 && j < n2) { + if (L[i] <= R[j]) { + arr[k] = L[i]; + i++; + } else { + arr[k] = R[j]; + j++; + } + k++; + } + + // Copy the remaining elements of L[], if any + while (i < n1) { + arr[k] = L[i]; + i++; + k++; + } + + // Copy the remaining elements of R[], if any + while (j < n2) { + arr[k] = R[j]; + j++; + k++; + } +} + +// Recursive merge sort function +void mergeSortRecursive(int arr[], int left, int right) { + if (left < right) { + int mid = left + (right - left) / 2; + + // Sort first and second halves + mergeSortRecursive(arr, left, mid); + mergeSortRecursive(arr, mid + 1, right); + + // Merge the sorted halves + merge(arr, left, mid, right); + } +} + +int main() { + int arr[] = {12, 11, 13, 5, 6, 7}; + int size = sizeof(arr) / sizeof(arr[0]); + + mergeSortRecursive(arr, 0, size - 1); + + cout << "Sorted array: \n"; + for (int i = 0; i < size; i++) { + cout << arr[i] << " "; + } + cout << endl; + + return 0; +} +``` + +### Summary: + +Merge sort is an efficient and reliable sorting algorithm, particularly for large datasets. It works by recursively dividing the array into smaller subarrays and merging them in the correct order. Despite its additional space complexity, its consistent time complexity of O(n log n) makes it a popular choice for sorting algorithms. The recursive approach is more intuitive, but the iterative version can be useful in cases where recursion depth could be a concern. diff --git a/docs/algorithms/Sorting Algorithms/QuickSort.md b/docs/algorithms/Sorting Algorithms/QuickSort.md new file mode 100644 index 000000000..193e35d5c --- /dev/null +++ b/docs/algorithms/Sorting Algorithms/QuickSort.md @@ -0,0 +1,161 @@ +--- + +id: quick-sort-algo +sidebar_position: 5 +title: Quick Sort +sidebar_label: Quick Sort + +--- + +### Definition: + +Quick sort is a **divide-and-conquer** sorting algorithm that works by selecting a 'pivot' element from the array and partitioning the other elements into two subarrays, according to whether they are less than or greater than the pivot. The subarrays are then recursively sorted. This process of partitioning and sorting leads to a highly efficient sorting algorithm. + +### Characteristics: + +- **Divide and Conquer**: + - Quick sort partitions the array into smaller subarrays and sorts them independently before merging the results back together. + +- **In-Place Sorting**: + - It sorts the array in place and typically requires little additional memory, only for recursive calls. + +- **Unstable**: + - Quick sort is an unstable algorithm, meaning it does not guarantee that the relative order of equal elements will be maintained. + +- **Efficient for Large Datasets**: + - Quick sort is one of the fastest sorting algorithms in practice, especially for large datasets, and has excellent cache performance. + +### Time Complexity: + +- **Best Case: O(n log n)** + In the best case, the pivot divides the array into two nearly equal subarrays, leading to a logarithmic number of comparisons across each recursive call. + +- **Average Case: O(n log n)** + On average, quick sort partitions the array into balanced subarrays, leading to an O(n log n) time complexity. + +- **Worst Case: O(n²)** + The worst-case scenario occurs when the pivot chosen is consistently the smallest or largest element, leading to unbalanced partitions and quadratic time complexity. This can be mitigated by using strategies like randomized pivots or choosing the median of three elements as the pivot. + +### Space Complexity: + +- **Space Complexity: O(log n)** + Quick sort requires O(log n) space for recursive calls when the partitioning is balanced. In the worst case (highly unbalanced partitioning), it requires O(n) space for recursion. + +### C++ Implementation: + +**Iterative Approach** +```cpp +#include +#include +using namespace std; + +// Partition function to place the pivot element in the correct position +int partition(int arr[], int low, int high) { + int pivot = arr[high]; // Pivot is taken as the last element + int i = (low - 1); // Index of the smaller element + + for (int j = low; j < high; j++) { + if (arr[j] <= pivot) { + i++; // Increment index of smaller element + swap(arr[i], arr[j]); // Swap current element with the smaller element + } + } + swap(arr[i + 1], arr[high]); // Place the pivot element in the correct position + return (i + 1); +} + +// Iterative quick sort function +void quickSortIterative(int arr[], int low, int high) { + stack s; + s.push(low); + s.push(high); + + // Keep popping elements until stack is empty + while (!s.empty()) { + high = s.top(); + s.pop(); + low = s.top(); + s.pop(); + + int p = partition(arr, low, high); + + // If there are elements on the left of the pivot, push them onto the stack + if (p - 1 > low) { + s.push(low); + s.push(p - 1); + } + + // If there are elements on the right of the pivot, push them onto the stack + if (p + 1 < high) { + s.push(p + 1); + s.push(high); + } + } +} + +int main() { + int arr[] = {10, 7, 8, 9, 1, 5}; + int size = sizeof(arr) / sizeof(arr[0]); + + quickSortIterative(arr, 0, size - 1); + + cout << "Sorted array: \n"; + for (int i = 0; i < size; i++) { + cout << arr[i] << " "; + } + cout << endl; + + return 0; +} +``` + +**Recursive Approach** +```cpp +#include +using namespace std; + +// Partition function to place the pivot element in the correct position +int partition(int arr[], int low, int high) { + int pivot = arr[high]; // Pivot is taken as the last element + int i = (low - 1); // Index of the smaller element + + for (int j = low; j < high; j++) { + if (arr[j] <= pivot) { + i++; // Increment index of smaller element + swap(arr[i], arr[j]); // Swap current element with the smaller element + } + } + swap(arr[i + 1], arr[high]); // Place the pivot element in the correct position + return (i + 1); +} + +// Recursive quick sort function +void quickSortRecursive(int arr[], int low, int high) { + if (low < high) { + int pi = partition(arr, low, high); + + // Recursively sort elements before and after partition + quickSortRecursive(arr, low, pi - 1); + quickSortRecursive(arr, pi + 1, high); + } +} + +int main() { + int arr[] = {10, 7, 8, 9, 1, 5}; + int size = sizeof(arr) / sizeof(arr[0]); + + quickSortRecursive(arr, 0, size - 1); + + cout << "Sorted array: \n"; + for (int i = 0; i < size; i++) { + cout << arr[i] << " "; + } + cout << endl; + + return 0; +} +``` + +### Summary: + +Quick sort is a highly efficient and widely used sorting algorithm that works well for large datasets. It employs the divide-and-conquer approach, partitioning the array around a pivot and sorting the subarrays recursively. Although its worst-case time complexity is O(n²), this can often be avoided by choosing an appropriate pivot (like the median of three). In practice, quick sort is often faster than other O(n log n) algorithms like merge sort due to its in-place sorting nature and better cache performance. diff --git a/docs/algorithms/Sorting Algorithms/SelectionSort.md b/docs/algorithms/Sorting Algorithms/SelectionSort.md new file mode 100644 index 000000000..bc09c5228 --- /dev/null +++ b/docs/algorithms/Sorting Algorithms/SelectionSort.md @@ -0,0 +1,136 @@ +--- + +id: selection-sort-algo +sidebar_position: 2 +title: Selection Sort +sidebar_label: Selection Sort + +--- + +### Definition: + +Selection sort is a simple comparison-based sorting algorithm that repeatedly selects the smallest (or largest) element from the unsorted portion of the array and swaps it with the first unsorted element. It works by dividing the array into a sorted and an unsorted region and systematically reducing the size of the unsorted region. + +### Characteristics: + +- **In-Place Sorting**: + - Selection sort operates directly on the input array and does not require additional storage, making it an in-place sorting algorithm. + +- **Not Stable**: + - Selection sort is not a stable sorting algorithm because equal elements can be swapped, potentially changing their relative order. + +- **Selection Process**: + - In each pass, the algorithm selects the smallest element from the unsorted part of the array and places it in the correct position by swapping it with the first element of the unsorted part. + +- **Inefficient for Large Datasets**: + - Although the algorithm is simple, it is not efficient for large datasets as it requires many comparisons and swaps. + +### Time Complexity: + +- **Best Case: O(n²)** + Even in the best case where the array is already sorted, the algorithm must make n²/2 comparisons because it systematically searches for the smallest element in the unsorted portion. + +- **Average Case: O(n²)** + On average, the algorithm must make n²/2 comparisons and n swaps. This quadratic time complexity makes selection sort inefficient for large datasets. + +- **Worst Case: O(n²)** + In the worst-case scenario (reverse sorted array), selection sort must still make the same number of comparisons as in the average case. + +### Space Complexity: + +- **Space Complexity: O(1)** + Selection sort is an in-place algorithm and requires only a constant amount of extra memory for swapping elements. + +### C++ Implementation: + +**Iterative Approach** +```cpp +#include +using namespace std; + +void selectionSortIterative(int arr[], int size) { + for (int i = 0; i < size - 1; i++) { + int minIndex = i; // Assume the current element is the minimum + + // Find the minimum element in the remaining unsorted array + for (int j = i + 1; j < size; j++) { + if (arr[j] < arr[minIndex]) { + minIndex = j; // Update the index of the minimum element + } + } + + // Swap the found minimum element with the first unsorted element + if (minIndex != i) { + int temp = arr[i]; + arr[i] = arr[minIndex]; + arr[minIndex] = temp; + } + } +} + +int main() { + int arr[] = {64, 25, 12, 22, 11}; + int size = sizeof(arr) / sizeof(arr[0]); + + selectionSortIterative(arr, size); + + cout << "Sorted array: \n"; + for (int i = 0; i < size; i++) { + cout << arr[i] << " "; + } + cout << endl; + + return 0; +} +``` + +**Recursive Approach** +```cpp +#include +using namespace std; + +void selectionSortRecursive(int arr[], int size, int index = 0) { + // Base case: If the array is completely sorted + if (index == size - 1) { + return; + } + + int minIndex = index; + + // Find the minimum element in the remaining unsorted array + for (int j = index + 1; j < size; j++) { + if (arr[j] < arr[minIndex]) { + minIndex = j; + } + } + + // Swap the found minimum element with the first unsorted element + if (minIndex != index) { + int temp = arr[index]; + arr[index] = arr[minIndex]; + arr[minIndex] = temp; + } + + // Recursively sort the rest of the array + selectionSortRecursive(arr, size, index + 1); +} + +int main() { + int arr[] = {64, 25, 12, 22, 11}; + int size = sizeof(arr) / sizeof(arr[0]); + + selectionSortRecursive(arr, size); + + cout << "Sorted array: \n"; + for (int i = 0; i < size; i++) { + cout << arr[i] << " "; + } + cout << endl; + + return 0; +} +``` + +### Summary: + +Selection sort is a simple, easy-to-understand algorithm that can be useful for small datasets or when memory space is limited. However, its quadratic time complexity makes it inefficient for large datasets. The algorithm iteratively selects the minimum element from the unsorted portion of the array and swaps it into place. The iterative and recursive implementations both perform the same function, with the recursive version offering a cleaner, although slightly more complex, approach.