Experiment -3
Bucket Sort and Other Problems
(1) Bucket Sort
Description:
Bucket Sort is a sorting algorithm that distributes elements into a number of buckets, sorts each bucket
individually (often using another sorting algorithm like insertion sort), and then concatenates the sorted
buckets. It is efficient when input values are uniformly distributed over a known range.
Algorithm Code (C++):
void bucket_sort(vector<float> &arr) {
int n = arr.size(); int main() {
vector<vector<float>> buckets(n); vector<float> arr = {0.42, 0.32, 0.23, 0.52,
for (int i = 0; i < n; i++) { 0.25, 0.47, 0.51};
int index = n * arr[i]; // Assuming values are bucket_sort(arr);
in the range [0,1)
buckets[index].push_back(arr[i]); for (float x : arr) {
} cout << x << " ";
for (int i = 0; i < n; i++) { }
sort(buckets[i].begin(), buckets[i].end()); return 0;
} }
int index = 0;
for (int i = 0; i < n; i++) { Output:
for (float value : buckets[i]) {
arr[index++] = value;
}
}
}
Analysis:
- Worst case: When all elements fall into a single bucket, causing sorting within that bucket to be inefficient.
- Best case: When elements are uniformly distributed across buckets, and each bucket has very few elements.
- Average case: O(n + k), where n is the number of elements and k is the number of buckets. If an efficient
sorting algorithm like insertion sort is used inside buckets, it can perform close to O(n) for small buckets.
Optimization:
- Adaptive Bucket Size: Choosing the right number of buckets dynamically instead of a fixed count can
improve efficiency.
- Efficient Sorting Inside Buckets: If the number of elements in each bucket is small, using Insertion Sort
(O(n²)) is optimal, while Quick Sort or Merge Sort can be used for larger buckets.
Complexity Analysis Table:
Case Time Complexity Space Complexity
Best Case O(n + k) O(n + k)
Average Case O(n + k) O(n + k)
Worst Case O(n²) (if all elements fall into one bucket) O(n + k)
(2) Dutch National Flag Algorithm
Description:
The Dutch National Flag Algorithm is a sorting algorithm that efficiently sorts an array containing three
distinct values, typically 0, 1, and 2. It works by maintaining three pointers (low, mid, and high) and swapping
elements to place them in the correct partition. This algorithm is commonly used to solve problems involving
three-way partitioning, such as sorting colors in an array where: 0 represents red, 1 represents white and 2
represents blue.
Algorithm Code (C++):
void dutch_national_flag(vector<int> &arr) { int main() {
int low = 0, mid = 0, high = arr.size() - 1; vector<int> arr = {2, 0, 2, 1, 1, 0};
while (mid <= high) { dutch_national_flag(arr);
if (arr[mid] == 0) {
swap(arr[low], arr[mid]); for (int x : arr) {
low++; cout << x << " ";
mid++; }
} else if (arr[mid] == 1) { return 0;
mid++; }
} else {
swap(arr[mid], arr[high]); Output:
high--;
}
}}
Analysis:
- Worst case: O(n) when all elements must be moved.
- Best case: O(n) since each element is processed at most once.
- Average case: O(n) as only a single pass is required.
Optimisation:
- Efficient Memory Usage: Operates in O(1) extra space since sorting is done in place.
- Linear Time Complexity: Unlike traditional sorting algorithms (O(n log n)), this runs in O(n), making it
optimal for sorting three distinct elements.
Complexity Analysis Table:
Case Time Complexity Space Complexity
Worst O(n) O(1)
Best O(n) O(1)
Average O(n) O(1)
(3)Merge Subintervals
Description:
The Merge Overlapping Intervals problem involves merging intervals that overlap into a single interval.
Given a list of intervals where each interval is represented as [start, end], the goal is to merge overlapping
intervals while maintaining non-overlapping intervals as they are.
The greedy approach ensures that we process intervals in sorted order, merging only when necessary, which
leads to an optimal O(n log n) time complexity due to sorting.
Algorithm Code (C++):
vector<vector<int>>merge_intervals(vector<vecto int main() {
r<int>> &intervals) { vector<vector<int>> intervals = {{1, 3}, {2, 6},
if (intervals.empty()) return {}; {8, 10}, {15, 18}};
sort(intervals.begin(), intervals.end()); vector<vector<int>> result =
vector<vector<int>> merged; merge_intervals(intervals);
merged.push_back(intervals[0]);
for (int i = 1; i < intervals.size(); i++) { for (auto interval : result) {
if (merged.back()[1] >= intervals[i][0]) { cout << "[" << interval[0] << ", " <<
merged.back()[1] = max(merged.back()[1], interval[1] << "] ";
intervals[i][1]); }
} else { return 0;
merged.push_back(intervals[i]);} }
} Output:
return merged;
}
Analysis:
- Worst case: O(n log n) (due to sorting)
- Best case: O(n) (if already sorted and non-overlapping)
- Average case: O(n log n) (sorting + linear traversal)
Optimisation:
1. Sorting in O(n log n): Sorting ensures intervals are processed in order.
2. Single Pass Merging: The merging step runs in O(n), making the approach efficient.
3. In-place Merging Possible: Can be optimized further by modifying the input list.
Complexity Analysis Table:
Case Time Complexity Space Complexity
Worst O(n) O(1) (if modifying input)
Best O(n log n) O(n) (new merged intervals)
Average O(n log n) O(n)