データ構造とアルゴリズムの基礎 (Wang Zhuo) (36): 為替ソートのクイック ソート [フェーズ 3: 問題を解決するための深掘り] の本質! エッセンス!エッセンス!!!大事なことは3回言う

目次

レビュー:

具体的な問題:

動作コア:

ノート:

操作の内訳:

操作の実装:

質問(1): 異なる時点での if/else 判断を行う

問題 (2): 「センチネルより小さい要素」として条件を渡すと機能しない

質問(3):

ループ条件(while)に「以上」を使用した場合のプログラム(フレームワーク)の動作原理

初めに

後で私たちは知りました

ついにここで終わると思いましたか?

しかしそうではなかった

最大の問題:

当初(以前)、私たちは次のように考えていました。

そして後で、実際にはそれほど単純ではないことがわかりました。

本当の問題:

問題をプログラム フローで具体化します。

(1):

(2):

具体化の完全なプロセス:

(1):

(2):

次のステップ(ポインタ変更操作)に進む前に、空白を含む次の要素が見つかるかどうかは確認されていません

代わりに、次の要素が比較されるか、要素が交換されるか、位置ポインタが移動される限り、次のステップが実行されます。

最終結果を次のように変更します。

ポインタ操作を変更するのは、センチネルより大きい/小さい要素が確実に見つかり、スペースが挿入されていることを確認した後のみです。

ただし、以前のプログラムでは、ポインタが見つかったか交換されたかに関係なく、ポインタを変更する操作が実行されます。

実際、変更したい結果は、次のステップ (次のラウンド) に進む前にスペースを挿入する操作が実行されることをプログラムに判断させることです。

標準的な答え:


レビュー:

具体的な問題:

ロジックメカニズムの操作の問題については、PPT の例を例として挙げてみましょう。

それぞれの特定の操作をテーブルに変換し、次のように表示します。 

最初の数ステップ 操作する 低いポイントは次のとおりです 高いポイント
ステップ1 49 センチネル変更 1 8
ステップ2

49' 動かない、高い -- (最初の if/else 判定)

1 7
ステップ3 49は動かない、low++(2回目のif/else判定) 2 7


ここで 3 番目のステップが始まりますが、明らかに何かが間違っています。

空間にはまだ要素がありません。アルゴリズムが彼を追い越して次の空間にジャンプし始めます。なぜですか?

繰り返しは比較的無害ですが、低い動きは危険です。(間違っていきます)

ここでわかるように、明らかに問題は次の点にあります。

最初に処理された要素がセンチネルに配置された後、センチネルと要素自体を比較します。

つまり、自分と比較して、このスペースを見逃してしまうことです。

このスペースを使用してセンチネルより小さい最初の要素を合わせる代わりに、


動作コア:

したがって、私たちがしなければならないことは次のとおりです。

プログラムの開始時から、プログラムの動作のすべてのステップを監視して、次のことを確認します。

[最初の要素によって残されたスペース] を [センチネルよりも小さい最初の要素] に入れます。


ノート:

モニター:

より正確に言うと、ここでのいわゆる「監視」とは書き換えを指します。

プログラムの先頭から[最初の要素によって残されたスペース]が[センチネルより小さい最初の要素]にロードされるまで開始します。

このプロセスでは、プロセス全体のすべての操作手順が段階的に手作業で書き換えられます。


操作の内訳:

そして、(最初は)次のように手書きします。

プログラムの先頭から[最初の要素によって残されたスペース]が[センチネルより小さい最初の要素]にロードされるまで開始します。

プロセス全体の操作は次のとおりです。

プログラムの先頭から、(常に) ハイポインターを比較します: (最初に最後のビットを比較します)

  1. [要素を指す高いポインター] がセンチネルより小さい場合、要素を前のスペースに置きます
  2. (>=) より小さくない場合:
  • この要素は引き続き後ろに配置されます
  • 【ハイポインター】前方検索を続け、前の要素のサイズをセンチネルと比較します。

操作の実装:

質問(1): 異なる時点での if/else 判断を行う

ここで、プログラムを書くか、前のセクションに従うか、毎回単純に if/else 判定ステートメントを使用する場合、

さまざまな順序のリスト:

間違いなく、毎回異なる if/else 判断が必要になります。

(センチネルよりも小さい最初の要素がどれくらい後ろにあるかは誰にも分かりません)

これは間違いなく不可能です


問題 (2): 「センチネルより小さい要素」として条件を渡すと機能しない

私たちの目的は、後ろから前に向かってセンチネルよりも小さい最初の要素を見つけることであるため、

この「センチネルより小さい最初の要素」自体を直接見つけます

設定条件が「判定条件が「センチネルより小さい要素」」の場合は直接検索

最後の要素がセンチネルよりも小さい場合を除き、それを直接見つけることは不可能です

センチネル以上の要素がある場合、それを超えることはできません

if (low < high && L.r[high].key >= L.r[0].key)
{
    if (low < high && L.r[high].key >= L.r[0].key)
    {
        ...//无数个:
//【if (low < high && L.r[high].key >= L.r[0].key){}     else  把元素放前面空格里面】语句
//根本写不完,实现不了,死循环
    }
    else  把元素放前面空格里面
}
else  把元素放前面空格里面

したがって、判定ループ条件をセンチネル以上 (>=) に設定することしかできません。


質問(3):

ループ条件(while)に「以上」を使用した場合のプログラム(フレームワーク)の動作原理


初めに

アルゴリズムを書くとき、私たちは次のことを当然のことと考えています。

[以上] をループ条件 (while) ループとして使用する場合、戦略は次善のものを採用することです。

[(センチネル要素) より小さい前の要素] を必ず見つけてください。

次に、演算対象のセンチネルより小さい要素の前の要素を (+1) 見つけます。


後で私たちは知りました

いいえ、上に書いた実行プロセスは当然の結果です。

実際、ループ条件がセンチネル [以上] に変更されると、プログラム動作のロジックは次のようになります。

以上の場合: 常に高い--;

センチネルより小さい最初の要素が見つかるまで

プログラムがループを終了すると、high は交換時に指す必要がある要素をすでに指しています。

「センチネルより小さい最初の要素」そのものを直接発見

前の要素の代わりに

そこで変更を加えます: 

int 遍历(SqList &L, int low, int high)
{
    L.r[0] = L.r[low];
    while (low < high && L.r[high].key >= L.r[0].key)
        high--;
    L.r[low] = L.r[high];


    while (low < high)
    {
        if (L.r[high].key < L.r[0].key)
        {
            L.r[low] = L.r[high];
            low++;
        }
        else
            high--;
        if (L.r[0].key < L.r[low].key)
        {
            L.r[high] = L.r[low];
            high--;
        }
        else
            low++;
    }
    L.r[low] = L.r[high] = L.r[0];
    return low;
}

void QuickSort(SqList& L, int low, int high)
{
    int pivot = 遍历(L, low, high);
    QuickSort(L, low, pivot-1);
    QuickSort(L, pivot + 1, high);
}

int main()
{
    SqList L;
    cin >> L.length;
    cin >> L.r->key;

    QuickSort(L, 1, L.length);
}

ついにここで終わると思いましたか?

しかしそうではなかった


最大の問題:

その後、さらに深く調査を続けたところ、このプログラムの問題はそれほど単純ではないことがわかりました。

当初(以前)、私たちは次のように考えていました。

プログラムが表示されるのは、最初の要素がセンチネルに格納されているためです。

要素自体がセンチネルと比較します。つまり、要素自体を比較してスペースを見逃します。

現象によって引き起こされるエラー、重要なことは次のとおりです。

私たちはこれは特殊なケースだと考え、プログラム全体に問題があったのは最初だけでした

そして後で、実際にはそれほど単純ではないことがわかりました。

プログラムがすでに先頭に表示されている (正しくソートできない) という問題は、後続の (連続した) プログラムにも存在します。

本当の問題:

前後のスペースが埋まっていない場合は、前後にポインタを移動し始めます。

そうすれば、この空白は二度と埋まらないので、(これからは)戻ってこの空白を見つけることはできなくなります

すると、その後の手順がすべてめちゃくちゃになり、大きな問題が発生します


問題をプログラム フローで具体化します。

(手順) 本当の (新たな) 問題は次のとおりです。

まだ分​​からない:

(1):

ハイポインタが指す要素] が前のスペースに移動(塗りつぶされ)しました

スペースの後ろの要素を指すように低いポインターを移動し始めます。

また

(2):

[下位ポインタが指す要素]が後ろのスペースに移動(塗りつぶされ)しました

空間の前の要素を指すように高いポインターを移動し始めます。


具体化の完全なプロセス:


(1):

まだ分​​からない:

ハイポインタが指す要素] が前のスペースに移動(塗りつぶされ)しました

スペースの後ろの要素を指すように低いポインターを移動し始めます。


(新しいラウンド) 比較を最初から開始する場合 (2 番目の if/else ステートメントから)

次のような状況が発生したとします。ハイポインタが指す要素がセンチネルよりも小さくありません。

(Lr[high].key < Lr[0].key) が成立しないため、else 文を実行します。

2 番目の if/else ステートメントを実行します。

ここではすべて問題ありませんが、次のステップで問題が発生します。

手動操作プロセスによれば、プログラムの次のステップでは「high--;」が実行され続けるはずです。

ただし、プロジェクト 1 では、プロジェクト 1 のプログラムの操作フローによれば、次のステップは次のとおりです。

  • low が指す最初の要素がセンチネルより大きい場合: Lr[high] = Lr[low]; high--;

    ハイポインター要素をスペースに直接配置します。この場合、最後から 2 番目の要素がセンチネルよりも小さい場合を除き、これも間違った挿入です。


  • low が指す最初の要素がセンチネル以下の場合、low++;

    下のポインタを直接移動すると、スペースが見つからなくなります


(2):

まだ分​​からない:

[下位ポインタが指す要素]が後ろのスペースに移動(塗りつぶされ)しました

空間の前の要素を指すように高いポインターを移動し始めます。


(新しいラウンド) 比較を最初から開始する場合 (2 番目の if/else ステートメントから)

以下の状況が発生した場合: low ポインターが指す要素がセンチネルより大きくない
 
(Lr[0].key < Lr[low].key) は true ではないため、else ステートメントを実行します。 low++;

最初の if/else ステートメントを再実行します


high が指す最初の要素がセンチネルより小さい場合: Lr[low] = Lr[high];low++;

ローポインタ要素をスペースに直接挿入します。この場合、2 番目の要素がセンチネルよりも大きくない限り、これも間違った挿入です。

high が指す最初の要素がセンチネル以上の場合、high--;

ハイポインタを直接移動すると、スペースが見つからなくなります


最終的に、プログラムの問題の核心は、プログラムによって実行される操作です。

次のステップ(ポインタ変更操作)に進む前に、空白を含む次の要素が見つかるかどうかは確認されていません

代わりに、次の要素が比較されるか、要素が交換されるか、位置ポインタが移動される限り、次のステップが実行されます。

最終結果を次のように変更します。

#include<iostream>
using namespace std;

#define MAXSIZE 20  //记录最大个数
typedef int KeyType;  //关键字类型

typedef int InfoType;

//定义每个记录(数据元素)的结构
struct RecType
    //Record Type:每条记录的类型
{
    KeyType key;  //关键字
    InfoType otherinfo;  //其他数据项
};

struct SqList
    //顺序表(的)结构
{
    RecType r[MAXSIZE + 1];
    //类型为【记录类型】的数组
    //r[0]一般做哨兵或缓冲区
    int length;  //顺序表长度
};

int 遍历(SqList& L, int low, int high)
{
    L.r[0] = L.r[low];
    while (low < high)
    {
        //从后往前遍历,指向小于等于哨兵的元素:退出循环、插入空格
        while (low < high && L.r[high].key > L.r[0].key)         
            high--;
        L.r[low] = L.r[high];

        //继续,从前往后遍历,指向大于等于哨兵的元素:退出循环、插入空格
        while (low < high && L.r[0].key < L.r[low].key)
            low++;
        L.r[high] = L.r[low];
    }
    L.r[low] = L.r[high] = L.r[0];
    return low;
}

void QuickSort(SqList& L, int low, int high)
{
    int pivot = 遍历(L, low, high);
    QuickSort(L, low, pivot - 1);
    QuickSort(L, pivot + 1, high);
}

int main()
{
    SqList L;
    cin >> L.length;
    cin >> L.r->key;

    QuickSort(L, 1, L.length);
}

以前に作成したプログラムとの違いは次のとおりです。

ポインタ操作を変更するのは、センチネルより大きい/小さい要素が確実に見つかり、スペースが挿入されていることを確認した後のみです。

ただし、以前のプログラムでは、ポインタが見つかったか交換されたかに関係なく、ポインタを変更する操作が実行されます。

実際、変更したい結果は、次のステップ (次のラウンド) に進む前にスペースを挿入する操作が実行されることをプログラムに判断させることです。


もちろん、この修正と標準の回答の間にはまだ違いがありますが、それほど大きな違いはありません。

標準的な答え:

int Partition(SqList& L, int low, int high)
{
    L.r[0] = L.r[low];
    KeyType pivotkey = L.r[low].key;
    while (low < high) 
    {
        while (low < high && L.r[high].key >= pivotkey)
            high--;  
        L.r[low] = L.r[high];

        while (low < high && L.r[low].key < pivotkey)
            low++;
        L.r[high] = L.r[low];
    }
    L.r[low] = L.r[0];
    return low;
}

void QuickSort(SqList& L, int low, int high) {
    if (low < high)
    {
        int pivotloc = Partition(L, low, high);  //将L一份为二
        QuickSort(L, low, pivotloc - 1);  //对低子表递归排序
        QuickSort(L, pivotloc + 1, high);  //对高子表递归排序
    }
}

int main()
{

}

唯一の違いは、標準の回答ではこの比較ノードの変数ピボットキーが特別に設定されていることです。

実際には、しがみつくものは何もなくて無駄だと感じています。

それはそれです、Cnmd、この記事を書いてこのクイックキューを実行することで、少なくとも週に5日は私を混乱させました、私は本当にNMで言葉を失いました

以上

おすすめ

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