クイックソートアルゴリズム:ピボット選択方法と計算量の解析

クイックソートの基本概念

クイックソートは、分割統治法に基づく効率的なソートアルゴリズムです。これはバブルソートの改良版とも言え、バブルソートのO(n²)の計算量を改善します。クイックソートでは、ある基準値(ピボット)を選び、配列をそれより大きいグループと小さいグループに分割して再帰的に処理します。

問題設定

整数配列を受け取り、クイックソートを使って昇順に並び替えるプログラムを作成します。入力は以下の形式:

  • 1行目:整数n(配列の長さ)
  • 2行目:n個の整数(1~10⁹)

出力はソート済みの整数列です。

実装例と比較

1. 最初の要素をピボットとする方法


#include <iostream>
using namespace std;

const int MAX = 1e6 + 5;
int arr[MAX];

int divide(int arr[], int left, int right) {
    int pivot = arr[left];
    while (left < right) {
        while (left < right && arr[right] >= pivot) right--;
        if (left < right) swap(arr[left], arr[right++]);
        
        while (left < right && arr[left] <= pivot) left++;
        if (left < right) swap(arr[left], arr[right]);
    }
    return left;
}

void quickSort(int arr[], int left, int right) {
    if (left < right) {
        int pivotIndex = divide(arr, left, right);
        quickSort(arr, left, pivotIndex - 1);
        quickSort(arr, pivotIndex + 1, right);
    }
}

int main() {
    int n;
    cin >> n;
    for (int i = 0; i < n; i++) cin >> arr[i];
    quickSort(arr, 0, n - 1);
    for (int i = 0; i < n; i++) cout << arr[i] << " ";
    return 0;
}

2. 中央値をピボットとする方法


// 中央値のインデックスを計算する関数
int findMedian(int arr[], int start, int mid, int end) {
    if (arr[start] <= arr[mid]) {
        if (arr[mid] <= arr[end]) return mid;
        return (arr[start] <= arr[end]) ? end : start;
    } else {
        if (arr[start] <= arr[end]) return start;
        return (arr[mid] <= arr[end]) ? mid : end;
    }
}

int divideMedian(int arr[], int left, int right) {
    int mid = (left + right) / 2;
    int medianIndex = findMedian(arr, left, mid, right);
    swap(arr[left], arr[medianIndex]);
    return divide(arr, left, right); // 前述のdivide関数を再利用
}

3. ランダムな要素をピボットとする方法


#include <cstdlib>

int divideRandom(int arr[], int left, int right) {
    int randomIndex = left + rand() % (right - left + 1);
    swap(arr[left], arr[randomIndex]);
    return divide(arr, left, right); // 前述のdivide関数を再利用
}

計算量の解析

クイックソートのパフォーマンスはピボットの選択に大きく依存します。

  • 最良時間計算量: O(n log n) - 各分割で配列がほぼ半分になる場合。
  • 最悪時間計算量: O(n²) - 入力が既にソート済みなど、偏った分割が続く場合。
  • 空間計算量: 再帰呼び出しの深さに依存します。
    • 最良:O(log n)
    • 最悪:O(n)

タグ: C++ アルゴリズム ソート クイックソート 計算量解析

6月16日 21:58 投稿