Divide & Conquer Sorting: Merge & Quick
Divide and Conquer — Merge ও Quick
1. The Divide & Conquer Idea
Divide & Conquer is one of the most beautiful ideas in algorithms: take a problem of size n, split it into two halves of size n/2, solve each half recursively, then combine. Both merge sort and quick sort follow this template — but split the work differently.
Merge sort-এর n log n আসে recurrence থেকে; quick sort-এর gain আসে cache locality থেকে।
2. Merge Sort — Split, Sort, Merge
Recursively split the array in half until each piece has length 1. Then merge pairs back, comparing front elements. Merging two sorted arrays of length n/2 takes O(n). We do log₂ n levels of merging, so total work is O(n log n).
#include <bits/stdc++.h>
using namespace std;
void merge(vector<int>& a, int l, int m, int r) {
vector<int> left(a.begin()+l, a.begin()+m+1);
vector<int> right(a.begin()+m+1, a.begin()+r+1);
int i = 0, j = 0, k = l;
while (i < (int)left.size() && j < (int)right.size()) {
if (left[i] <= right[j]) a[k++] = left[i++];
else a[k++] = right[j++];
}
while (i < (int)left.size()) a[k++] = left[i++];
while (j < (int)right.size()) a[k++] = right[j++];
}
void mergeSort(vector<int>& a, int l, int r) {
if (l >= r) return;
int m = l + (r - l) / 2;
mergeSort(a, l, m);
mergeSort(a, m + 1, r);
merge(a, l, m, r);
}
int main() {
vector<int> a = {38, 27, 43, 3, 9, 82, 10};
mergeSort(a, 0, a.size() - 1);
for (int x : a) cout << x << " ";
cout << "\n";
}
3. Counting Inversions with Merge Sort
An inversion is a pair (i, j) with i < j but a[i] > a[j]. Inversions measure how "unsorted" an array is — used in metrics like Kendall's τ, recommendation systems, and competitive coding. Merge sort counts them while sorting, in O(n log n).
#include <bits/stdc++.h>
using namespace std;
long long mergeCount(vector<int>& a, int l, int m, int r) {
vector<int> L(a.begin()+l, a.begin()+m+1);
vector<int> R(a.begin()+m+1, a.begin()+r+1);
int i=0, j=0, k=l; long long inv=0;
while (i < (int)L.size() && j < (int)R.size()) {
if (L[i] <= R[j]) a[k++] = L[i++];
else { a[k++] = R[j++]; inv += L.size() - i; }
}
while (i < (int)L.size()) a[k++] = L[i++];
while (j < (int)R.size()) a[k++] = R[j++];
return inv;
}
long long countInv(vector<int>& a, int l, int r) {
if (l >= r) return 0;
int m = l + (r - l) / 2;
return countInv(a, l, m) + countInv(a, m+1, r) + mergeCount(a, l, m, r);
}
int main() {
vector<int> a = {8, 4, 2, 1};
cout << "inversions = " << countInv(a, 0, a.size()-1) << "\n";
}
4. Quick Sort — Partition First, Recurse Later
Pick a pivot. Rearrange the array so that all elements smaller than the pivot are to its left, and all greater are to its right. Then recursively sort the two halves. The merging is "free" — the partition has already done the work.
#include <bits/stdc++.h>
using namespace std;
// Lomuto partition with random pivot for safety
int partition(vector<int>& a, int l, int r) {
int pi = l + rand() % (r - l + 1);
swap(a[pi], a[r]);
int pivot = a[r], i = l - 1;
for (int j = l; j < r; j++)
if (a[j] < pivot) swap(a[++i], a[j]);
swap(a[i+1], a[r]);
return i + 1;
}
void quickSort(vector<int>& a, int l, int r) {
if (l >= r) return;
int p = partition(a, l, r);
quickSort(a, l, p - 1);
quickSort(a, p + 1, r);
}
int main() {
srand(42);
vector<int> a = {5, 2, 9, 1, 7, 3, 8, 4, 6};
quickSort(a, 0, a.size() - 1);
for (int x : a) cout << x << " ";
cout << "\n";
}
5. Pivot Strategies — Why Random Wins
A bad pivot creates imbalanced partitions and makes quick sort O(n²) in the worst case (e.g., always picking the first element on a sorted array). Three common defences:
| Strategy | Idea | বাংলায় |
|---|---|---|
| Random pivot | Choose any index uniformly at random. | random index বেছে নিন — adversary-proof। |
| Median-of-three | Pick median of first, middle, last. | প্রথম, মাঝ, শেষ — তিনটির median। |
| Introsort | Switch to heap sort if recursion depth too high. | recursion depth বেশি হলে heap sort-এ চলে যায়। |
6. Merge vs Quick — Real-World Trade-offs
Merge Sort (কখন)
- Stable
- Worst case = O(n log n)
- External sorting (data on disk)
- Linked lists (cheap to splice)
Quick Sort (কখন)
- In-place (O(log n) stack)
- Excellent cache locality
- 2–3× faster in practice
- Worst case O(n²) — mitigated by random pivot
7. Practice Problems
Try them on paper first; then run the answer.
-
Count the number of inversions in
{2, 4, 1, 3, 5}.{2, 4, 1, 3, 5}array-তে inversion সংখ্যা গণনা করুন।✨ Show Answer (উত্তর দেখুন)
inv.cpp#include <bits/stdc++.h> using namespace std; long long solve(vector<int>& a){ long long inv=0; for(int i=0;i<(int)a.size();i++) for(int j=i+1;j<(int)a.size();j++) if(a[i]>a[j]) inv++; return inv; } int main(){ vector<int> a={2,4,1,3,5}; cout<<solve(a)<<"\n"; } -
Find the k-th smallest using quick-select (partial quicksort).quick-select দিয়ে k-তম ক্ষুদ্রতম বের করুন।
✨ Show Answer (উত্তর দেখুন)
qselect.cpp#include <bits/stdc++.h> using namespace std; int part(vector<int>& a,int l,int r){ int p=a[r], i=l-1; for(int j=l;j<r;j++) if(a[j]<p) swap(a[++i],a[j]); swap(a[i+1],a[r]); return i+1; } int qsel(vector<int>& a,int l,int r,int k){ if(l==r) return a[l]; int p=part(a,l,r); if(p==k) return a[p]; return p>k? qsel(a,l,p-1,k) : qsel(a,p+1,r,k); } int main(){ vector<int> a={7,10,4,3,20,15}; cout<<qsel(a,0,a.size()-1,2)<<"\n"; // 3rd smallest (0-indexed) } -
Sort an array of only 0s, 1s and 2s in one pass (Dutch National Flag).শুধু 0, 1, 2 দিয়ে গঠিত array এক pass-এ sort করুন (Dutch National Flag)।
✨ Show Answer (উত্তর দেখুন)
dnf.cpp#include <bits/stdc++.h> using namespace std; int main(){ vector<int> a={2,0,1,2,1,0,0,2,1}; int lo=0, mid=0, hi=a.size()-1; while(mid<=hi){ if(a[mid]==0) swap(a[lo++],a[mid++]); else if(a[mid]==1) mid++; else swap(a[mid],a[hi--]); } for(int x:a) cout<<x<<" "; cout<<"\n"; } -
Implement merge sort on a singly linked list (no extra arrays).singly linked list-এ merge sort করুন (অতিরিক্ত array ছাড়া)।
✨ Show Answer (উত্তর দেখুন)
ll_merge.cpp#include <bits/stdc++.h> using namespace std; struct Node { int v; Node* nx; }; Node* merge(Node* a, Node* b){ Node dummy{0,nullptr}, *t=&dummy; while(a&&b){ if(a->v<=b->v){t->nx=a; a=a->nx;} else {t->nx=b; b=b->nx;} t=t->nx; } t->nx = a? a : b; return dummy.nx; } Node* msort(Node* h){ if(!h || !h->nx) return h; Node *slow=h, *fast=h->nx; while(fast && fast->nx){slow=slow->nx; fast=fast->nx->nx;} Node* mid=slow->nx; slow->nx=nullptr; return merge(msort(h), msort(mid)); } int main(){ int v[]={4,2,5,1,3}; Node* h=nullptr; for(int i=4;i>=0;i--) h=new Node{v[i],h}; h=msort(h); for(auto p=h;p;p=p->nx) cout<<p->v<<" "; cout<<"\n"; } -
Merge k sorted arrays into one sorted array (k-way merge).k-টি sorted array merge করে একটি sorted array তৈরি করুন।
✨ Show Answer (উত্তর দেখুন)
kway.cpp#include <bits/stdc++.h> using namespace std; int main(){ vector<vector<int>> arr={{1,5,9},{2,6,7},{3,4,8}}; priority_queue<tuple<int,int,int>, vector<tuple<int,int,int>>, greater<>> pq; for(int i=0;i<(int)arr.size();i++) pq.push({arr[i][0],i,0}); vector<int> out; while(!pq.empty()){ auto[v,i,j]=pq.top(); pq.pop(); out.push_back(v); if(j+1<(int)arr[i].size()) pq.push({arr[i][j+1],i,j+1}); } for(int x:out) cout<<x<<" "; cout<<"\n"; } -
Implement 3-way quick sort for arrays with many duplicates.duplicate-বহুল array-এর জন্য 3-way quick sort বানান।
✨ Show Answer (উত্তর দেখুন)
three_way.cpp#include <bits/stdc++.h> using namespace std; void qs3(vector<int>& a,int lo,int hi){ if(lo>=hi) return; int lt=lo, gt=hi, i=lo, p=a[lo]; while(i<=gt){ if(a[i]<p) swap(a[lt++],a[i++]); else if(a[i]>p) swap(a[i],a[gt--]); else i++; } qs3(a,lo,lt-1); qs3(a,gt+1,hi); } int main(){ vector<int> a={3,5,3,1,5,3,1,5,3}; qs3(a,0,a.size()-1); for(int x:a) cout<<x<<" "; cout<<"\n"; } -
Given two sorted arrays, find the median of the merged array in O(log(min(n,m))).দুটি sorted array দেওয়া; merge করার পরের median O(log(min(n,m)))-এ বের করুন।
✨ Show Answer (উত্তর দেখুন)
median2.cpp#include <bits/stdc++.h> using namespace std; double median(vector<int>& A, vector<int>& B){ if(A.size()>B.size()) return median(B,A); int n=A.size(), m=B.size(), lo=0, hi=n; while(lo<=hi){ int i=(lo+hi)/2, j=(n+m+1)/2-i; int Lx = i==0?INT_MIN:A[i-1], Rx = i==n?INT_MAX:A[i]; int Ly = j==0?INT_MIN:B[j-1], Ry = j==m?INT_MAX:B[j]; if(Lx<=Ry && Ly<=Rx){ if((n+m)%2) return max(Lx,Ly); return (max(Lx,Ly)+min(Rx,Ry))/2.0; } if(Lx>Ry) hi=i-1; else lo=i+1; } return 0; } int main(){ vector<int> A={1,3}, B={2,4,5}; cout<<median(A,B)<<"\n"; }
Summary — Module 13
Merge sort gives a guaranteed O(n log n) bound and stability — perfect for sorting on disk or linked lists. Quick sort with random pivots is faster in practice because it works in-place and respects CPU cache. Both are Divide & Conquer; the partition step is what changes.