クイックソートアルゴリズム (3 つの再帰的および非再帰的)

目次

1. クイックソートの再帰的実装

1. クイックソート (Hoare メソッド) 左右ポインタメソッドとも呼ばれます

2. クイックソート(穴掘り法)

3. クイックソート(前後ポインタ方式)

2 番目に、クイック ソートの非再帰的実装

3. クイックソートの特徴まとめ


1. クイックソートの再帰的実装

1. クイックソート (Hoare メソッド) 左右ポインタメソッドとも呼ばれます

そのアイデア:

まず、キー (参照オブジェクト)として数値を選択します。通常、キーとして左端を選択し、次に右端から開始します (キーとして右端を選択する場合は、最初に左から開始します。そうでない場合は、次の番号から交換されます)キーが指す値よりも数値が小さい場合に停止し、次に左から歩き、キーが指す値よりも大きい数値に遭遇した場合に停止し、最後にキーで指定された値に対応する値を交換します左ポインタと右ポインタ(添字)を入力し、左ポインタと右ポインタが一致するまで順番に繰り返し、下に来て、キーが指している値をその時点で左ポインタと右ポインタが指している値と交換し、最初のこのとき、左側の値はキーの指す値より小さく、右側の値はキーの指す値より大きくなります。

最後に、左右のポインタが等しい位置から、左辺がキーの指す値より小さく、右辺がキーの指す値より大きい部分列に分割します。キーが指す値を取得し、左右のサブシーケンスで上記のプロセスを繰り返します。

注: キーは下付き文字を意味します

void Swap(int* p1, int* p2)
{
  int tmp = *p1;
  *p1 = *p2;
  *p2 = tmp;
}

void Quicksort(int* a,int left ,int right)//左右指针法
{
   if (left >= right)return;
    int l = left, r = right;
	int key = left;
  while (l < r)
  {
	while (l < r&&a[r]>=a[key])//注意这里需要相等,如果不相等
	{                            //左右指针同时遇到等于key的数将会发生死循环
		r--;
	}

	while (l < r && a[l] <= a[key])//这里也一样
	{
		l++;
	}

	Swap(&a[l], &a[r]);
  }

   Swap(&a[l], &a[key]);
   Quicksort(a,  left,  l-1);
   Quicksort( a,  r+1, right);
	
}

2. クイックソート(穴掘り法)

アイデア: 名前が示すように、まず基準値として数値を選択します (この数値を変数 tmp で保存します)。その後、この数値に対応する位置 (ピット位置) が空きます。一般的には、左端の数値を基準値として選択します。 , 最初に右から開始し (右端の数字を選択した場合は、最初に左から開始します)、tmp より小さい値に遭遇したら停止し、空いている位置に値を埋めてから、左から歩きます。 tmp より大きい値を指定するには、空の位置に値を入力し、順番に繰り返します。

出会うまで停止し、出会った場所に tmp を置きます。

最後にシーケンス全体を tmp の位置から左右のサブシーケンスに分割し、このとき、左側の値が tmp より小さく、右側の値が tmp より大きいことを繰り返します。左と右のサブシーケンスに対する上記のプロセス。

 

 

void Quicksort(int* a, int left, int right)//挖坑法
{
  if (left >= right)return;
  int l = left, r = right;
  int tmp = a[left];

 while (l < r)
 {
	while (l < r && tmp <= a[r])
	{
		r--;
	}

	if(l<r)
	a[l++] = a[r];

	while (l < r && tmp >= a[l])
	{
		l++;
	}

	if(l<r)
	a[r--] = a[l];
}
	a[l] = tmp;
	Qs2(a, left, l-1 );
	Qs2(a, l+1 , right);
}

3. クイックソート(前後ポインタ方式)

アイデア: 参照値として数値を選択し (この数値の下付き文字を表すために key を使用します)、通常は左端の数値を参照値として選択し、ポインタ pre (下付き文字) を key と等しくさせ、別のポインタを cur ( subscript) key+1 と等しい場合、cur ポインタを逆方向に移動させ、key が指す値より小さい数値に遭遇した場合は、それを pre+1 に対応する数値と交換し、cur ポインタが等しくなるまで順番に繰り返します。右端の境界より大きい場合はループを抜け、その時点で key が指す値と pre が指す値を交換し、pre の値を key に代入します。

最後に、シーケンス全体をキーの位置から左右のサブシーケンスに分割しますが、このとき、左側はキーが指す値より小さく、右側はキーが指す値より大きくなります次に、左と右のサブシーケンスで上記のプロセスを繰り返します。

 

void Swap(int* p1, int* p2)
{
  int tmp = *p1;
  *p1 = *p2;
  *p2 = tmp;
}

void  Quicksort(int* a, int left, int right)//前后指针法
{
  if (left >= right)return ;

   int key = left;
   int cur = key+1, pre = key ;

   while (cur <= right)
   { 
   //pre+1等于cur时就不交换,因为没必要
      if (a[cur] <= a[key] && ++pre != cur)
      {
		 Swap(&a[cur], &a[pre]);
      }
		cur++;
   }

	Swap(&a[key], &a[pre]);
	key = pre;
	Quicksort(a, left, key-1);
	Quicksort(a, key+1, right);
}

2 番目に、クイック ソートの非再帰的実装

アイデア: クイック ソートは、バイナリ ツリーの事前順序トラバースに似ており、最初にシーケンス全体を並べ替えて、左側の数値がキーが指す数値よりも小さくなるようにします。

右側がキーで指定された数値より大きい場合、シーケンス全体をキーの位置から左右の部分シーケンスに分割し、上記のソートを左右の部分シーケンスに対して順番に実行します。再帰的思考で達成するのは簡単ですが、非再帰的思考でそれを達成する方法。

再帰の考え方によれば、左の部分列が最初に配置され、右の部分列が最後に配置されると想像できます。このとき、データ構造内のスタックを利用して非再帰的実装を完了できます。

まず右端の添え字をスタックに置き、次に左の添え字をスタックに入れます。2 つの要素を順番に取得し、これら 2 つの要素に対応する間隔を並べ替えます。並べ替えた後、間隔を左と右の部分間隔に分割します。次に、最初に次の値を入力します。右の区間の 2 つの添字を入力し、次に左の区間の 2 つの添字を入力し (左の区間を最初に配置する必要があるため)、スタック内の要素が空になり配置が完了するまで順番に繰り返します。

void Swap(int* p1, int* p2)
{
  int tmp = *p1;
  *p1 = *p2;
  *p2 = tmp;
}

int  Quicksort(int* a, int left, int right)//前后指针法
{
  if (left >= right)return ;

  int cur = left+1, pre = left ;
  int key = left;

   while (cur <= right)
   {  
        //pre+1等于cur时就不交换,因为没必要
      if (a[cur] <= a[key] && ++pre != cur)
      {
        Swap(&a[cur], &a[pre]);
      }
		cur++;
	}
	  Swap(&a[key], &a[pre]);
	  key = pre;
	  return key;
}

void QsortNonR(int* a, int left, int right)
{
	ST st;
	StackInit(&st);
	StackPush(&st, right);
	StackPush(&st, left);

	while (!STEmpty(&st))
	{
		int L = StackTOP(&st);
		StackPop(&st);
		int r = StackTOP(&st);
		StackPop(&st);

		int mid=Quicksort(a, L, r);

		if (mid + 1 < r)
		{
			StackPush(&st, r);
			StackPush(&st, mid + 1);
		}
		if (L < mid - 1)
		{
			StackPush(&st, mid);
			StackPush(&st, L);
		}
	}

}

3. クイックソートの特徴まとめ

 上の図からわかるように、シーケンス内に n 個の数値があると仮定すると、各数値を約 logN 回走査する必要があるため、タイム スケールは N*logN となり、再帰ごとにスタック フレームを確立する必要があります。上の図からわかるように、最大​​再帰深さは logN であるため、空間計算量は logN です。

1. 時間計算量: O(N*logN)

2. 空間複雑度: O(logN)

3. 安定性: 不安定

4. クイック ソートの全体的な総合的なパフォーマンスとアプリケーション シナリオは比較的良好であるため、あえてクイック ソートと呼びます。

おすすめ

転載: blog.csdn.net/m0_72532428/article/details/130150979