Divide & Conquer Sorting: Merge & Quick

Divide and Conquer — Merge ও Quick

Read: ~35 min Intermediate 7 practice problems Live code runner

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.

Divide & Conquer একটি অপূর্ব ধারণা: n আকারের সমস্যাকে n/2 আকারের দুটি অংশে ভাগ করুন, recursively সমাধান করুন, তারপর জোড়া দিন। Merge sort এবং quick sort — দুটোই এই pattern অনুসরণ করে, কিন্তু ভিন্নভাবে কাজ ভাগ করে।
Today's insight
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).

array-কে অর্ধেক করে recursively ভাগ করুন যতক্ষণ প্রতিটি অংশের আকার ১ হয়। এরপর জোড়া জোড়া merge করুন। দুইটি sorted array merge-এ O(n) সময় লাগে, log n level — মোট O(n log n)।
[38, 27, 43, 3, 9, 82, 10] [38, 27, 43, 3] [9, 82, 10] [38, 27] [43, 3] [9, 82] [10] [38] [27] [43] [3] [9] [82] ↓ merge upward ↓ [3, 9, 10, 27, 38, 43, 82] Figure 13.1 — merge sort-এর recursion tree, [38,27,43,3,9,82,10]-এর ওপর।
merge_sort.cpp
#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";
}
Recurrence. T(n) = 2T(n/2) + O(n). By the master theorem (case 2) this gives T(n) = Θ(n log n). Merge sort is stable and predictable — its worst case equals its best case.

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).

Inversion মানে এমন pair (i, j) যেখানে i < j কিন্তু a[i] > a[j]। merge sort sort করার সময়েই inversion-সংখ্যা গুনে নেয় — O(n log n)।
inversions.cpp
#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.

একটি pivot বেছে নিন। array-কে এমনভাবে সাজান যাতে pivot-এর চেয়ে ছোট সব বামে এবং বড় সব ডানে থাকে। তারপর recursively দুটি অংশ sort করুন। আলাদা merge step নেই — partition নিজেই কাজ শেষ করে।
quick_sort.cpp
#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:

StrategyIdeaবাংলায়
Random pivotChoose any index uniformly at random.random index বেছে নিন — adversary-proof।
Median-of-threePick median of first, middle, last.প্রথম, মাঝ, শেষ — তিনটির median।
IntrosortSwitch to heap sort if recursion depth too high.recursion depth বেশি হলে heap sort-এ চলে যায়।
Never use a fixed pivot (always a[l] or always a[r]) on data you do not control. Real-world data is often partially sorted — and that's exactly the worst case for fixed pivots.

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
Merge sort stable এবং worst case-ও O(n log n)। Quick sort in-place ও cache-friendly — তাই বাস্তবে দ্রুত। std::sort বেশিরভাগ implementation-এ introsort ব্যবহার করে — quick sort + heap sort + insertion sort-এর hybrid।

7. Practice Problems

Try them on paper first; then run the answer.

আগে নিজে কাগজে চেষ্টা করুন; তারপর runnable উত্তর দেখুন।
  1. 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";
    }
  2. 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)
    }
  3. 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";
    }
  4. 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";
    }
  5. 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";
    }
  6. 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";
    }
  7. 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.

Merge sort guaranteed O(n log n) এবং stable — disk sort ও linked list-এর জন্য আদর্শ। Quick sort random pivot সহ বাস্তবে দ্রুত — কারণ in-place ও cache-friendly। দুটিই Divide & Conquer, পার্থক্য partition-এ।

Next Module → Non-Comparison Sorting — Counting, Radix ও Bucket sort, যেগুলো n log n-এর নিচে নামতে পারে।