データ構造とアルゴリズムの基礎 (Wang Zhuo) (32): 直接挿入ソートのアイデアのレビュー、個人版の最終解答

本質: アルゴリズムの概要、帰納および相違点のレビュー:

目次

本質: アルゴリズムの概要、帰納および相違点のレビュー:

標準的な回答のアイデア:

私の答えのアイデア:

違いと問題点:

(1):

[順序なしシーケンス(センチネル)の最初の要素]が[順序付きシーケンスの最後の要素]より小さい場合のみ

Sentinel を有効にしたところです

(2): while (j>0) は修飾されたループ判定文ではありません

(3): [順序付きシーケンスの最後の要素] を [順序なしシーケンスの最初の要素] の位置に 1 回コピーするだけで済みます。

(4): i が要素の位置 (要素の数) を示すか、ビット列 (配列の添え字) を示すかは、実際には次のようになります。

上記の問題の表現に従って、最初の改訂版のプログラムは次のように書き換えられます。

まだ問題が残っています:

(5): 判定文の前 while (Lr[j].key >= Lr[0].key) センチネルには値が代入されていません

(6): 現時点では、センチネルをいつ使用するかについて、状態の範囲をどのように決定するかが新たな問題となっています。

(7): [センチネル要素] が [比較する要素] より小さいというステートメントは、次のようにすべきではありません。

(8): j = i - 1 は次のように変更されるべきです: j = i (疑わしい)

(9): while ループ内の 2 つのステートメントの順序は逆にできないことに注意してください。

(10): 最後のステップで要素を挿入する操作に [j + 1] が使用されるのはなぜですか

個人版の最終回答:



標準的な回答のアイデア:

初めて、2 番目の要素から開始して 1 つずつ逆方向にトラバースします。

各トラバースの番号 (i) から、次のことを覚えておいてください。

開始要素と開始前のいくつかの (i) 要素は順序付けられたシーケンスです

[順序なしシーケンスの最初の要素] が [順序付きシーケンスの最後の要素] より小さい場合は、センチネルを使用します。

(i+1) 番目の要素は (i) 番目の要素より小さい

From: 各比較でソートされたシーケンスの最後の要素 (i) を指します。

ポイントされた要素がセンチネル要素より大きいかどうかを比較し、大きい場合は次のようにします。

[順序付きシーケンスの最後の要素] を [順序なしシーケンスの最初の要素] の位置に 1 回コピーします

(j) を (j + 1) にコピー [ == センチネル要素]

ポイントされた前の要素がセンチネル要素より大きいかどうかを比較し、大きい場合は上記のループ操作を繰り返します。

センチネルより大きい最後の要素まで (<= 停止) まで前方比較を続けます。
 

センチネル要素を次の場所に挿入します。

センチネルより大きい最後の要素の前
センチネルより小さい最初の要素の後


私の答えのアイデア:

センチネル (ビット順序 0) 要素とポインター j が指す要素を比較します。

以上の場合: (センチネル要素がポインタ j が指す要素以上である)
【要素の交換】

j と j の後の順序付けされたシーケンスのすべての要素を 1 ビット後ろに移動します

(挿入する新しい要素は既にセンチネルに配置されているため、1 ビット前に戻ったときにデータが失われるかどうかを心配する必要はありません)

ポインタ j が指す位置にセンチネル要素を挿入します。
 

以下の場合: (センチネル要素はポインタ j が指す要素より小さい)
[比較を続行、前の要素と比較]

j--;


違いと問題点:

(1):

[順序なしシーケンス(センチネル)の最初の要素]が[順序付きシーケンスの最後の要素]より小さい場合のみ

Sentinel を有効にしたところです

この書き方の方が効率的です


(2): while (j>0) は修飾されたループ判定文ではありません

サイクルを比較するたびに、最終的には比較しなければならない可能性はありますか? ? ?

 ここで私たちは突然次のことに気づきました。

実際、[センチネル要素] が [比較する要素] より小さい場合にのみ、コピー バックアップと挿入操作を実行します。

したがって、ループ条件を次のように変更する必要があります。

        while (L.r[j].key >= L.r[0].key)

 書く必要はありません

            else if (L.r[j].key < L.r[0].key)
                j--;

(3): [順序付きシーケンスの最後の要素] を [順序なしシーケンスの最初の要素] の位置に 1 回コピーするだけで済みます。

私の思考の流れでは:

[順序なしシーケンス (センチネル) の最初の要素] が [順序付きシーケンスの最後の要素] より小さい限り、毎回

順序付けられたシーケンス内の要素の後ろにあるすべての要素が 1 ビット前に移動されます


必要ない、時間の無駄

次のことだけを行う必要があります:

[順序付きシーケンスの最後の要素] を [順序なしシーケンスの最初の要素] の位置に 1 回コピーします

できる


これが問題にならない理由:

実際、私たちのアルゴリズムは次のように動作します。

まず、後続の各要素がコピーされてバックアップされた後、次の要素をコピーしてバックアップする必要があるかどうかを確認します。

以前にバックアップされていない要素があるかどうかを心配する必要はありません。これは間違った命題です。


(4): i が要素の位置 (要素の番号) を示すか、ビット列 (配列の添え字) を示すかは、実際には次のようになります。

プログラムのさまざまなニーズに応じてプログラムを設定および調整できます


上記の問題の表現に従って、最初の改訂版のプログラムは次のように書き換えられます。

void InsertSort(SqList& L) 
{
    int i;
    for (i = 1; i < L.length; i++)
        //i代表的是位序
    {
        int j = i - 1;
        while (L.r[j].key >= L.r[0].key)
        {
            L.r[0].key = L.r[i].key;
            L.r[j + 1].key = L.r[j].key;
            j--;
        }
        L.r[j + 1].key = L.r[0].key;
    }
}

まだ問題が残っています:


(5): 判定文の前 while (Lr[j].key >= Lr[0].key) センチネルには値が代入されていません

つまり、実際には、この時点では歩哨はまったく存在しません


(6): 現時点では、センチネルをいつ使用するかについて、状態の範囲をどのように決定するかが新たな問題となっています。

(実際、ここでの私たちの考え方や考え方は標準的な答えの実践に基づいています)

まず第一に、1 つずつ逆方向にトラバースしているので、次のようになります。

別の状況/問題が存在するはずなので、ターゲット オブジェクトに対して特別な/別の操作を実行します。

したがって、ここでの状況は次のようになります。

キューは以前の順序付けられた性質を維持しなくなり、次のことが起こります。

後続の要素が先行する 1 つ以上の要素より小さい場合

現時点では、この状況に対応して作成する対応する操作アルゴリズムの範囲は、単純なセンチネルの割り当て操作以上のものでなければなりません

以前の設計アイデアを再参照すると、すべてのアルゴリズム操作が直接挿入されます。

[順序付けされたシーケンスの最後の要素] を [順序付けされていないシーケンスの最初の要素] の位置にコピーする/順序付けされたシーケンスのすべての要素を j と j の後に 1 ビット後方に移動する センチネル要素を挿入する: 最後の方が大きい

センチネルよりも
前の要素の後の
最初の要素よりも小さい

明らかにすべては状況の範囲内です


(7): [センチネル要素] が [比較する要素] より小さいというステートメントは、次のようにすべきではありません。

        if (Lr[i].key < Lr[i - 1].key)

を次のように変更する必要があります。

        if (Lr[i + 1].key < Lr[i].key)

なぜなら、必要なのは次のとおりです。

[センチネル要素] は [比較する要素] より小さいです

要素の位置に対応して、次のようになります。

(i) 番目は (i - 1) 番目より小さい

配列の内部ビット順序に対応するものは次のとおりです。

【i + 1】小于【 i 】


(8): j = i - 1 は次のように変更されるべきです: j = i (疑わしい)

j はポイントされた要素のビット順序を表し、j の開始/初期値は i と揃えられる必要があります。

前の標準的な答えが j = i - 1 である理由については、次のようになります。

前の標準解答 i は要素(数値)の位置を示します

疑わしい:

Github 上のバージョンでも i は 1 から始まりますが、これも i - 1 を使用します。


(9): while ループ内の 2 つのステートメントの順序は逆にできないことに注意してください。

同時に、この機会を利用して、for ステートメントの語順実行フローも確認します。

for([式 1];[式 2];[式 3]) ステートメント

実行順序:

  1. 式 1 を解く
  2. true (0 ではない) 場合、式 2 を解きます。
  3. ステートメント a [for ステートメントで指定された埋め込みステートメント (つまり、最後のステートメント)] を実行します。
  4. 式3を解く(実行する)

(10): 最後のステップで要素を挿入する操作に [j + 1] が使用されるのはなぜですか

理由は、[j + 1] が使用される前の標準的な回答と同様です (同じ)

ここでは繰り返しません


個人版の最終回答:

void InsertSort(SqList& L) 
{
    for (int i = 1; i < L.length; i++)
        //i代表的是位序
    {
        int j = i;
        if (L.r[i + 1].key < L.r[i].key)
        {
            L.r[0].key = L.r[i].key;
            while (L.r[j].key >= L.r[0].key)
            {
                L.r[j + 1].key = L.r[j].key;
                j--;
            }
            L.r[j + 1].key = L.r[0].key;
        }
    }
}

アルゴリズムの時間計算量: O(n^2)、空間計算量: O(1)


以上

おすすめ

転載: blog.csdn.net/Zz_zzzzzzz__/article/details/130351560