2019.09.06

クイックソート

クイックソート

はじめに

クイックソートは最も高速であるといわれているソート法です。

この方法は、並び替える値の中から基準の値を決め、その基準値より小さい値を集めたグループ、大きい値を集めたグループを作成していき、さらに、各グループの中の値から基準値を決めて同様の方法でグループを分割していきます。分割の結果、グループ内の値が一つになったとき、その値が確定されます。

クイックソートの考え方

それでは、具体的な値でクイックソートの動きをみていきましょう。

以下は、並び替える前のデータです。

クイックソート 元データ

この中から基準値を決めます。

ここでは、配列に格納されているほぼ中心の値を基準値とします。

クイックソート 基準値

データ数が10なので、配列添え字は0~9です。

ここでは、添え字最大値9を2で割った整数部の4(左から5番目)に格納されている値12を基準値としています。

次に、配列の先頭(添え字:0)から後方に向けて基準値以上の値を探します。

その後、配列の最後(添え字:9)から前方に向けて基準値以下の値を探します。

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

この例では、配列の2番目(添え字:1)の値20が基準値12以上、配列の10番目(添え字:9)の値9が基準値12以下の値が見つかりました。この処理で値が見つかった場合、その2つの値を入れ替えます。

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

この操作を、前方からの検索位置sが後方からの検索位置eと同じになるか、超えるまで行います。これを満たす条件は、s>=e

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

検索位置s=6、検索位置e=5となり、s>=e条件を満たしました。この時点で、配列の中の基準値12の前方に格納されている数字は12以下、後方に格納されている数字は12以上であることが確認できます。

次に検索位置s=6、検索位置e=5でグループを分割します。ここでの分割は、2つの新しい配列を作成するのではなく、ここまでの処理を添え字0~5、添え字6~9の2つの範囲で行うことを意味しています。(下図)

このように分割処理を繰り返していき、グループの値の数が1つになればその値を確定します。また、グループの値の数が2つの場合、2つの値の大小を比較して小さいほうから並べて値を確定します。

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

この処理では、1つのグループの値の並びで、基準値の前方の値が全て基準値未満で、後方の値が基準値より大きい場合、検索位置sと検索位置eが同じ状態で処理を終了します。この場合、検索位置sに1を足してグループを分割します。

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

最後にグループ内の値の数が2個以下になった場合について説明します。

ループ内の値の数が1個の場合は、その値で確定です。グループ内の値の数が2個の場合は、その2つの値を比較して、前方の値が後方の値より大きければ値を入れ替えて値を確定します。2個の場合は基準値は使いません。

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

以上が、クイックソートの考えかたです。

Javaソースコード

以下は、クイックソートJavaソースコード例です。

QuickSort.java

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
public class QuickSort {
	// クイックソート(再帰用)
	static void quick_sort( int[] a, int start, int end )
	{
		int pivot;	// 基準の値
		int w;		// 値の交換用
		int s, e;	// 添え字の位置
	
		// 配列の添え字startがend以下であれば、処理しない
		if ( start >= end ) return;

		// 配列の添え字startとendの差が1の同じ場合(隣どうし)
		if ( 1 == ( end - start ) ) {
			if ( a[ start ] > a[ end ] ) {
				// 値の入れ替え
				w = a[ start ];
				a[ start ] = a[ end ];
				a[ end ] = w;
			}
			return;
		}

		// 基準値を代入
		pivot = a[ ( start + end ) / 2  ];


		// 基準値で値の並び替え
		s = start - 1;
		e = end + 1;
		for ( ; ; ) {
			// sをインクリメントし、pivot以上の値を探す
			for ( ; ; ) {
				++ s;
				if ( a[ s ] >= pivot ) break;
			}

			// eをデクリメントし、pivot以下の値を探す
			for ( ; ; ) {
				-- e;
				if ( a[ e ] <= pivot ) break;
			}

			// sがe以上であれば終了
			if ( s >= e ) break;

			// 値の入れ替え
			w = a[ s ];
			a[ s ] = a[ e ];
			a[ e ] = w;
		}

		// sとeが同じであればsをインクリメント
		if ( s == e ) ++ s;

		// ブロックの分割
		quick_sort( a, start, e );
		quick_sort( a, s, end );
	}


	// クイックソート
	static void quick_sort( int[] a )
	{
		// 配列の要素数が1以下の場合、処理をしない
		if ( 1 >= a.length ) return;

		// クイックソート実行
		quick_sort( a, 0, a.length - 1 );
	}


	// メイン
	public static void main( String[] args ) {
		// 配列aを宣言
		int[] a;

		// 要素数10を設定
		a = new int[ 10 ];

		// 値を代入
		a[ 0 ] =  4;
		a[ 1 ] = 20;
		a[ 2 ] = 15;
		a[ 3 ] =  0;
		a[ 4 ] = 12;
		a[ 5 ] = 10;
		a[ 6 ] =  7;
		a[ 7 ] = 13;
		a[ 8 ] =  8;
		a[ 9 ] =  9;

		// 昇順ソート
		quick_sort( a );

		// 結果の表示
		for ( int i = 0; i < a.length; ++ i )
			System.out.println( a[ i ] );
	}
}

コンパイル ソースコードが「ANSI」の場合

C:\talavax\javasample>javac -encoding sjis QuickSort.java

コンパイル ソースコードが「UTF-8」の場合

C:\talavax\javasample>javac QuickSort.java

実行

C:\talavax\javasample>java QuickSort

QuickSortの出力結果

0
4
7
8
9
10
12
13
15
20

出力結果から、元の配列の値が昇順に並び替わっていることが確認できます。

Javaソースコードの解説

それでは、ここからソースコードを解説していきます。

001
public class QuickSort {

クラス名を、QuickSortとしています。

002
003
	// クイックソート(再帰用)
	static void quick_sort( int[] a, int start, int end )

クイックソート行うquick_sortメソッドです。int型配列aとint型変数startとendに処理する範囲の添え字を指定してソートを実行します。

005
006
007
		int pivot;	// 基準の値
		int w;		// 値の交換用
		int s, e;	// 添え字の位置

クイックソートで使う変数を宣言しています。

009
010
		// 配列の添え字startがend以下であれば、処理しない
		if ( start >= end ) return;

変数startの値がendの値以上であればreturn文メソッドを抜けます。

012
013
014
015
016
017
018
019
020
021
		// 配列の添え字startとendの差が1の同じ場合(隣どうし)
		if ( 1 == ( end - start ) ) {
			if ( a[ start ] > a[ end ] ) {
				// 値の入れ替え
				w = a[ start ];
				a[ start ] = a[ end ];
				a[ end ] = w;
			}
			return;
		}

処理範囲の値の数が2個の場合、単純な値の比較による値の入れ替え処理を行い、return文メソッドを抜けます。

ifの条件式( 1 == ( end - start ) )は、startとendが隣同士の関係であることを表し、これにより値が2個であることがわかります。

023
024
		// 基準値を代入
		pivot = a[ ( start + end ) / 2  ];

変数startとendを足して2で割った位置の配列の値を基準値を格納する変数pivotに代入しています。

027
028
029
		// 基準値で値の並び替え
		s = start - 1;
		e = end + 1;

基準値との比較位置の初期値を変数sとeに代入しています。変数sにstart-1を代入しているのは、基準値の前方の値を比較する処理が、先に変数sをインクリメントした後で配列の値を比較するようにしているためです。変数eにend+1を代入している理由も同様に、先に変数eをデクリメントしているからです。

030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
		for ( ; ; ) {
			// sをインクリメントし、pivot以上の値を探す
			for ( ; ; ) {
				++ s;
				if ( a[ s ] >= pivot ) break;
			}

			// eをデクリメントし、pivot以下の値を探す
			for ( ; ; ) {
				-- e;
				if ( a[ e ] <= pivot ) break;
			}

			// sがe以上であれば終了
			if ( s >= e ) break;

			// 値の入れ替え
			w = a[ s ];
			a[ s ] = a[ e ];
			a[ e ] = w;
		}

for文無限ループ作成しています。

そのループの中で、a[s]>=pivotになるsと、a[e]<=pibotになるeを求めています。

sとeを求めた結果、sがe以上になったときにbreak文ループを終了するようにしています。

sがe未満のときは、a[s]とa[e]の値を入れ替えます。

052
053
		// sとeが同じであればsをインクリメント
		if ( s == e ) ++ s;

for文を抜けたときのsとeが同じとき、sをインクリメントします。

055
056
057
		// ブロックの分割
		quick_sort( a, start, e );
		quick_sort( a, s, end );

範囲を2つに分けて処理を行います。その範囲はstart~eとs~endの2つで、quick_sortメソッドにそれらの範囲を渡し再帰的に処理をしていきます。

061
062
063
064
065
066
067
068
069
	// クイックソート
	static void quick_sort( int[] a )
	{
		// 配列の要素数が1以下の場合、処理をしない
		if ( 1 >= a.length ) return;

		// クイックソート実行
		quick_sort( a, 0, a.length - 1 );
	}

処理範囲を引数として渡さないクイックソートメソッドです。

与えられたint型配列aの個数が1個以下であれば処理をせずメソッドreturn文で抜けます。2個以上の場合、処理範囲を引数として渡せる再帰処理用のquick_sortメソッドに、処理範囲0~a.length-1を渡してソートを実行します。

以上です。

関連コンテンツ

配列に格納されている値を順番に並び替える方法を解説しています。

2019.03.11

ソート(並び替え)アルゴリズムの1つであるバブルソート(bubble sort)について詳しく解説しています。Javaのソースコード付きです。

2023.01.13

配列に格納されている数値をランダムに並び替える方法を詳しく解説しています。ソースコード付きです。

2016.01.31

同じ型の変数(データ)を複数個まとめて管理するデータの持ちかたがあります。これが配列です。くわしくは、記事をご覧ください。

2016.01.14

2つの変数の値を入れ替える方法を紹介しています。

2016.01.29

処理を繰り返すために使用するfor文について解説しています。

2020.03.23

条件式を判断して処理を分岐する方法を詳しく説明しています。

2023.03.20

プログラミングで使う変数って何?

2020.03.23

Javaの学習に役立つソースコードを多数紹介しています。是非、ご覧ください。

2022.09.10

Javaを使った簡単な応用プログラム(生年月日から年齢を計算プログラムなど)を紹介しています。

2022.07.07

画像フォーマット形式・色・大きさ・傾きなどの変更、特定の図形(文字・記号など)を見つけたり、取り出したりする画像処理について詳しく解説。

2015.11.29

プログラミング、ITに関する用語をまとめています。

2022.10.17

メソッドを抜けるときに使用するreturn文について説明しています。

2020.03.20

繰り返し処理(ループ)から強制的に抜けかたについて解説しています。

2017.07.14

変数やクラスに格納されている値をコンソール出力する方法は?

2020.03.23

プログラムの最初に実行されるメソッドは?

2022.12.13

Javaのプログラムを書いてみませんか?プログラムの書き方をくわしく説明しています。

2020.03.23

「Javaソースコード」から実行可能な「オブジェクトコード」に変換する方法をくわしく説明しています。

2020.03.23

Javaのプログラムを作ってみませんか?プログラミングに必要なものの用意から実行までを説明しています。

2020.03.23

Swingパッケージを使ってグラフィック表示を行う方法を解説しています。

2020.03.23

繰り返し処理を使ったJavaのソースコードサンプルを紹介しています。

2020.03.23

配列を使うJavaソースコードを多数紹介しています。

2021.05.18

数学に関係するJavaのメソッドやソースコードなどを紹介しています。

2022.10.25

三角形、台形、円などいろいろな図形の面積を計算するプログラムを紹介しています。詳しくは、記事をご覧ください。

2021.05.18

StringクラスとStringBuilderクラスを利用したプログラミングの仕方を紹介しています。

2016.12.16

日本で使われてきた伝統文様「和柄」について解説しています。

2022.07.27

繰り返し処理の作り方を解説しています。

2016.03.02

2つの値のうち、小さい方の値と、大きい方の値を取得する方法。

2020.03.23

自然数と整数って何が違う?

2020.03.23

プログラミング言語とは?種類や特徴について説明しています。

2022.08.03

Javaプログラムの構成について解説しています。詳しくは、こちらをご覧ください。

2020.03.23

メソッドの定義方法を詳しく解説しています。Javaのサンプルソースコードを使った説明もあります。

2020.03.23

整数型の変数に1を足すインクリメント、1つ引くデクリメントについて詳しく説明しています。

2020.03.23

「ゆるゆるプログラム」のコンテンツを紹介しています。興味のある方はこの記事をご覧ください。

2020.03.23

アルゴリズムって何?

2022.12.29

広告