私は問題を解決していますが、10個の数値(int32)を非常に迅速にソートする必要があります。私のアプリケーションは、可能な限り高速で何百万回も10個の数字をソートする必要があります。私は数十億個の要素のデータセットをサンプリングしており、その中から10個の数字を選択(簡略化)して並べ替える(そして並べ替えられた10個の要素のリストから結論を出す)必要があります。
現在、私は挿入ソートを使用していますが、挿入ソートに勝る10個の数字という特定の問題に対して、非常に高速なカスタムソートアルゴリズムを実装できると思います。
誰もこの問題に取り組む方法について何か考えがありますか?
(HelloWorldの提案に従い、ネットワークのソートを検討します。)
29入力の比較を行うには、29比較/スワップネットワークが最も速い方法のようです。 Javascriptのこの例では、1969年にWaksmanによって発見されたネットワークを使用しました。これは、if
ステートメント、比較、およびスワップの単なるリストであるため、Cに直接変換する必要があります。
function sortNet10(data) { // ten-input sorting network by Waksman, 1969
var swap;
if (data[0] > data[5]) { swap = data[0]; data[0] = data[5]; data[5] = swap; }
if (data[1] > data[6]) { swap = data[1]; data[1] = data[6]; data[6] = swap; }
if (data[2] > data[7]) { swap = data[2]; data[2] = data[7]; data[7] = swap; }
if (data[3] > data[8]) { swap = data[3]; data[3] = data[8]; data[8] = swap; }
if (data[4] > data[9]) { swap = data[4]; data[4] = data[9]; data[9] = swap; }
if (data[0] > data[3]) { swap = data[0]; data[0] = data[3]; data[3] = swap; }
if (data[5] > data[8]) { swap = data[5]; data[5] = data[8]; data[8] = swap; }
if (data[1] > data[4]) { swap = data[1]; data[1] = data[4]; data[4] = swap; }
if (data[6] > data[9]) { swap = data[6]; data[6] = data[9]; data[9] = swap; }
if (data[0] > data[2]) { swap = data[0]; data[0] = data[2]; data[2] = swap; }
if (data[3] > data[6]) { swap = data[3]; data[3] = data[6]; data[6] = swap; }
if (data[7] > data[9]) { swap = data[7]; data[7] = data[9]; data[9] = swap; }
if (data[0] > data[1]) { swap = data[0]; data[0] = data[1]; data[1] = swap; }
if (data[2] > data[4]) { swap = data[2]; data[2] = data[4]; data[4] = swap; }
if (data[5] > data[7]) { swap = data[5]; data[5] = data[7]; data[7] = swap; }
if (data[8] > data[9]) { swap = data[8]; data[8] = data[9]; data[9] = swap; }
if (data[1] > data[2]) { swap = data[1]; data[1] = data[2]; data[2] = swap; }
if (data[3] > data[5]) { swap = data[3]; data[3] = data[5]; data[5] = swap; }
if (data[4] > data[6]) { swap = data[4]; data[4] = data[6]; data[6] = swap; }
if (data[7] > data[8]) { swap = data[7]; data[7] = data[8]; data[8] = swap; }
if (data[1] > data[3]) { swap = data[1]; data[1] = data[3]; data[3] = swap; }
if (data[4] > data[7]) { swap = data[4]; data[4] = data[7]; data[7] = swap; }
if (data[2] > data[5]) { swap = data[2]; data[2] = data[5]; data[5] = swap; }
if (data[6] > data[8]) { swap = data[6]; data[6] = data[8]; data[8] = swap; }
if (data[2] > data[3]) { swap = data[2]; data[2] = data[3]; data[3] = swap; }
if (data[4] > data[5]) { swap = data[4]; data[4] = data[5]; data[5] = swap; }
if (data[6] > data[7]) { swap = data[6]; data[6] = data[7]; data[7] = swap; }
if (data[3] > data[4]) { swap = data[3]; data[3] = data[4]; data[4] = swap; }
if (data[5] > data[6]) { swap = data[5]; data[5] = data[6]; data[6] = swap; }
return(data);
}
alert(sortNet10([5,7,1,8,4,3,6,9,2,0]));
これは、独立したフェーズに分割されたネットワークのグラフィカルな表現です。
並列処理を活用するために、5-4-3-4-4-4-3-2のグループ化を4-4-4-4-4-4-3-2のグループ化に変更できます。
この固定サイズを扱うときは Sorting Networks をご覧ください。これらのアルゴリズムは実行時間が固定されており、入力に依存しません。あなたのユースケースでは、いくつかのソートアルゴリズムが持つようなオーバーヘッドはありません。
Bitonic sort は、そのようなネットワークの実装です。これは、CPUのlen(n)<= 32で最適に機能します。より大きな入力では、GPUへの移行を考えることができます。 https://en.wikipedia.org/wiki/Sorting_network
ところで、ソートアルゴリズムを比較するのに適したページはこちらです(ただし、bitonic sort
はありません)。
4つのグループで比較するソートネットワークを使用して、SIMDレジスタで比較できるようにします。パックされた最小/最大命令のペアは、パックされたコンパレータ機能を実装します。申し訳ありませんが、今私はこのことを覚えているページを探す時間がありませんが、SIMDまたはSSEソートネットワークで検索するとうまくいくと思います。
x86 SSEには、4つの32ビット整数のベクター用のパックド32ビット整数の最小および最大命令があります。 AVX2(Haswell以降)も同じですが、8 intの256bベクトル用です。効率的なシャッフル命令もあります。
多数の独立した小さなソートがある場合、ベクトルを使用して4または8のソートを並行して実行できる可能性があります。特に要素をランダムに選択している場合(したがって、並べ替えられるデータがメモリ内で連続しないように)、シャッフルを回避し、必要な順序で単純に比較できます。 10個のintの4つのリスト(AVX2:8)からのすべてのデータを保持する10個のレジスタは、スクラッチスペース用に6個のregを残します。
関連するデータも並べ替える必要がある場合、ベクトル並べ替えネットワークの効率は低下します。その場合、最も効率的な方法は、packed-compareを使用してどの要素が変更されたかのマスクを取得し、そのマスクを使用して関連データ(への参照)のベクトルをブレンドすることです。
展開されたブランチレスの選択ソートはどうですか?
#include <iostream>
#include <algorithm>
#include <random>
//return the index of the minimum element in array a
int min(const int * const a) {
int m = a[0];
int indx = 0;
#define TEST(i) (m > a[i]) && (m = a[i], indx = i );
//see http://stackoverflow.com/a/7074042/2140449
TEST(1);
TEST(2);
TEST(3);
TEST(4);
TEST(5);
TEST(6);
TEST(7);
TEST(8);
TEST(9);
#undef TEST
return indx;
}
void sort( int * const a ){
int work[10];
int indx;
#define GET(i) indx = min(a); work[i] = a[indx]; a[indx] = 2147483647;
//get the minimum, copy it to work and set it at max_int in a
GET(0);
GET(1);
GET(2);
GET(3);
GET(4);
GET(5);
GET(6);
GET(7);
GET(8);
GET(9);
#undef GET
#define COPY(i) a[i] = work[i];
//copy back to a
COPY(0);
COPY(1);
COPY(2);
COPY(3);
COPY(4);
COPY(5);
COPY(6);
COPY(7);
COPY(8);
COPY(9);
#undef COPY
}
int main() {
//generating and printing a random array
int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
std::random_device rd;
std::mt19937 g(rd());
std::shuffle( a, a+10, g);
for (int i = 0; i < 10; i++) {
std::cout << a[i] << ' ';
}
std::cout << std::endl;
//sorting and printing again
sort(a);
for (int i = 0; i < 10; i++) {
std::cout << a[i] << ' ';
}
return 0;
}
http://coliru.stacked-crooked.com/a/71e18bc4f7fa18c6
関連する行は、最初の2つの#define
のみです。
これは2つのリストを使用し、最初のリストを完全に10回再チェックします。これは選択ソートの実装方法としては不適切ですが、分岐と可変長ループを回避します。
ソーティングネットワークに対してベンチマークを実行しましたが、コードが遅いようです。ただし、展開とコピーを削除しようとしました。このコードの実行:
#include <iostream>
#include <algorithm>
#include <random>
#include <chrono>
int min(const int * const a, int i) {
int m = a[i];
int indx = i++;
for ( ; i<10; i++)
//see http://stackoverflow.com/a/7074042/2140449
(m > a[i]) && (m = a[i], indx = i );
return indx;
}
void sort( int * const a ){
for (int i = 0; i<9; i++)
std::swap(a[i], a[min(a,i)]); //search only forward
}
void sortNet10(int * const data) { // ten-input sorting network by Waksman, 1969
int swap;
if (data[0] > data[5]) { swap = data[0]; data[0] = data[5]; data[5] = swap; }
if (data[1] > data[6]) { swap = data[1]; data[1] = data[6]; data[6] = swap; }
if (data[2] > data[7]) { swap = data[2]; data[2] = data[7]; data[7] = swap; }
if (data[3] > data[8]) { swap = data[3]; data[3] = data[8]; data[8] = swap; }
if (data[4] > data[9]) { swap = data[4]; data[4] = data[9]; data[9] = swap; }
if (data[0] > data[3]) { swap = data[0]; data[0] = data[3]; data[3] = swap; }
if (data[5] > data[8]) { swap = data[5]; data[5] = data[8]; data[8] = swap; }
if (data[1] > data[4]) { swap = data[1]; data[1] = data[4]; data[4] = swap; }
if (data[6] > data[9]) { swap = data[6]; data[6] = data[9]; data[9] = swap; }
if (data[0] > data[2]) { swap = data[0]; data[0] = data[2]; data[2] = swap; }
if (data[3] > data[6]) { swap = data[3]; data[3] = data[6]; data[6] = swap; }
if (data[7] > data[9]) { swap = data[7]; data[7] = data[9]; data[9] = swap; }
if (data[0] > data[1]) { swap = data[0]; data[0] = data[1]; data[1] = swap; }
if (data[2] > data[4]) { swap = data[2]; data[2] = data[4]; data[4] = swap; }
if (data[5] > data[7]) { swap = data[5]; data[5] = data[7]; data[7] = swap; }
if (data[8] > data[9]) { swap = data[8]; data[8] = data[9]; data[9] = swap; }
if (data[1] > data[2]) { swap = data[1]; data[1] = data[2]; data[2] = swap; }
if (data[3] > data[5]) { swap = data[3]; data[3] = data[5]; data[5] = swap; }
if (data[4] > data[6]) { swap = data[4]; data[4] = data[6]; data[6] = swap; }
if (data[7] > data[8]) { swap = data[7]; data[7] = data[8]; data[8] = swap; }
if (data[1] > data[3]) { swap = data[1]; data[1] = data[3]; data[3] = swap; }
if (data[4] > data[7]) { swap = data[4]; data[4] = data[7]; data[7] = swap; }
if (data[2] > data[5]) { swap = data[2]; data[2] = data[5]; data[5] = swap; }
if (data[6] > data[8]) { swap = data[6]; data[6] = data[8]; data[8] = swap; }
if (data[2] > data[3]) { swap = data[2]; data[2] = data[3]; data[3] = swap; }
if (data[4] > data[5]) { swap = data[4]; data[4] = data[5]; data[5] = swap; }
if (data[6] > data[7]) { swap = data[6]; data[6] = data[7]; data[7] = swap; }
if (data[3] > data[4]) { swap = data[3]; data[3] = data[4]; data[4] = swap; }
if (data[5] > data[6]) { swap = data[5]; data[5] = data[6]; data[6] = swap; }
}
std::chrono::duration<double> benchmark( void(*func)(int * const), const int seed ) {
std::mt19937 g(seed);
int a[10] = {10,11,12,13,14,15,16,17,18,19};
std::chrono::high_resolution_clock::time_point t1, t2;
t1 = std::chrono::high_resolution_clock::now();
for (long i = 0; i < 1e7; i++) {
std::shuffle( a, a+10, g);
func(a);
}
t2 = std::chrono::high_resolution_clock::now();
return std::chrono::duration_cast<std::chrono::duration<double>>(t2 - t1);
}
int main() {
std::random_device rd;
for (int i = 0; i < 10; i++) {
const int seed = rd();
std::cout << "seed = " << seed << std::endl;
std::cout << "sortNet10: " << benchmark(sortNet10, seed).count() << std::endl;
std::cout << "sort: " << benchmark(sort, seed).count() << std::endl;
}
return 0;
}
ソートネットワークと比較して、ブランチレス選択ソートの結果が一貫してより良い結果になっています。
$ gcc -v
gcc version 5.2.0 (GCC)
$ g++ -std=c++11 -Ofast sort.cpp && ./a.out
seed = -1727396418
sortNet10: 2.24137
sort: 2.21828
seed = 2003959850
sortNet10: 2.23914
sort: 2.21641
seed = 1994540383
sortNet10: 2.23782
sort: 2.21778
seed = 1258259982
sortNet10: 2.25199
sort: 2.21801
seed = 1821086932
sortNet10: 2.25535
sort: 2.2173
seed = 412262735
sortNet10: 2.24489
sort: 2.21776
seed = 1059795817
sortNet10: 2.29226
sort: 2.21777
seed = -188551272
sortNet10: 2.23803
sort: 2.22996
seed = 1043757247
sortNet10: 2.2503
sort: 2.23604
seed = -268332483
sortNet10: 2.24455
sort: 2.24304
質問は、これが何らかのWebベースのアプリケーションであるとは言っていません。私の目に留まったのは、次の1つです。
私は数十億個の要素のデータセットをサンプリングしており、そこから10個の数字を選び(簡略化)、ソートする必要があります(そしてソートされた10個の要素リストから結論を出します)。
ソフトウェアとハードウェアのエンジニアとして、これは絶対に悲鳴を上げます"FPGA"並べ替えられた数字のセットからどのような結論を引き出す必要があるのか、データがどこから来たのかはわかりませんが、1億から10億これらの「ソートと分析」操作のper second。過去にFPGAを使用したDNAシーケンス作業を行ってきました。問題がそのタイプのソリューションに適している場合、FPGAの大規模な処理能力を打ち負かすことはほぼ不可能です。
あるレベルでは、唯一の制限要因は、FPGAにデータをシャベルで送信する速度と、データを取り出す速度です。
参考として、32ビットRGB画像データを約3億ピクセル/秒の速度で受信する高性能リアルタイム画像プロセッサを設計しました。 FIRフィルター、行列乗算器、ルックアップテーブル、空間エッジ検出ブロック、および他の多くの操作を通じてストリームされたデータは、もう一方の端から出力されます。これらすべては、内部クロックが約33MHzから、私が正しく覚えていれば400MHzまでの比較的小さなザイリンクスVirtex2 FPGAで行われます。ああ、そうです、DDR2コントローラーの実装もあり、DDR2メモリの2つのバンクを実行しました。
FPGAは、数百MHzで動作しながら、クロックの遷移ごとに10個の32ビット数を出力できます。データが処理パイプラインを埋めるため、操作の開始時に短い遅延が発生します。その後、クロックごとに1つの結果を取得できるはずです。または、並べ替えと分析のパイプラインを複製することで処理を並列化できる場合はそれ以上です。解決策は、原則として、ほとんど些細なことです。
重要なのは、アプリケーションがPCにバインドされておらず、データストリームと処理がFPGAソリューション(スタンドアロンまたはマシンのコプロセッサカード)と「互換性がある」場合は、その方法はありません。アルゴリズムに関係なく、任意の言語で作成されたソフトウェアを使用して、達成可能なパフォーマンスレベルを超えることができます。
編集:
クイック検索を実行して、あなたに役立つかもしれない論文を見つけました。 2012年までさかのぼるようです。今日(そして当時も)パフォーマンスを向上させることができます。ここにあります:
最近、 little class を作成しました。これは、Bose-Nelsonアルゴリズムを使用して、コンパイル時にソートネットワークを生成します。
10個の数字の非常に高速なソートを作成するために使用できます。
/**
* A Functor class to create a sort for fixed sized arrays/containers with a
* compile time generated Bose-Nelson sorting network.
* \tparam NumElements The number of elements in the array or container to sort.
* \tparam T The element type.
* \tparam Compare A comparator functor class that returns true if lhs < rhs.
*/
template <unsigned NumElements, class Compare = void> class StaticSort
{
template <class A, class C> struct Swap
{
template <class T> inline void s(T &v0, T &v1)
{
T t = Compare()(v0, v1) ? v0 : v1; // Min
v1 = Compare()(v0, v1) ? v1 : v0; // Max
v0 = t;
}
inline Swap(A &a, const int &i0, const int &i1) { s(a[i0], a[i1]); }
};
template <class A> struct Swap <A, void>
{
template <class T> inline void s(T &v0, T &v1)
{
// Explicitly code out the Min and Max to Nudge the compiler
// to generate branchless code.
T t = v0 < v1 ? v0 : v1; // Min
v1 = v0 < v1 ? v1 : v0; // Max
v0 = t;
}
inline Swap(A &a, const int &i0, const int &i1) { s(a[i0], a[i1]); }
};
template <class A, class C, int I, int J, int X, int Y> struct PB
{
inline PB(A &a)
{
enum { L = X >> 1, M = (X & 1 ? Y : Y + 1) >> 1, IAddL = I + L, XSubL = X - L };
PB<A, C, I, J, L, M> p0(a);
PB<A, C, IAddL, J + M, XSubL, Y - M> p1(a);
PB<A, C, IAddL, J, XSubL, M> p2(a);
}
};
template <class A, class C, int I, int J> struct PB <A, C, I, J, 1, 1>
{
inline PB(A &a) { Swap<A, C> s(a, I - 1, J - 1); }
};
template <class A, class C, int I, int J> struct PB <A, C, I, J, 1, 2>
{
inline PB(A &a) { Swap<A, C> s0(a, I - 1, J); Swap<A, C> s1(a, I - 1, J - 1); }
};
template <class A, class C, int I, int J> struct PB <A, C, I, J, 2, 1>
{
inline PB(A &a) { Swap<A, C> s0(a, I - 1, J - 1); Swap<A, C> s1(a, I, J - 1); }
};
template <class A, class C, int I, int M, bool Stop = false> struct PS
{
inline PS(A &a)
{
enum { L = M >> 1, IAddL = I + L, MSubL = M - L};
PS<A, C, I, L, (L <= 1)> ps0(a);
PS<A, C, IAddL, MSubL, (MSubL <= 1)> ps1(a);
PB<A, C, I, IAddL, L, MSubL> pb(a);
}
};
template <class A, class C, int I, int M> struct PS <A, C, I, M, true>
{
inline PS(A &a) {}
};
public:
/**
* Sorts the array/container arr.
* \param arr The array/container to be sorted.
*/
template <class Container> inline void operator() (Container &arr) const
{
PS<Container, Compare, 1, NumElements, (NumElements <= 1)> ps(arr);
};
/**
* Sorts the array arr.
* \param arr The array to be sorted.
*/
template <class T> inline void operator() (T *arr) const
{
PS<T*, Compare, 1, NumElements, (NumElements <= 1)> ps(arr);
};
};
#include <iostream>
#include <vector>
int main(int argc, const char * argv[])
{
enum { NumValues = 10 };
// Arrays
{
int rands[NumValues];
for (int i = 0; i < NumValues; ++i) rands[i] = Rand() % 100;
std::cout << "Before Sort: \t";
for (int i = 0; i < NumValues; ++i) std::cout << rands[i] << " ";
std::cout << "\n";
StaticSort<NumValues> staticSort;
staticSort(rands);
std::cout << "After Sort: \t";
for (int i = 0; i < NumValues; ++i) std::cout << rands[i] << " ";
std::cout << "\n";
}
std::cout << "\n";
// STL Vector
{
std::vector<int> rands(NumValues);
for (int i = 0; i < NumValues; ++i) rands[i] = Rand() % 100;
std::cout << "Before Sort: \t";
for (int i = 0; i < NumValues; ++i) std::cout << rands[i] << " ";
std::cout << "\n";
StaticSort<NumValues> staticSort;
staticSort(rands);
std::cout << "After Sort: \t";
for (int i = 0; i < NumValues; ++i) std::cout << rands[i] << " ";
std::cout << "\n";
}
return 0;
}
if (compare) swap
ステートメントの代わりに、minとmaxの三項演算子を明示的にコーディングしていることに注意してください。これは、コンパイラーがブランチレスコードを使用するように促すためです。
次のベンチマークはclang -O3でコンパイルされ、2012年半ばのMacbook Airで実行されました。
これをDarioPのコードと比較すると、サイズ10の100万個の32ビットint配列をソートするのにかかるミリ秒数は次のとおりです。
Hardcoded Sort Net 10:88.774 ms
テンプレート化されたボーズネルソン分類10:27.815 ms
このテンプレート化されたアプローチを使用すると、コンパイル時に他の数の要素のソートネットワークを生成することもできます。
さまざまなサイズの100万アレイをソートする時間(ミリ秒)。
サイズ2、4、8の配列のミリ秒数は、それぞれ1.943、8.655、20.246です。
展開された挿入ソートの Glenn Teitelbaum へのクレジット。
6要素の小さな配列のソートごとの平均クロックを以下に示します。ベンチマークコードと例は、次の質問で見つけることができます。
固定長6 int配列の最速のソート
Direct call to qsort library function : 326.81
Naive implementation (insertion sort) : 132.98
Insertion Sort (Daniel Stutzbach) : 104.04
Insertion Sort Unrolled : 99.64
Insertion Sort Unrolled (Glenn Teitelbaum) : 81.55
Rank Order : 44.01
Rank Order with registers : 42.40
Sorting Networks (Daniel Stutzbach) : 88.06
Sorting Networks (Paul R) : 31.64
Sorting Networks 12 with Fast Swap : 29.68
Sorting Networks 12 reordered Swap : 28.61
Reordered Sorting Network w/ fast swap : 24.63
Templated Sorting Network (this class) : 25.37
6要素の質問の最速の例と同じ速さで実行されます。
多くの場合、入力配列は既にソートされているか、ほとんどがソートされています。
このような場合、挿入ソートの方が適している場合があります。
データに応じて適切なソートアルゴリズムを選択できます。
ベンチマークに使用されるコードは here にあります。
ネットワークソートは小さなアレイでは高速になる可能性が高くなりますが、適切に最適化されていれば挿入ソートに勝てない場合があります。たとえば、2つの要素を含むバッチ挿入:
{
final int a=in[0]<in[1]?in[0]:in[1];
final int b=in[0]<in[1]?in[1]:in[0];
in[0]=a;
in[1]=b;
}
for(int x=2;x<10;x+=2)
{
final int a=in[x]<in[x+1]?in[x]:in[x+1];
final int b=in[x]<in[x+1]?in[x+1]:in[x];
int y= x-1;
while(y>=0&&in[y]>b)
{
in[y+2]= in[y];
--y;
}
in[y+2]=b;
while(y>=0&&in[y]>a)
{
in[y+1]= in[y];
--y;
}
in[y+1]=a;
}
insertion sort
を完全に展開できます
これを簡単にするために、再帰的なtemplate
sを関数のオーバーヘッドなしで使用できます。既にtemplate
であるため、int
はtemplate
パラメーターにもなります。これにより、10以外の配列サイズのコーディングも簡単に作成できます。
クラスが最後のアイテムのインデックスを使用するため、int x[10]
をソートするための呼び出しはinsert_sort<int, 9>::sort(x);
であることに注意してください。これはラップすることもできますが、それは読み通すためにより多くのコードになるでしょう。
template <class T, int NUM>
class insert_sort;
template <class T>
class insert_sort<T,0>
// stop template recursion
// sorting 1 item is a no-op
{
public:
static void place(T *x) {}
static void sort(T * x) {}
};
template <class T, int NUM>
class insert_sort
// use template recursion to do insertion sort
// NUM is the index of the last item, eg. for x[10] call <9>
{
public:
static void place(T *x)
{
T t1=x[NUM-1];
T t2=x[NUM];
if (t1 > t2)
{
x[NUM-1]=t2;
x[NUM]=t1;
insert_sort<T,NUM-1>::place(x);
}
}
static void sort(T * x)
{
insert_sort<T,NUM-1>::sort(x); // sort everything before
place(x); // put this item in
}
};
私のテストでは、これはソーティングネットワークの例よりも高速でした。
here で説明した理由と同様の理由により、次のソート関数sort6_iterator()
およびsort10_iterator_local()
は、ソートネットワークが here から取得された場合にうまく機能するはずです。
template<class IterType>
inline void sort10_iterator(IterType it)
{
#define SORT2(x,y) {if(data##x>data##y)std::swap(data##x,data##y);}
#define DD1(a) auto data##a=*(data+a);
#define DD2(a,b) auto data##a=*(data+a), data##b=*(data+b);
#define CB1(a) *(data+a)=data##a;
#define CB2(a,b) *(data+a)=data##a;*(data+b)=data##b;
DD2(1,4) SORT2(1,4) DD2(7,8) SORT2(7,8) DD2(2,3) SORT2(2,3) DD2(5,6) SORT2(5,6) DD2(0,9) SORT2(0,9)
SORT2(2,5) SORT2(0,7) SORT2(8,9) SORT2(3,6)
SORT2(4,9) SORT2(0,1)
SORT2(0,2) CB1(0) SORT2(6,9) CB1(9) SORT2(3,5) SORT2(4,7) SORT2(1,8)
SORT2(3,4) SORT2(5,8) SORT2(6,7) SORT2(1,2)
SORT2(7,8) CB1(8) SORT2(1,3) CB1(1) SORT2(2,5) SORT2(4,6)
SORT2(2,3) CB1(2) SORT2(6,7) CB1(7) SORT2(4,5)
SORT2(3,4) CB2(3,4) SORT2(5,6) CB2(5,6)
#undef CB1
#undef CB2
#undef DD1
#undef DD2
#undef SORT2
}
この関数を呼び出すには、std::vector
イテレーターを渡しました。
挿入ソートでは、10の入力をソートするために平均で29,6回の比較が必要であり、ベストケースは9、ワーストは45です(入力が逆の場合)。
{9,6,1}シェルソートでは、10個の入力をソートするために平均25.5回の比較が必要です。最良の場合は14回の比較、最悪の場合は34回、逆入力のソートには22回の比較が必要です。
したがって、挿入ソートの代わりにシェルソートを使用すると、平均ケースが14%減少します。最良の場合は56%増加しますが、最悪の場合は24%減少します。これは、最悪の場合のパフォーマンスを維持することが重要なアプリケーションでは重要です。逆の場合は51%減少します。
挿入ソートに精通しているように見えるため、{9,6}のソートネットワークとしてアルゴリズムを実装し、その後、挿入ソート({1})を追加できます。
i[0] with i[9] // {9}
i[0] with i[6] // {6}
i[1] with i[7] // {6}
i[2] with i[8] // {6}
i[3] with i[9] // {6}
i[0 ... 9] // insertion sort