[ソート] マージソート(再帰的+非再帰的な詳細図)

序章

この記事では、ソート アルゴリズムであるマージ ソートを引き続き紹介します。
マージ ソートでは、2 つの順序付けられたシーケンスを 1 つの順序付けられたシーケンスにマージするというマージの概念が使用されます。前回の 2 つの順序付きリンク リストのマージでは、次のアイデアが使用されました: 2 つの順序付きリンク リストのマージを実現するためにマージを確認する (方法 2)

マージソート

一連の考え

マージ ソートには、マージされたデータを一時的に保存するための配列と同じサイズのスペースが必要です。

並べ替えるときは、まず配列全体を 2 つの等しい部分に分割し、次に 4 つの部分に分割します...というように、均等に分割できなくなるまで (区間内に 1 つの要素だけが残る)、上向きにマージします。つまり、
2 つの区間を一時領域の位置の対応する区間にマージし、
一時配列の対応する区間の並べ替えられたデータを元の配列にコピーして、
並べ替えが完了するまで上向きにマージします。
(ここでの二等分は同時に行われないことに注意してください。再帰では一行ずつ行われます。左側が先頭まで再帰した後、上の階層に戻り再帰が続きます)最後から 2 番目のレベルの右側に移動し、右側は最後尾に戻ります 上のレベルでは、左下の区間と右下の区間のマージを実行します マージ後、最後から 2 番目の 3 番目のレイヤーに戻り、次にレイヤーの右側に再帰します...)

ここに画像の説明を挿入

再帰的な実装

クイック ソートの再帰モードとは異なり、クイック ソートでは、最大間隔での操作が完了した後、下方向に再帰し、ソートが終了するまで左右の間隔で操作しますが、ソートでは、最初に最小間隔まで再帰し、その後ソートが完了します。上に向かって合流します。したがって、クイック ソートを実装する場合、各層の操作が最初で、関数の再帰が最後になりますが、クイック ソートが最初に再帰的で、各層の操作が最後になります。

この関数には、元の配列、間隔の左右の添え字、および一時スペースの 4 つのパラメーターがあります。
関数に渡される初期パラメータは配列全体で、下向きに再帰的に行われます。begin>=end の場合、return で再帰が終了します。midi を作成し、
間隔の中央の位置を記録し、次に左と右の間隔をそれぞれ再帰します。

次に、区間に対する操作を実現します。
まず、begin1、end1、begin2、end2 を使用して、マージする区間の左右の区間の開始と終了の添え字を記録し、k を使用して、対応する区間の開始位置を記録します。一時空間 temp にマージされ、次に左右の
間隔が temp の対応する位置にマージされ、
最後にデータを temp の対応する空間にコピーされます。

ここに画像の説明を挿入

マージのプロセスでは、2 つの間隔のデータが順番に比較され、小さい要素が一時スペースに末尾挿入されます。

ここに画像の説明を挿入

void _MergeSort(int* a, int begin, int end, int* temp)
{
    
    
	if (begin >= end)
	{
    
    
		return;
	}
	int midi = (begin + end) / 2;
	_MergeSort(a, begin, midi, temp);
	_MergeSort(a, midi+1, end, temp);
	
	int begin1 = begin, end1 = midi;
	int begin2 = midi+1, end2 = end;
	int k = begin1;
	while (begin1 <= end1 && begin2 <= end2)
	{
    
    
		if (a[begin1] < a[begin2])
		{
    
    
			temp[k++] = a[begin1++];
		}
		else
		{
    
    
			temp[k++] = a[begin2++];
		}
	}
	while (begin1 <= end1)
	{
    
    
		temp[k++] = a[begin1++];      
	}
	while (begin2 <= end2)
	{
    
    
		temp[k++] = a[begin2++];
	}

	memcpy(a + begin, temp + begin, sizeof(int)*(end - begin + 1));
}

void MergeSort(int* a, int n) //递归实现
{
    
    
	int left = 0;
	int right = n - 1;
	int* temp = (int*)malloc(sizeof(int) * n);
	if (temp == NULL)
	{
    
    
		perror("malloc");
		return;
	}
	_MergeSort(a, left, right, temp);
	free(temp);
}

非再帰的に並べ替える

一連の考え

非再帰的にソートを実装する場合、クイックソートとは異なり、前の層の間隔を記録する必要はなく、一定間隔をマージした後、元の配列に戻すだけで十分です。したがって、配列をグループ化し、各 2 つのグループの値をマージするだけです。各グループの要素の数は 1 から始まります。各ループの後、ループが終了するまで、各グループの要素の数は 2 倍されます。配列の長さよりも長いです。

ループの各層では、この層の 2 つの間隔をすべて 1 つのグループにマージし、これらのデータを元の配列に転送する必要があります。

(写真)

達成

非再帰を実装する場合は、まず一時的な空間温度を動的に開き、
次に各レイヤーでマージする必要がある各間隔の要素の数としてギャップを作成し、それを 1 に初期化します。

外側の while ループはギャップの値を制御します。配列内の要素の数が要素の数以上になると、ループは終了します。内側の for ループは、各レイヤー (2 つのセル) でマージされるグループを制御します
。要素の数がギャップである場合、グループとしてマージされます):
左側の区間の開始位置 begin1 は i、終了位置 end1 は begin1+gap-1、右側の区間の開始位置 begin2 は end1+1、および終了位置 end2 は begin2+gap-1 です; k は temp の対応する開始位置です 開始位置の添え字;
次に、この間隔グループが範囲外であるかどうかを判断する必要があります。for ループは i<n のみを制御するため、つまり begin1<n、end1、begin2、および end2 はすべて境界を越える可能性があります。end1 または begin2 が境界を越える場合、
このグループ (左と右) の間隔が 1 つであることを意味します。間隔が短いため、マージする必要はありません。end2 が境界を越える場合、左右の間隔のセットが存在し、マージする必要があることを意味します。end2 を配列の最後の要素の添字に変更するだけです。

次に、左と右の間隔を temp の対応する位置にマージします。
各グループ (左と右) の間隔がマージされた後、配列を元の配列にコピーして戻し
ます。 、2*gap データを直接コピーして戻すことはできませんが、end2-i+1 データである必要があります]

ここに画像の説明を挿入

void MergeSortNonR2(int* a, int n) //非递归实现(分别转移)
{
    
    
	int gap = 1;
	int* temp = (int*)malloc(sizeof(int) * n);
	if (temp == NULL)
	{
    
    
		perror("malloc");
		return;
	}

	while (gap < n)
	{
    
    
		for (int i = 0; i < n; i += (gap * 2))//每层
		{
    
    
			int begin1 = i, end1 = begin1 + gap - 1;
			int begin2 = end1 + 1, end2 = begin2 + gap - 1;
			int k = begin1;

			//判断是否越界(如果越界,直接break跳过归并)
			if (end1 >= n || begin2 >= n)
			{
    
    
				break;
			}
			else if (end2 >= n)
			{
    
    
				end2 = n - 1;
			}

			while (begin1 <= end1 && begin2 <= end2)
			{
    
    
				if (a[begin1] < a[begin2])
				{
    
    
					temp[k++] = a[begin1++];
				}
				else
				{
    
    
					temp[k++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
    
    
				temp[k++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
    
    
				temp[k++] = a[begin2++];
			}

			memcpy(a + i, temp + i, (end2 - i + 1) * sizeof(int));//每次归并都转移
		}

		gap *= 2;//走向下一层
	}
}

要約する

ここまででマージソートに関する内容が紹介され、ソートアルゴリズムの説明も終わりましたが、「ここが明確に
紹介できていない」「ここに問題がある」と思われる方は、コメント欄でご意見をお待ちしております。

この記事が少しでもお役に立てましたら、ワンクリックでつながれば幸いです

皆さんと一緒に進歩していきたいと思っています

おすすめ

転載: blog.csdn.net/weixin_73450183/article/details/130319055