Fractal Sort is a distinctive sorting technique that implements the divide and conquer approach inspired by fractals. Despite its uniqueness, Fractal Sort is not as commonly utilized or discussed compared to popular algorithms like Quicksort or Merge Sort. Nonetheless, it stands out as a sorting method that divides an array and sorts its components repeatedly, following a fractal patterns algorithm. Similar to traditional sorting models, Fractal Sort involves partitioning and sorting an array into separate segments before merging them back together. What sets Fractal Sort apart is its approach of subdividing the array into smaller and more intricate parts, mirroring the self-replicating nature of fractals.
In every recursive invocation of the merge process, the array undergoes multiple divisions instead of just splitting down the middle, a characteristic distinct to the merge sort procedure. The merging phase initiates once the array is fragmented into smaller segments containing one or two elements each successively. This continual process of breaking down and reassembling continues until the final element is reached, at which point the elements are arranged in sorted order.
At every stage, the array is capable of being subdivided to the desired extent, with varying levels of intricacy in the recursive subdivisions. Fractal Sort, for example, has the ability to segment the array into quarters or other fractions, resulting in a sorting scheme that is significantly more intricate than mere halving, and relies on recursion to refine solutions. This approach typically exhibits a time complexity of O(n log n).
Fractal Sort is a fascinating theoretical investigation into non-comparative sorting methods. The unique recursive character of the algorithm is firmly grounded in its foundation on fractal mathematical patterns that display self-replication at various levels. Although Fractal Sort may not be practical for typical computing tasks, it showcases the benefits that can arise from employing recursive self-replicating approaches in computational challenges like sorting, which are most effectively tackled through divide and conquer methodologies.
Approach-1: Basic Recursive Fractal Sort (Divide and Conquer)
The fundamental iterative fractal Sort draws inspiration from the divide and conquer strategy, incorporating elements of recursion and self-replication found in fractals. Unlike Merge Sort or Quick Sort, where the array is split into two equal parts in each recursive iteration, Fractal Sort dissects the array into smaller, more complex segments. The recursive division persists until the subarrays become too minuscule to undergo individual sorting, at which point they are recombined into a unified, sorted array.
Program:
#include <iostream>
#include <vector>
// Helper function to merge two sorted arrays
std::vector<int> mergeTwoSortedArrays(const std::vector<int>& left, const std::vector<int>& right) {
std::vector<int> merged;
int i = 0, j = 0;
while (i < left.size() && j < right.size()) {
if (left[i] <= right[j]) {
merged.push_back(left[i]);
i++;
} else {
merged.push_back(right[j]);
j++;
}
}
// Append any remaining elements from both arrays
while (i < left.size()) {
merged.push_back(left[i]);
i++;
}
while (j < right.size()) {
merged.push_back(right[j]);
j++;
}
return merged;
}
//Function to merge four sorted parts of the array
std::vector<int> merge(std::vector<int>& arr, int left, int mid1, int mid2, int mid3, int right) {
// Creating vectors for the four segments
std::vector<int> leftSegment(arr.begin() + left, arr.begin() + mid1 + 1);
std::vector<int> mid1Segment(arr.begin() + mid1 + 1, arr.begin() + mid2 + 1);
std::vector<int> mid2Segment(arr.begin() + mid2 + 1, arr.begin() + mid3 + 1);
std::vector<int> rightSegment(arr.begin() + mid3 + 1, arr.begin() + right + 1);
// First, merge the left and mid1 segments
std::vector<int> mergedLeftMid1 = mergeTwoSortedArrays(leftSegment, mid1Segment);
// Then merge mid2 and right segments
std::vector<int> mergedMid2Right = mergeTwoSortedArrays(mid2Segment, rightSegment);
// Finally, merge the results of the two merges
return mergeTwoSortedArrays(mergedLeftMid1, mergedMid2Right);
}
//Function to recursively divide the array and sort each part
void fractalSort(std::vector<int>& arr, int left, int right) {
if (right - left < 1) {
return; // Base case: single element or invalid range
}
// Divide the array into four parts
int mid1 = left + (right - left) / 4;
int mid2 = left + (right - left) / 2;
int mid3 = left + 3 * (right - left) / 4;
// Recursively sort each part
fractalSort(arr, left, mid1);
fractalSort(arr, mid1 + 1, mid2);
fractalSort(arr, mid2 + 1, mid3);
fractalSort(arr, mid3 + 1, right);
// Merge the sorted parts back together
std::vector<int> merged = merge(arr, left, mid1, mid2, mid3, right);
// Copy the merged result back to the original array
for (int i = 0; i < merged.size(); ++i) {
arr[left + i] = merged[i];
}
}
int main() {
std::vector<int> arr = {38, 27, 43, 3, 9, 10, 12, 22, 67, 51};
int n = arr.size();
std::cout << "Original array: ";
for (int i = 0; i < n; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
fractalSort(arr, 0, n - 1);
std::cout << "Sorted array: ";
for (int i = 0; i < n; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
return 0;
}
Output:
Original array: 38 27 43 3 9 10 12 22 67 51
Sorted array: 3 9 10 12 22 27 38 43 51 67
Explanation:
While Merge Sort partitions the array into two segments, the Fundamental Recursive Fractal Sort employs a four-way segmentation, adopting a fractal-esque method to disassemble the structure into tinier components. This methodology aligns with the fractal notion of subdividing a structure into minute units. It recursively divides the array into these segments, arranges them separately, and subsequently consolidates them.
Recursive Division:
The array is segmented into four parts by utilizing three midpoints: mid1, mid2, and mid3. These midpoints specifically correspond to the initial quarter of the array and equivalent positions within the array.
Let's consider an illustration: if the array consists of 16 elements, the midpoints will split it into four parts with around 4 elements in each section. These individual parts serve as subproblems that are then recursively broken down even further. The recursive process halts when the array segments reach a small size (1 or 0 elements). At this point, the entire array is sorted on the call stack, and the recursion concludes.
Base Case:
Once the size of the subarray drops below two, the algorithm reaches the base case, halting any additional recursive invocations and transitioning to merging these already sorted partitions as the recursion unwinds. This serves as a termination point, preventing further recursion. The process of merging the partially sorted partitions begins as the recursion unfolds, triggered by the base case scenarios.
The procedure commences with small, organized fragments and progressively merges them to form larger, ordered sections. This incremental merging process combines the initially tiny sorted segments into more substantial sorted portions, ultimately assembling a completely sorted array by efficiently linking these optimally arranged elements.
Merging the Sections:
The next substep after the recursive division of each section results in a sorted section is to merge it into larger sorted arrays. Because the array was broken in half into four pieces, the merge becomes a more complex version than the two-piece merge present in traditional Merge Sort. The algorithm uses a three-stage merging process:
- It first merges the first quarter (mid1) with the leftmost section (left).
- After that, it takes the second quarter and merges it with the last section (right).
- It comes the results of the above two merges, which leads to a fully sorted section.
- The helper function mergeTwoSortedArrays helps us to perform all this by allowing us to compare two sorted arrays and merge them in a sorted manner. It continues until all four parts are combined into one sorted array.
Copying the Merged Array:
Afterward, the four segments are combined into one sorted array, and the merged outcome is inserted back into the initial array at their original positions. This guarantees that the latest sorting method applied to the array is retained in the original array following each recursive iteration. The process demonstrates a logical characteristic where the array progressively becomes sorted for larger segments as the recursion is resolved, eventually resulting in the complete sorting of the entire array by the end of the algorithm.
Complexity Analysis:
Time Complexity:
In the Basic Recursive Fractal Sort algorithm, recursion plays a key role in the process. This includes the division that takes place at every level, necessitating a split into four parts at each stage. Furthermore, there is a logarithmic recursion involved in the merging phase. Unlike Merge Sort, which splits the array into two parts at each recursive step, this method divides it into four parts. Although the branching factor increases, the overall depth of recursion remains logarithmic. To reach the base case where each segment of the array contains only one element, it requires 4 times the logarithm of the array size (log₄(n)).
The process of combining elements in each recursion level occurs in a linear time manner. Aggregating the subarrays into four sections also involves a linear merging process at each stage, resulting in an overall time complexity of O(nlogn) similar to merge sort. Despite its resemblance to Merge Sort, the distinctive aspect lies in the division into four parts, which affects the constant factors involved.
Space Complexity:
The reason for Fractal Sort's space complexity lies in the additional memory utilized during the merging phase. Temporary arrays are employed to store the four sections being merged at every level. These auxiliary arrays match the size of the original input array, requiring extra memory. Since merging occurs recursively across different levels, an additional O(n) space is essential to accommodate the intermediate arrays. Consequently, the total space complexity amounts to O(n).
Approach-2: In-Place Fractal Sort
In-place recursive sorting is an innovative sorting technique that aims to minimize additional memory allocation by merging elements directly within the existing array. In contrast to many traditional sorting methods that rely on separate arrays for merging, InPlace Fractal Sort rearranges elements within the original array itself to produce a sorted sequence. This strategy offers significant benefits in scenarios where memory constraints are a concern or optimizing space utilization is a priority.
It implements the fractal sorting idea where the array is divided into four segments recursively for sorting. Once the segments are sorted, they are merged back into the initial array using an in-place merging method. The in-place merging process requires precise management of indices for comparison and repositioning to ensure that the sorted order of elements is maintained without any disruption.
The In-Place Fractal Sort leverages the recursive partitioning and in-place combining technique to maintain a time complexity of O(n log n) and auxiliary space complexity of O(1), similar to other high-performing sorting algorithms. This distinct blend of methods positions it as a suitable choice for diverse scenarios and a reliable option for sorting operations that prioritize memory optimization.
Program:
#include <iostream>
#include <vector>
using namespace std;
//Function to perform in-place merge of two sorted segments
void inPlaceMerge(vector<int>& arr, int leftStart, int leftEnd, int rightStart, int rightEnd) {
int leftIndex = leftStart;
int rightIndex = rightStart;
// While both segments have elements
while (leftIndex <= leftEnd && rightIndex <= rightEnd) {
// If the left element is less than the right element, just move the lefcpp tutorialer
if (arr[leftIndex] <= arr[rightIndex]) {
leftIndex++;
} else {
// If the right element is smaller, move it to the left segment
int value = arr[rightIndex];
int index = rightIndex;
// Shift all elements of the left segment right by one position
while (index > leftIndex) {
arr[index] = arr[index - 1];
index--;
}
arr[leftIndex] = value;
// Update all pointers
leftIndex++;
rightIndex++;
leftEnd++;
}
}
}
//Function to recursively sort the segments
void inPlaceFractalSortUtil(vector<int>& arr, int left, int right) {
if (left < right) {
int mid1 = left + (right - left) / 4;
int mid2 = left + (right - left) / 2;
int mid3 = left + 3 * (right - left) / 4;
// Recursively sort the four segments
inPlaceFractalSortUtil(arr, left, mid1);
inPlaceFractalSortUtil(arr, mid1 + 1, mid2);
inPlaceFractalSortUtil(arr, mid2 + 1, mid3);
inPlaceFractalSortUtil(arr, mid3 + 1, right);
// Merge the segments in-place
inPlaceMerge(arr, left, mid1, mid1 + 1, mid2);
inPlaceMerge(arr, left, mid2, mid2 + 1, mid3);
inPlaceMerge(arr, left, mid3, mid3 + 1, right);
}
}
// Main function to be called to sort the array
void inPlaceFractalSort(vector<int>& arr) {
if (arr.empty()) return; // Handle empty array case
inPlaceFractalSortUtil(arr, 0, arr.size() - 1);
}
// Helper function to print the array
void printArray(const vector<int>& arr) {
for (int num : arr) {
cout << num << " ";
}
cout << endl;
}
// Example usage
int main() {
vector<int> arr = {38, 27, 43, 3, 9, 10, 12, 22, 67, 51};
cout << "Original array: ";
printArray(arr);
inPlaceFractalSort(arr);
cout << "Sorted array: ";
printArray(arr);
return 0;
}
Output:
Original array: 38 27 43 3 9 10 12 22 67 51
Sorted array: 3 9 10 12 22 27 38 43 51 67
Explanation:
In the execution of the In-Place Fractal Sort method, recursive partitioning and in-place merging are employed to efficiently organize an array with minimal space usage. This technique is especially beneficial in situations where conserving memory is crucial.
Core Functions
In-Place Merge: The objective of this procedure is to effectively merge two sorted sections of an array without requiring additional storage space. This optimization enhances both memory efficiency and performance. The merging operation involves two segments of the array: the first segment extends from left to right, while the second segment extends from right to right. The process proceeds by initializing two pointers: leftIndex and rightIndex, each corresponding to the left and right segments, respectively.
It goes through both sections and checks the items at these indexes. If the left section's value is less than or equal to the right one, the left section's index is increased. However, if the right element is smaller, it is inserted at the correct position in the left section. This is accomplished by shifting the left-side element to the right to make room for the right element.
Achieving O(1) auxiliary space complexity is crucial for maintaining the sorted order through in-place merging, eliminating the need for additional arrays.
The recursive partitioning of the array is managed by the inPlaceFractalSortUtil Function. It divides the initial array into four sections by determining three midpoints: mid1, mid2, and mid3. Subsequently, the function iteratively arranges each of these four sections until it reaches a point where each segment comprises just one or zero elements, signifying that further sorting is unnecessary.
After that, the Function combines all the segments again by utilizing the globally defined inPlaceMerge Function once all segments have been sorted recursively. This specific sequence ensures that all segments are merged correctly in a single sorted order.
Main Process: The sorting process is specified through the public interface called inPlaceFractalSort. Initially, it validates that the input array is not void. Upon confirmation, it invokes the recursive auxiliary function to initiate the sorting process. Additionally, the displayArrayProcess function serves as a tool to showcase the array elements both before and after the sorting operation for result verification.
Complexity Analysis:
Time Complexity:
In-situ fractal Sort operates with a time complexity of O(n log n), matching the efficiency of certain techniques like Merge Sort and Quick Sort.
The process of recursive division involves splitting the array into smaller segments of four repeatedly until each segment contains either one element or no elements. It is essential to verify that the array's size is zero before deciding to further divide it. This entire process maintains a logarithmic depth proportional to the array's size, denoted as log n.
Combining Segments: The sequence of a recursion tier is disordered, necessitating the merging of arranged segments at each tier. This merging procedure operates in linear time, denoted as O(n), as it involves comparing and reorganizing elements within the segments while merging.
The overall time complexity of this entire issue remains O(n log n) due to the fact that both stages are carried out across a logarithmic quantity of tiers (log n).
Space Complexity
In-Situ Fractal Sort boasts an auxiliary space complexity of O(1). This characteristic sets it apart:
- Unlike conventional merge sort that requires an extra array or data structure for merging, this algorithm executes the merge operation without the need for additional memory storage.
- The merging process occurs directly within the initial array, resulting in minimal memory consumption throughout all the operations.