ソートアルゴリズム(Java版)

1. バブルソート

バブルソートは、単純で直感的なソートアルゴリズムです。配列を繰り返し走査し、隣接する要素を比較して順序が逆であれば交換します。このプロセスを、配列が完全にソートされるまで繰り返します。このアルゴリズムの名前は、小さい要素が「泡」として徐々に配列の先頭に「浮かび上がって」いく様子に由来します。

アルゴリズムのステップ:

  1. 隣接する要素を比較します。もし一つ目の要素が二つ目の要素より大きければ、それらを交換します。
  2. 配列の先頭から末尾まで、すべての隣接する要素ペアに対して上記の操作を行います。このステップが完了すると、最後の要素は配列の中で最大の値になります。
  3. 最後の要素を除いたすべての要素に対して、上記のステップを繰り返します。
  4. 比較が必要な要素の数がゼロになるまで、上記のステップを繰り返します。
public void bubbleSort(int[] array) {
    int swapValue;
    int arrayLength = array.length;
    for (int outerIndex = 0; outerIndex < arrayLength; outerIndex++) {
        for (int innerIndex = 1; innerIndex < arrayLength - outerIndex; innerIndex++)
            if (array[innerIndex - 1] > array[innerIndex]) {
                // 順序が逆であれば交換
                swapValue = array[innerIndex - 1];
                array[innerIndex - 1] = array[innerIndex];
                array[innerIndex] = swapValue;
            }
    }
}

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

配列が早期にソートされる場合、元のバブルソートは最後まで走査を続けます。この無駄を省くために、フラグを導入して、あるパスで交換が発生しなかった場合、ソートが完了したと判断し処理を終了させることができます。

public void optimizedBubbleSort(int[] array) {
    int swapValue;
    int arrayLength = array.length;
    boolean swapped;
    do {
        swapped = false;
        for (int innerIndex = 1; innerIndex < arrayLength; innerIndex++)
            if (array[innerIndex - 1] > array[innerIndex]) {
                swapValue = array[innerIndex - 1];
                array[innerIndex - 1] = array[innerIndex];
                array[innerIndex] = swapValue;
                swapped = true; // 交換が発生したことを記録
            }
    } while (swapped); // 交換が発生しなかったら終了
}

2. クイックソート

クイックソートは、分割統治法に基づいた効率的なソートアルゴリズムです。平均的なケースでは O(n log n) の比較回数でソートが完了します。最悪のケースでは O(n²) が必要になりますが、これは稀な状況です。実際には、多くの O(n log n) アルゴリズムよりも高速に動作します。

クイックソートは、配列を「ピボット」と呼ばれる要素を基準に二つの部分に分割します。

アルゴリズムのステップ:

  1. 配列から一つの要素をピボットとして選択します。
  2. 配列を再配置し、ピボットより小さい要素をその前に、大きい要素をその後に配置します(同じ値はどちら側でも構いません)。この「分割」操作の後、ピボットは配列の中央に配置されます。
  3. ピボットより小さい要素の部分配列と、大きい要素の部分配列を再帰的にソートします。
  4. 再帰の基底ケースは、配列のサイズが0または1のときです。この場合はすでにソート済みと見なされます。再帰は、各反復で少なくとも一つの要素が最終的な位置に配置されるため、必ず終了します。
private static void quickSortRecursive(int[] data, int left, int right) {
    if (left < right) {
        int pivotIndex = partition(data, left, right);
        quickSortRecursive(data, left, pivotIndex - 1);
        quickSortRecursive(data, pivotIndex + 1, right);
    }
}

private static int partition(int[] data, int left, int right) {
    int pivotValue = data[right];
    int partitionIndex = left - 1;
    int temp;
    for (int currentIndex = left; currentIndex < right; currentIndex++) {
        if (data[currentIndex] < pivotValue) {
            partitionIndex++;
            temp = data[partitionIndex];
            data[partitionIndex] = data[currentIndex];
            data[currentIndex] = temp;
        }
    }
    temp = data[partitionIndex + 1];
    data[partitionIndex + 1] = data[right];
    data[right] = temp;
    return partitionIndex + 1;
}

3. マージソート

マージソートは、マージ操作に基づいた効率的なソートアルゴリズムです。分割統治法の典型的な応用例です。

アルゴリズムのステップ:

  1. ソート済みの二つの部分配列の合計サイズを持つ領域を確保し、マージ後の配列を格納します。
  2. 二つの部分配列の先頭を指すポインタを設定します。
  3. 二つのポインタが指す要素を比較し、小さい方をマージ領域にコピーし、対応するポインタを進めます。
  4. 一方のポインタが部分配列の末尾に達するまでステップ3を繰り返します。
  5. もう一方の部分配列に残った要素を、そのままマージ領域の末尾にコピーします。
public static int[] mergeSort(int[] sourceArray, int startIndex, int endIndex) {
    int middleIndex = (startIndex + endIndex) / 2;
    if (startIndex < endIndex) {
        mergeSort(sourceArray, startIndex, middleIndex);
        mergeSort(sourceArray, middleIndex + 1, endIndex);
        merge(sourceArray, startIndex, middleIndex, endIndex);
    }
    return sourceArray;
}

public static void merge(int[] sourceArray, int startIndex, int middleIndex, int endIndex) {
    int[] mergedArray = new int[endIndex - startIndex + 1];
    int leftPointer = startIndex;
    int rightPointer = middleIndex + 1;
    int mergedIndex = 0;

    while (leftPointer <= middleIndex && rightPointer <= endIndex) {
        if (sourceArray[leftPointer] <= sourceArray[rightPointer]) {
            mergedArray[mergedIndex++] = sourceArray[leftPointer++];
        } else {
            mergedArray[mergedIndex++] = sourceArray[rightPointer++];
        }
    }

    while (leftPointer <= middleIndex) {
        mergedArray[mergedIndex++] = sourceArray[leftPointer++];
    }

    while (rightPointer <= endIndex) {
        mergedArray[mergedIndex++] = sourceArray[rightPointer++];
    }

    for (int i = 0; i < mergedArray.length; i++) {
        sourceArray[startIndex + i] = mergedArray[i];
    }
}

4. 選択ソート

選択ソートは、単純な比較ベースのソートアルゴリズムです。

アルゴリズムのステップ:

  1. 未ソートの部分配列から最小(または最大)の要素を見つけ、ソート済み部分の末尾に移動します。
  2. 残りの未ソートの要素から、再び最小の要素を見つけ、ソート済み部分の末尾に追加します。
  3. すべての要素がソートされるまでステップ2を繰り返します。
public int[] selectionSort(int[] inputArray) {
    int arrayLength = inputArray.length;
    for (int sortedIndex = 0; sortedIndex < arrayLength - 1; sortedIndex++) {
        int minIndex = sortedIndex;
        for (int searchIndex = sortedIndex + 1; searchIndex < arrayLength; searchIndex++) {
            if (inputArray[searchIndex] < inputArray[minIndex]) {
                minIndex = searchIndex;
            }
        }
        // 最小の要素を現在の位置と交換
        int swapTemp = inputArray[sortedIndex];
        inputArray[sortedIndex] = inputArray[minIndex];
        inputArray[minIndex] = swapTemp;
    }
    return inputArray;
}

5. ヒープソート

ヒープソートは、ヒープというデータ構造を利用した比較的高性能なソートアルゴリズムです。ヒープは、完全二分木に近い構造を持ち、親ノードの値が子ノードの値より常に大きい(または小さい)という性質を満たします。ヒープソートの平均時間計算量は O(n log n) です。

アルゴリズムのステップ:

  1. ヒープ H[0..n-1] を構築します。
  2. ヒープのルート(最大値)と最後の要素を交換します。
  3. ヒープのサイズを1減らし、`heapify` 関数を呼び出して新しいルート要素を正しい位置に調整します。
  4. ヒープのサイズが1になるまでステップ2を繰り返します。
public void heapSort(int[] heapArray) {
    int n = heapArray.length;

    // ヒープを構築する(最大ヒープ)
    for (int i = n / 2 - 1; i >= 0; i--) {
        heapify(heapArray, n, i);
    }

    // ヒープから要素を1つずつ取り出す
    for (int i = n - 1; i > 0; i--) {
        // 現在のルート(最大要素)を配列の末尾に移動
        int temp = heapArray[0];
        heapArray[0] = heapArray[i];
        heapArray[i] = temp;

        // 縮小したヒープを再構築
        heapify(heapArray, i, 0);
    }
}

// ヒープの性質を維持するためのヘルパー関数
void heapify(int[] heapArray, int n, int i) {
    int largest = i; // 親ノードのインデックス
    int leftChild = 2 * i + 1; // 左の子ノードのインデックス
    int rightChild = 2 * i + 2; // 右の子ノードのインデックス

    // 左の子が親より大きい場合
    if (leftChild < n && heapArray[leftChild] > heapArray[largest]) {
        largest = leftChild;
    }

    // 右の子が現在の最大値より大きい場合
    if (rightChild < n && heapArray[rightChild] > heapArray[largest]) {
        largest = rightChild;
    }

    // 最大値が親ノードでない場合、交換して再帰的にヒープを修正
    if (largest != i) {
        int swap = heapArray[i];
        heapArray[i] = heapArray[largest];
        heapArray[largest] = swap;

        heapify(heapArray, n, largest);
    }
}

タグ: Java ソートアルゴリズム バブルソート クイックソート マージソート

5月17日 14:18 投稿