web-dev-qa-db-ja.com

クイックソートコードの説明

これは、クイックソートアルゴリズムの実装で遭遇したコードです。ここで再帰がどのように機能するか説明していただけますか?

 void quickSort(int arr[], int left, int right)
 {
  int i = left, j = right;
  int tmp;
  int pivot = arr[(left + right) / 2];

  /* partition */
  while (i <= j) {
        while (arr[i] < pivot)
              i++;
        while (arr[j] > pivot)
              j--;
        if (i <= j) {
              tmp = arr[i];
              arr[i] = arr[j];
              arr[j] = tmp;
              i++;
              j--;
    }
}
/* recursion */
if (left < j)
    quickSort(arr, left, j);
if (i < right)
        quickSort(arr, i, right);
}

そして、これは宿題ではないことに注意してください。

「再帰がどのように機能しているか説明してください」とはどういう意味かわかりません。しかし、ここに行きます:

投稿した関数は、intの配列と2つのインデックスを受け取ります。配列全体をソートするのではなく、2つのインデックス間の一部のみをソートして、それらの外側にあるものはすべて無視します。つまり、同じ関数は、最初と最後のインデックスを渡した場合は配列全体をソートできます。配列の最初の要素のインデックスではないleft値を渡した場合はサブ配列のみをソートできます。最後の要素のインデックスではないright値。

ソートアルゴリズムは、よく知られているクイックソートです。ピボットとしては中央の要素を使用します(他の要素を使用することもできます)。配列をless than (or equal to) pivotサブ配列とgreater than (or equal to) pivotサブ配列に分割し、2つのパーティション間のピボットに等しい要素を残します。

次に、それ自体を再帰的に呼び出して2つのパーティションをソートしますが、必要な場合にのみ行います(したがって、再帰的な呼び出しの前にifsを使用します)。

実装は機能しますが、多くの点で最適ではなく、改善される可能性があります。考えられる改善点は次のとおりです。

  1. 配列が十分に短い場合、別のソートアルゴリズムに切り替えます
  2. ピボット値をつの値の中央値として選択しました(通常、最初、最後、および中央)
  3. 最初に1つのピボット値を配列から移動し(最初または最後の位置に配置して、フォーカスを配列の残りの部分に減らします)、次にピボットに等しい値を渡すようにテストを変更して、数を減らします入れ替えそれらを含みます。最後に最後の交換でピボット値を戻します。これは、提案2に従わず、この実装のように中間の要素の代わりに最初の要素または最後の要素を選択した場合に特に便利です。
8
Analog File

返信が遅くなりましたが、私は印刷物をいくつか追加しただけで、これに遭遇した人は誰でもコードを理解するのに役立ちます。

#include<iostream>
using namespace std;

void quickSort(int arr[], int left, int right)
 {
  int i = left, j = right;
  int tmp;
  int pivot = arr[abs((left + right) / 2)];
  cout<<"pivot is"<<pivot<<endl;

  /* partition */
  while (i <= j) {
        while (arr[i] < pivot)
              i++;
        while (arr[j] > pivot)
              j--;
        if (i <= j) {
              cout<<"i and j are"<<i<<" "<<j<<"and corresponding array value is"<<arr[i]<<" " <<arr[j]<<endl;
              tmp = arr[i];
              arr[i] = arr[j];
              arr[j] = tmp;
              i++;
              j--;
              cout<<"entering first big while loop"<<endl;
         for(int i=0;i<7;i++)
    cout<<arr[i]<<" "<<endl ;
    }
}
cout<<"recursion"<<endl;

/* recursion */
if (left < j)
    quickSort(arr, left, j);

if (i< right)
        quickSort(arr, i, right);
}
int main(){
    int arr[7]= {2,3,8,7,4,9,1};
        for(int i=0;i<7;i++)
    cout<<arr[i]<<" " ;
    quickSort(arr,0,6);
    cout<<endl;
    for(int i=0;i<7;i++)
    cout<<arr[i]<<" " ;
int wait;
cin>>wait;
return 0;
}
4
Illusionist

これがあなたの答えです-一般的なケースでは、それらの上の条件が真であるため、両方の再帰呼び出しが実行されます。ただし、コーナーケースでは、ピボット要素を最大(または最小)の要素にすることができます。その場合、配列からピボット要素を削除した後で、別のピボットを選択することにより、基本的にもう一度プロセスを試行する1回の再帰呼び出しのみを行う必要があります。

1
necromancer