序文
この記事は、著者の学習経験とソート アルゴリズムの挿入ソートにおけるヒル ソートの経験を共有するために C 言語に基づいています。レベルが限られているため、間違いは避けられません。修正や交換を歓迎します。
ヒルソート
ヒル ソート法は、縮小増分法としても知られています。Hill ソート法の基本的な考え方は次のとおりです。まず、ソート対象のレコードのシーケンス全体を直接挿入ソートのためにいくつかのサブシーケンスに分割し、シーケンス全体のレコードが「基本的に順序付けされている」場合に、直接挿入ソートを実行します。すべてのレコードを順に表示します。
ヒル ソートは、挿入ソートの次の 2 つの特性に基づいて改良された方法です。
- 挿入ソートは、ほぼソートされたデータを操作する場合に効率的です。つまり、線形ソートの効率を達成できます。
- ただし、挿入ソートは一度に 1 ビットしかデータを移動できないため、一般に非効率的です。
なぜ縮小増分法とも呼ばれるのでしょうか? 直接挿入ソートを見てみましょう。比較するたびに要素は 1 つずつ移動しますか? 実際、直接挿入による並べ替えのデフォルトの増分 (ギャップで表される) は 1 で、つまり、1 つおきの要素が比較されます。ヒルソートとは、最初に設定した増分値から配列に対して直接挿入ソートを行い、ソートが完了するたびに増分ギャップを減らしていき、最後は1ずつ増分した直接挿入ソートとなります。
手順:
-
事前ソートの目的: 順序を近い順序に保つこと
ギャップ間隔のあるデータを 1 つのグループに分割し、合計でギャップ グループが存在し、各ラウンドで各グループのデータを直接挿入してソートし、ラウンドごとにギャップを減らします。
-
直接挿入ソート
ギャップが 1 に減少すると、最後の直接挿入ソートが実行されます。
次の例に従ってプロセスを説明します (昇順が必要です)。
最初にギャップを 3 として選択し、図に示すようにグループができました。異なる色の数字は異なるグループを表します。実際、各グループのソートは、直接挿入ソートの改良版と同等です。ギャップの値を 1 から Got 3 に変更します。
int gap = 3;
//单轮排序
for(int j = 0; j < gap; ++j)
{
//单组排序
for(int i = 0; i < n - gap; i += gap)
{
int end = i;
int tmp = arr[end + gap];
while(end >= 0)
{
if(tmp < arr[end])
{
arr[end + gap] = arr[end];
}
else
break;
end -= gap;
}
arr[end + gap] = tmp;
}
}
ここでギャップの初期値を取得し、最終的にギャップが 1 になるようにsz / 3
毎回 1 を加算します。gap = gap / 3 + 1
assert は arr ポインタが空かどうかを検出するもので、通常の状況では空であることは不可能です。
void ShellSort(int* arr, int sz)
{
assert(arr);
int gap = sz;
while(gap > 1)
{
gap = gap / 3 + 1;
//单轮排序
for(int j = 0; j < gap; ++j)
{
//单组排序
for(int i = j; i < n - gap; i += gap)
{
int end = i;
int tmp = arr[end + gap];
while(end >= 0)
{
if(tmp < arr[end])
arr[end + gap] = arr[end];
else
break;
end -= gap;
}
arr[end + gap] = tmp;
}
}
}
}
このように実装すると、コードがちょっと...ハッタリっぽく見えることがわかりました。一見すると、4 つのループのセットではありませんか? これはグループで行われますが、コードをより「快適」に見せる方法はありますか?
ギャップグループのデータを複数のグループで並べて実行できますが、これは何を意味しますか? 次の図を例に挙げます。毎回 1 ステップだけ後退し、赤グループのデータに遭遇した場合は赤グループの直接挿入ソートを実行し、青グループのデータに遭遇した場合は青グループを実行します。黒人グループは黒人グループをやります。終了するたびに一巡の仕分けです。
void ShellSort(int* arr, int sz)
{
assert(arr);
int gap = sz;
while(gap > 1)
{
gap = gap / 3 + 1;
//单轮排序
for(int i = 0; i < n - gap; ++i)
{
int end = i;
int tmp = arr[end + gap];
while(end >= 0)
{
if(tmp < arr[end])
arr[end + gap] = arr[end];
else
break;
end -= gap;
}
}
}
}
私たちが見つけたのは:
ギャップが大きいほど、大きなデータはより速く後方にジャンプでき、小さなデータはより速く前にジャンプできます (たとえば、ギャップが 3 の場合、一度に 3 つの要素位置にまたがって並べ替えることができ、ギャップが 1 の場合、かなり遅くなります)。
ギャップが小さいほど、ジャンプは遅くなり (たとえば、ギャップが 1 の場合、一度に 1 つの要素のみを並べ替えることができます)、順序に近づきます。
注:
-
ギャップ>1は事前ソートです
-
ギャップ == 1 は直接挿入ソートです
-
最後のギャップが 1 であることを確認する必要があり、ギャップの値には多くのオプションがありますが、ここでは
gap = sz / 2
sumgap /= 2
またはgap = sz / 3
sumをお勧めしますgap = gap / 3 + 1
。
ヒルソートの特徴の概要:
- ヒル ソートは直接挿入ソートを最適化したものです。
- ギャップ > 1 の場合、事前に並べ替えられます。目的は、配列を順序に近づけることです。ギャップ == 1 の場合、配列はすでに順序に近いため、
非常に高速になります。これにより、全体の最適化効果が得られる。実装した後は、パフォーマンス テストを比較できます。 - ヒル ソートの時間計算量を計算するのは簡単ではありません。ギャップを評価する方法が多数あり、それが計算を困難にするためです。そのため、多くの書籍で示されている
ヒル ソートの時間計算量は固定されていません。
私たちのギャップは Knuth によって提案された方法に従って評価されており、Knuth は多数の実験統計を行っているため、次に従って一時的に計算します: O(n 1.25 ) から O(1.6n 1.25 )、効率とO ( nlogn ) 同様に、データ量が多い場合は O(nlogn) よりも若干悪くなります。いずれの場合でも、ヒル ソートは時間計算量でO(n 2 ) を突破します。
- 安定性: 不安定
ご覧いただきありがとうございます、あなたのサポートが私の最大の励みです〜