整数配列のソート実装:複数アルゴリズムによる昇順整列化

問題概要

非負とは限らない整数からなる配列 nums が与えられる。この配列を昇順に並べ替える関数を実装する。制約条件として、配列長は最大で 50,000、各要素の値は -50,000 から 50,000 の範囲内である。

基本的なソート手法

バブルソート(改良なし)

隣接する要素を比較し、必要に応じて交換を行うことで、毎回最大値が末尾に移動する。このプロセスを繰り返す。

public void bubbleSort(int[] arr) {
    int n = arr.length;
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

最適化されたバブルソート

一巡の中で交換が一度も発生しなければ、既に整列済みと判断して早期終了できる。

public void optimizedBubbleSort(int[] arr) {
    int n = arr.length;
    for (int i = 0; i < n - 1; i++) {
        boolean swapped = false;
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                swapped = true;
            }
        }
        if (!swapped) break;
    }
}

選択ソート

未整列部分から最小値を探し、それを未整列部分の先頭と交換する。これを繰り返す。

public void selectionSort(int[] arr) {
    int n = arr.length;
    for (int i = 0; i < n - 1; i++) {
        int minIdx = i;
        for (int j = i + 1; j < n; j++) {
            if (arr[j] < arr[minIdx]) {
                minIdx = j;
            }
        }
        if (minIdx != i) {
            int temp = arr[i];
            arr[i] = arr[minIdx];
            arr[minIdx] = temp;
        }
    }
}

挿入ソート

整列済み領域を拡大しながら、新しい要素を適切な位置に挿入する。

public void insertionSort(int[] arr) {
    for (int i = 1; i < arr.length; i++) {
        int key = arr[i];
        int j = i - 1;
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = key;
    }
}

二分挿入ソート

挿入位置を線形探索ではなく、二分探索で求める。シフト操作は依然O(n)だが、比較回数を削減できる。

public void binaryInsertionSort(int[] arr) {
    for (int i = 1; i < arr.length; i++) {
        int key = arr[i];
        int left = 0, right = i - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] > key) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        for (int j = i - 1; j >= left; j--) {
            arr[j + 1] = arr[j];
        }
        arr[left] = key;
    }
}

効率的なソートアルゴリズム

マージソート

分割統治法に基づく安定ソート。配列を半分に分割し、それぞれを再帰的にソートした後、結果をマージする。

public void mergeSort(int[] arr, int l, int r, int[] buf) {
    if (l >= r) return;
    
    // 小サイズでは挿入ソートに切り替え
    if (r - l + 1 <= 16) {
        insertionSubSort(arr, l, r);
        return;
    }
    
    int m = l + (r - l) / 2;
    mergeSort(arr, l, m, buf);
    mergeSort(arr, m + 1, r, buf);
    
    if (arr[m] <= arr[m + 1]) return; // 既に整列済み
    
    System.arraycopy(arr, l, buf, l, r - l + 1);
    int i = l, j = m + 1;
    for (int k = l; k <= r; k++) {
        if (i > m) {
            arr[k] = buf[j++];
        } else if (j > r) {
            arr[k] = buf[i++];
        } else if (buf[i] <= buf[j]) {
            arr[k] = buf[i++];
        } else {
            arr[k] = buf[j++];
        }
    }
}

private void insertionSubSort(int[] arr, int l, int r) {
    for (int i = l + 1; i <= r; i++) {
        int key = arr[i];
        int j = i - 1;
        while (j >= l && arr[j] > key) {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = key;
    }
}

クイックソート(ピボット最適化付き)

ランダムなピボットを選択し、配列を分割する。小規模な部分配列では挿入ソートを使用してパフォーマンスを向上させる。

private static final int THRESHOLD = 16;
private Random rand = new Random();

public void quickSort(int[] arr, int low, int high) {
    if (low >= high) return;

    if (high - low + 1 <= THRESHOLD) {
        insertionSubSort(arr, low, high);
        return;
    }

    int pivotIndex = partition(arr, low, high);
    quickSort(arr, low, pivotIndex - 1);
    quickSort(arr, pivotIndex + 1, high);
}

private int partition(int[] arr, int l, int r) {
    int randomPivot = l + rand.nextInt(r - l + 1);
    swap(arr, l, randomPivot);

    int pivotVal = arr[l];
    int i = l + 1, j = r;

    while (true) {
        while (i <= r && arr[i] < pivotVal) i++;
        while (j > l && arr[j] > pivotVal) j--;
        if (i >= j) break;
        swap(arr, i++, j--);
    }
    swap(arr, l, j);
    return j;
}

private void swap(int[] arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

解法の選定と性能比較

  • O(n²) 手法(バブル・選択・挿入):教育目的や極小データ向け
  • O(n log n) 手法:実用的。マージソートは安定かつ予測可能な性能、クイックソートは平均高速だが最悪ケースに注意
  • ハイブリッド戦略:クイックソート+挿入ソートは現実的な最適化

タグ: sorting-algorithm merge-sort quick-sort insertion-sort bubble-sort

6月14日 23:21 投稿