7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaAdvent Calendar 2024

Day 18

Javaで学ぶアルゴリズム -クイックソート

Posted at

クイックソート

クイックソート(英: quicksort)は、1960年にアントニー・ホーアが開発したソートのアルゴリズム。分割統治法の一種。
他のソート法と比べて一般的に最も高速だと言われているが、対象のデータの並びやデータの数によっては必ずしも速いわけではなく、最悪の計算量は $O(n^{2})$である。安定ソートではない。

実行環境

  • JUnit 5
  • Java 8

テストコード

QuickSortTest.java
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class QuickSortTest {

	@DisplayName("配列を渡すとソートされた配列が返ること")
	@Test
	void testStraightExchange() {
		int[] array = { 5, 12, 7, 0, 1, 14, 6, 9, 8, 3, 2, 11, 4, 13, 10 };
		int[] expected = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 };

		int[] actual = QuickSort.quickSort(array, 0, array.length - 1);
		assertThat(actual, is(expected));
	}

	@DisplayName("要素が1つしかない配列を渡すと要素が1つしかない配列が返ること")
	@Test
	void testStraightExchangeOneElement() {
		int[] array = { 5 };
		int[] expected = { 5 };

		int[] actual = QuickSort.quickSort(array, 0, array.length - 1);
		assertThat(actual, is(expected));
	}

	@DisplayName("空の配列を渡すと空の配列が返ること")
	@Test
	void testStraightExchangeEmpty() {
		int[] array = {};
		int[] expected = {};

		int[] actual = QuickSort.quickSort(array, 0, array.length);
		assertThat(actual, is(expected));
	}

	@DisplayName("すでに昇順でソートされた配列を渡すとソートされた配列が返ること")
	@Test
	void testStraightExchangeAlreadySorted() {
		int[] array = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 };
		int[] expected = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 };

		int[] actual = QuickSort.quickSort(array, 0, array.length - 1);
		assertThat(actual, is(expected));
	}

	@DisplayName("要素が重複した配列を渡すとソートされた配列が返ること")
	@Test
	void testStraightExchangeDuplicateElement() {
		int[] array = { 5, 3, 5, 2, 1, 5, 3, 2, 5, 3, 5 };
		int[] expected = { 1, 2, 2, 3, 3, 3, 5, 5, 5, 5, 5 };

		int[] actual = QuickSort.quickSort(array, 0, array.length - 1);
		assertThat(actual, is(expected));
	}
}

クイックソートアルゴリズム

QuickSort.java
public class QuickSort {

	/**
	 * クイックソート
	 * 再帰呼び出しを使用して実装されることが一般的らしい
	 * @param array
	 * @param lower
	 * @param upper
	 * @return ソート済みの array
	 */
	public static int[] quickSort(int[] array, int lower, int upper) {

		if (array.length == 0) {
			return array;
		}
		int left = lower;
		int right = upper;
		int mid = array[(left + right) / 2];

		while (left <= right) {
			while (array[left] < mid) {
				left++;
			}
			while (array[right] > mid) {
				right--;
			}
			if (left <= right) {
				int temp = array[left];
				array[left] = array[right];
				array[right] = temp;
				left++;
				right--;
			}
		}
		if (lower < right) {
			quickSort(array, lower, right);
		}
		if (upper > left) {
			quickSort(array, left, upper);
		}
		return array;
	}
}

メソッドで何してるか確認するためのデバッグ用コード

要素を入れ替える前後に配列の状態を出力するデバッグ用のコードを差し込んで確認してみる。

QuickSortTest.java
public class QuickSortTest {

	@DisplayName("配列を渡すとソートされた配列が返ること")
	@Test
	void testStraightExchange() {
		int[] array = { 5, 12, 7, 0, 1, 14, 6, 9, 8, 3, 2, 11, 4, 13, 10 };
		int[] expected = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 };

		// デバッグ用のラップメソッドを呼び出す
        int[] actual = QuickSort.quickSortForDebug(array);
		assertThat(actual, is(expected));
	}
 }
QuickSort.java
import java.util.Arrays;

public class QuickSort {

	private static int count = 0;

	/**
	 * デバッグ用にquickSort()をラップしたメソッド
	 * @param array
	 * @return sortedArray
	 */
	public static int[] quickSortForDebug(int[] array) {
		int[] sortedArray = array.clone();
		sortedArray = quickSort(sortedArray, 0, sortedArray.length - 1);
		System.out.println(Arrays.toString(sortedArray));
		return sortedArray;
	}

	/**
	 * クイックソート
	 * 再帰呼び出しを使用して実装されることが一般的らしい
	 * @param array
	 * @param lower
	 * @param upper
	 * @return ソート済みの array
	 */
	public static int[] quickSort(int[] array, int lower, int upper) {

		if (array.length == 0) {
			return array;
		}
		int left = lower;
		int right = upper;
		int mid = array[(left + right) / 2];

		// デバッグ用
		count++;
		printForDebug(count, "Before", array, lower, upper);

		while (left <= right) {
			while (array[left] < mid) {
				left++;
			}
			while (array[right] > mid) {
				right--;
			}
			if (left <= right) {
				int temp = array[left];
				array[left] = array[right];
				array[right] = temp;
				left++;
				right--;
			}
		}
		// デバッグ用
		printForDebug(count, "After ", array, lower, upper);
		if (lower < right) {
			quickSort(array, lower, right);
		}
		if (upper > left) {
			quickSort(array, left, upper);
		}
		return array;
	}

	/**
	 * デバッグ用に配列の状態を吐き出す
	 * @param count
	 * @param label
	 * @param array
	 * @param lower
	 * @param upper
	 */
	private static void printForDebug(int count, String label, int[] array, int lower, int upper) {
		System.out.printf("[%d周目 %s] array[%d] ~ [%d]:{", count, label, lower, upper);
		for (int i = lower; i <= upper; i++) {
			System.out.printf("%d", array[i]);
			if (i < upper) {
				System.out.print(" , ");
			}
		}
		System.out.println("}");
	}
}

※ 分かりやすくするために改行入れてます

デバッグ出力の内容
[1周目 Before] array[0] ~ [14]:{5 , 12 , 7 , 0 , 1 , 14 , 6 , 9 , 8 , 3 , 2 , 11 , 4 , 13 , 10}
[1周目 After ] array[0] ~ [14]:{5 , 4 , 7 , 0 , 1 , 2 , 6 , 3 , 8 , 9 , 14 , 11 , 12 , 13 , 10}

[2周目 Before] array[0] ~ [8]:{5 , 4 , 7 , 0 , 1 , 2 , 6 , 3 , 8}
[2周目 After ] array[0] ~ [8]:{1 , 0 , 7 , 4 , 5 , 2 , 6 , 3 , 8}

[3周目 Before] array[0] ~ [1]:{1 , 0}
[3周目 After ] array[0] ~ [1]:{0 , 1}

[4周目 Before] array[2] ~ [8]:{7 , 4 , 5 , 2 , 6 , 3 , 8}
[4周目 After ] array[2] ~ [8]:{2 , 4 , 5 , 7 , 6 , 3 , 8}

[5周目 Before] array[3] ~ [8]:{4 , 5 , 7 , 6 , 3 , 8}
[5周目 After ] array[3] ~ [8]:{4 , 5 , 3 , 6 , 7 , 8}

[6周目 Before] array[3] ~ [6]:{4 , 5 , 3 , 6}
[6周目 After ] array[3] ~ [6]:{4 , 3 , 5 , 6}

[7周目 Before] array[3] ~ [4]:{4 , 3}
[7周目 After ] array[3] ~ [4]:{3 , 4}

[8周目 Before] array[5] ~ [6]:{5 , 6}
[8周目 After ] array[5] ~ [6]:{5 , 6}

[9周目 Before] array[7] ~ [8]:{7 , 8}
[9周目 After ] array[7] ~ [8]:{7 , 8}

[10周目 Before] array[9] ~ [14]:{9 , 14 , 11 , 12 , 13 , 10}
[10周目 After ] array[9] ~ [14]:{9 , 10 , 11 , 12 , 13 , 14}

[11周目 Before] array[9] ~ [10]:{9 , 10}
[11周目 After ] array[9] ~ [10]:{9 , 10}

[12周目 Before] array[12] ~ [14]:{12 , 13 , 14}
[12周目 After ] array[12] ~ [14]:{12 , 13 , 14}

ソート結果
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?