using System; using System.Collections.Generic; static class Sort { #region common /// /// a と b の中身を入れ替える。 /// /// オペランドa /// オペランドb public static void Swap(ref T a, ref T b) { T c = a; a = b; b = c; } #endregion #region O(n^2) ソート /// /// バブルソート。 /// /// 対象の配列 public static void BubbleSort(T[] a) where T : IComparable { int n = a.Length; for (int i = 0; i < n - 1; i++) for (int j = n - 1; j > i; j--) if (a[j].CompareTo(a[j - 1]) < 0) Swap(ref a[j], ref a[j - 1]); } /// /// 選択ソート。 /// /// 対象の配列 public static void SelectSort(T[] a) where T : IComparable { int n = a.Length; for (int i = 0; i < n; i++) for (int j = 1; j < n - i; j++) if (a[j - 1].CompareTo(a[j]) > 0) Swap(ref a[j - 1], ref a[j]); } /// /// 挿入ソート。 /// /// 対象の配列 public static void InsertSort(T[] a) where T : IComparable { int n = a.Length; for (int i = 1; i < n; i++) for (int j = i; j >= 1 && a[j - 1].CompareTo(a[j]) > 0; --j ) Swap(ref a[j], ref a[j - 1]); } /// /// シェルソート。 /// /// 対象の配列 public static void ShellSort(T[] a) where T : IComparable { int n = a.Length; int h; for (h = 1; h < n / 9; h = h * 3 + 1) ; for (; h > 0; h /= 3) for (int i = h; i < n; i++) for (int j = i; j >= h && a[j - h].CompareTo(a[j]) > 0; j -= h) Swap(ref a[j], ref a[j - h]); } #endregion #region クイックソート・マージソート準備 /// /// クイックソートやマージソート → 挿入ソートに切り替える配列長の閾値。 /// const int THREASHOLD = 64; /// /// 挿入ソート。 /// 配列のどこからどこまでをソートするかを指定するバージョン。 /// /// 対象の配列 /// ソート対象の先頭インデックス /// ソート対象の末尾インデックス static void InsertSort(T[] a, int first, int last) where T : IComparable { for (int i = first + 1; i <= last; i++) for (int j = i; j > first && a[j - 1].CompareTo(a[j]) > 0; --j) Swap(ref a[j], ref a[j - 1]); } #endregion #region クイックソート /// /// クイックソート。 /// /// 対象の配列 public static void QuickSort(T[] a) where T : IComparable { QuickSort(a, 0, a.Length - 1); } /// /// クイックソート本体。 /// 配列のどこからどこまでをソートするかを指定するバージョン。 /// /// 対象の配列 /// ソート対象の先頭インデックス /// ソート対象の末尾インデックス static void QuickSort(T[] a, int first, int last) where T : IComparable { // 要素数が少なくなってきたら挿入ソートに切り替え if (last - first < THREASHOLD) { InsertSort(a, first, last); return; } // 枢軸決定(配列の先頭、ど真ん中、末尾の3つの値の中央値を使用。) T pivot = Median(a[first], a[(first + last) / 2], a[last]); // 左右分割 int l = first; int r = last; while(l <= r) { while (l < last && a[l].CompareTo(pivot) < 0) l++; while (r > first && a[r].CompareTo(pivot) >= 0) r--; if (l > r) break; Swap(ref a[l], ref a[r]); l++; r--; } // 再帰呼び出し QuickSort(a, first, l-1); QuickSort(a, l, last); } /// /// 3つの値の中央値を求める。 /// /// オペランドa /// オペランドb /// オペランドc /// 中央値 static T Median(T a, T b, T c) where T : IComparable { if (a.CompareTo(b) > 0) Swap(ref a, ref b); if (a.CompareTo(c) > 0) Swap(ref a, ref c); if (b.CompareTo(c) > 0) Swap(ref b, ref c); return b; } #endregion #region ヒープソート /// /// ヒープソート。 /// /// 対象の配列 public static void HeapSort(T[] a) where T : IComparable { for (int i = 1; i < a.Length; ++i) MakeHeap(a, i); for (int i = a.Length - 1; i >= 0; --i) a[i] = PopHeap(a, i); } /// /// 配列をヒープ化する。 /// n - 1 番目までの要素は既にヒープ化されていることを仮定して、 /// n 番目の要素をヒープに追加。 /// /// 対象の配列 /// 要素数 static void MakeHeap(T[] a, int n) where T : IComparable { while (n != 0) { int i = (n - 1) / 2; if (a[n].CompareTo(a[i]) > 0) Swap(ref a[n], ref a[i]); n = i; } } /// /// ヒープから最大値を取り出す。 /// /// 対象の配列 /// 要素数 - 1 /// 取り出した最大値 static T PopHeap(T[] a, int n) where T : IComparable { T max = a[0]; a[0] = a[n]; for (int i=0, j; (j = 2 * i + 1) < n; ) { if ((j != n - 1) && (a[j].CompareTo(a[j + 1]) < 0)) j++; if (a[i].CompareTo(a[j]) < 0) Swap(ref a[i], ref a[j]); i = j; } return max; } #endregion #region マージソート /// /// マージソート。 /// /// 対象の配列 public static void MergeSort(T[] a) where T : IComparable { T[] work = new T[a.Length / 2]; MergeSort(a, 0, a.Length, work); } /// /// マージソート。 /// 配列のどこからどこまでをソートするかを指定するバージョン。 /// /// 対象の配列 /// ソート対象部分の先頭 /// ソート対象部分の末尾+1 /// 作業領域。a の 1/2 のサイズが必要。 static void MergeSort(T[] a, int begin, int end, T[] work) where T : IComparable { if (end - begin < THREASHOLD) { InsertSort(a, begin, end - 1); return; } int mid = (begin + end) / 2; MergeSort(a, begin, mid, work); MergeSort(a, mid, end, work); Merge(a, begin, mid, end, work); } /// /// 配列 a の、[begin, mid) の部分と [mid, end) の部分をマージ。 /// /// /// マージ対象の配列 /// aの先頭 /// aの分割点 /// aの末尾+1 /// 作業領域 static void Merge(T[] a, int begin, int mid, int end, T[] work) where T : IComparable { int i, j, k; for (i = begin, j = 0; i != mid; ++i, ++j) work[j] = a[i]; mid -= begin; for (j = 0, k = begin; i != end && j != mid; ++k) { if (a[i].CompareTo(work[j]) < 0) { a[k] = a[i]; ++i; } else { a[k] = work[j]; ++j; } } for (; i < end; ++i, ++k) a[k] = a[i]; for (; j < mid; ++j, ++k) a[k] = work[j]; } #endregion #region バケットソート /// /// [0, max] の範囲の整数をバケットソート。 /// /// 対象の配列 /// 配列 a 中の最大値 public static void BucketSort(int[] a, int max) { // バケツを用意 int[] bucket = new int[max + 1]; // バケツに値を入れる for (int i = 0; i < a.Length; ++i) ++bucket[a[i]]; // バケツ中の値の結合 for (int j = 0, i = 0; j < bucket.Length; ++j) for (int k = bucket[j]; k != 0; --k, ++i) a[i] = j; } /// /// [0, max] の範囲の整数をキーに持つデータ構造をバケットソート。 /// /// 値の型 /// 対象の配列 /// キーの最大値 public static void BucketSort(KeyValuePair[] a, int max) { // バケツを用意 List[] bucket = new List[max + 1]; // バケツに値を入れる for (int i = 0; i < a.Length; ++i) { if (bucket[a[i].Key] == null) bucket[a[i].Key] = new List(); bucket[a[i].Key].Add(a[i].Value); } // バケツ中の値の結合 for (int j = 0, i = 0; j < bucket.Length; ++j) if(bucket[j] != null) foreach (T val in bucket[j]) a[i++] = new KeyValuePair(j, val); } #endregion #region 基数ソート /// /// 基数ソート。 /// 概念説明用の簡易版。 /// 10進数で3桁(0~999)までしかソートできない。 /// /// 対象の配列 /// 配列 a 中の最大値 public static void RadixSort10(int[] a) { // バケツを用意 List[] bucket = new List[10]; for (int d = 0, r = 1; d < 3; ++d, r *= 10) { // バケツに値を入れる for (int i = 0; i < a.Length; ++i) { int key = (a[i] / r) % 10; // a[i] の d 桁目だけを取り出す。 if (bucket[key] == null) bucket[key] = new List(); bucket[key].Add(a[i]); } // バケツ中の値の結合 for (int j = 0, i = 0; j < bucket.Length; ++j) if (bucket[j] != null) foreach (int val in bucket[j]) a[i++] = val; // バケツを一度空にする for (int j = 0; j < bucket.Length; ++j) bucket[j] = null; } } /// /// 基数ソート。 /// /// 対象の配列 /// 配列 a 中の最大値 public static void RadixSort(int[] a) { // バケツを用意 List[] bucket = new List[256]; for (int d = 0, logR = 0; d < 4; ++d, logR += 8) { // バケツに値を入れる for (int i = 0; i < a.Length; ++i) { int key = (a[i] >> logR) & 255; // a[i] を256進 d 桁目だけを取り出す。 if (bucket[key] == null) bucket[key] = new List(); bucket[key].Add(a[i]); } // バケツ中の値の結合 for (int j = 0, i = 0; j < bucket.Length; ++j) if (bucket[j] != null) foreach (int val in bucket[j]) a[i++] = val; // バケツを一度空にする for (int j = 0; j < bucket.Length; ++j) bucket[j] = null; } } #endregion }