AIZU ONLINE JUDGE

AOJ ALDS1 6_C(Quick Sort)を解く

AOJ_ALDS1_6_C

Aizu Online Judge(AOJ)が提供している「アルゴリズムとデータ構造入門」(ALDS1)の6_C問題をC++で解いてみました。

ALDS1では、「アルゴリズムとデータ構造の基礎を体得します。」とあります。トピック#6「ソート」では、実用的なソートアルゴリズムについて紹介します。

問題(6_C: Quick Sort)

問題はリンク先をご覧ください。

AOJ ALDS1 6_C問題: Quick Sort

クイックソートについて学びます。

クイックソートについて

全体を2つに分割して、それぞれをソートします。分割は再帰的に行います。この考え方は、マージソートと同じです。ただし、外部メモリを必要とせず、与えられた配列内で入れ替えをします(partition)。このため、離れた要素を入れ替える場合があり、安定なソートではなくなります。

以下の擬似コードを実装します。関数 partition は、6_B問題で紹介済です。

partition(A, p, r)
    x = A[r]
    i = p-1
    for j = p to r-1
        if A[j] <= x
            i = i+1
            A[i] と A[j] を交換
    A[i+1] と A[r] を交換
    return i+1

quicksort(A, p, r)
    if p < r
         q = partition(A, p, r)
         quickSort(A, p, q-1)
         quickSort(A, q+1, r)

クイックソートの特徴は以下です。

  • 平均の計算量は、$O(N \log N)$、ただし、上述の擬似コードの場合、データの並びによっては、計算量が $O(N^2)$ となることがあります。これを避けるためには、基準(A[r])を中央値に近い値を選ぶなどの工夫をします。
  • 関数 partition で離れた位置の要素を入れ替えるため、安定なソートではない。
  • マージソートとは異なり外部メモリを必要としない。

C++ プログラム例(ALDS1 6_C)

上で説明した擬似コードをそのままC++として書き換えました(45ー67、88行目)。

結果が安定なソートとなっているかマージソートの結果と比較します。マージソートは、5_B問題で紹介したプログラムを流用しました(6ー43、84ー86行目)。

問題文で示されている擬似コードに合わせたため、マージソートでは、[0, n) を渡しています。一方、クイックソートでは、[0, n-1] を渡しています。

以下が、C++プログラムとなります。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

#define INFTY (1 << 30)
vector<pair<char, int>> L;
vector<pair<char, int>> R;
int cnt = 0;

void merge(vector<pair<char, int>>& a, int left, int mid, int right)
{
	int n1 = mid - left;
	int n2 = right - mid;
	for (int i = 0; i < n1; ++i) {
		L[i] = a[left + i];
	}
	for (int i = 0; i < n2; ++i) {
		R[i] = a[mid + i];
	}
	L[n1] = make_pair(' ', INFTY);
	R[n2] = make_pair(' ', INFTY);
	int i = 0;
	int j = 0;
	for (int k = left; k < right; ++k) {
		++cnt;
		if (L[i].second <= R[j].second) {
			a[k] = L[i++];
		} else {
			a[k] = R[j++];
		}
	}
}

void mergeSort(vector<pair<char, int>>& a, int left, int right)
{
	if (left + 1 < right) {
		int mid = (left + right) / 2;
		mergeSort(a, left, mid);
		mergeSort(a, mid, right);
		merge(a, left, mid, right);
	}
}

int partition(vector<pair<char, int>>& a, int p, int r)
{
	int x = a[r].second;
	int i = p - 1;
	for (int j = p; j < r; ++j) {
		if (a[j].second <= x) {
			++i;
			swap(a[i], a[j]);
		}
	}
	swap(a[i + 1], a[r]);

	return i + 1;
}

void quickSort(vector<pair<char, int>>& a, int p, int r)
{
	if (p < r) {
		int q = partition(a, p, r);
		quickSort(a, p, q - 1);
		quickSort(a, q + 1, r);
	}
}

int main()
{
	int n;
	cin >> n;
	vector<pair<char, int>> a(n);
	for (int i = 0; i < n; ++i) {
		char c;
		int number;
		cin >> c >> number;
		a[i] = make_pair(c, number);
	}

	vector<pair<char, int>> b(n);
	b = a;

	L.assign(n / 2 + 2, make_pair(' ', 0));
	R.assign(n / 2 + 2, make_pair(' ', 0));
	mergeSort(b, 0, n);

	quickSort(a, 0, n - 1);

	bool stable = true;
	for (int i = 0; i < n; ++i) {
		if (a[i] != b[i]) {
			stable = false;
		}
	}
	if (stable) {
		cout << "Stable" << endl;
	} else {
		cout << "Not stable" << endl;
	}

	for (int i = 0; i < n; ++i) {
		cout << a[i].first << " " << a[i].second << endl;
	}

	return 0;
}

上記プログラムは、AOJ で「AC(Accepted=正解)」と判定されました。

最後に

C++標準ライブラリでは、特定のアルゴリズムを指定していませんが、計算量を指定しています。このため、以下のような実装が多いようです(cpprefjp 様の記述を引用させていただきました。)。

  • sort: クイックソートの改良版であるイントロソートが使われることが多い
      イントロソートでは、クイックソートとヒープソートを組み合わせる。
  • stable_sort: 一般的なマージソートで実装される。

引き続き、ALDS1 の問題を紹介していきます。

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA