【データ構造 - ハンドティアソートアルゴリズム その7】マージソートの再帰的実装

目次

1. 合併の考え方

2. マージソートの考え方

2.1 基本的な考え方

2.2 グラフ分析

3. マージソート再帰バージョンコードの実装

3.1 コード分析

3.2 注意事項

3.2.1 間違った分割: [begin、mid-1]、[mid、end]

3.2.2 正しい除算: [begin、mid]、[mid+1、end]

4. マージソートテスト

5. 時間計算量と空間計算量の解析

5.1 時間計算量

5.2 空間の複雑性


1. 合併の考え方

マージの考え方を理解するのはこれで 2 回目です。前回のリンク リスト oj 問題で初めて、2 つの順序付けされたリンク リストをマージしました。そのときの問題を解決するためのアイデアは、合併中。

今回は体系的にマージするというアイデアを勉強してみましょう (この記事では例として昇順を使用します)。

2 つの配列 (リンク リスト) をマージする場合、2 つのポインターを使用して異なる配列の最初の要素を指し、2 つの配列を制御および走査し、2 つのポインターが指す値を比較します。一時配列を作成し、ポインタを小さい値のポインタに一段階戻して比較を続行します。比較を続けます。最初に走査される配列が常に存在します。これは、順序付けられた配列であるためです。また、走査されていない他の配列は、並べ替えを完了するために一時配列の後ろに直接接続されます。

この考え方を理解するために絵を描いてみましょう。

2. マージソートの考え方

2.1 基本的な考え方

マージ ソートは、マージ操作に基づく効率的な並べ替えアルゴリズムです。

1. マージとソートの考え方は、配列を左右 2 つの配列に分割し、分割された部分範囲が 1 つのデータになるまで左右の配列を分割し続け、データが順序通りになるようにすることです。二分法。

2. 次に、左右のサブ間隔のソートとマージを行い、継続的にソートとマージを行って、最終的に配列全体のソートを実現します。

2.2 グラフ分析

マージする場合、元の配列に対して直接の交換は行わないため、元の配列に対して直接操作を行うと上書きが発生しエラーが発生します。したがって、元の配列と同じサイズのスペース tmp を malloc し、tmp にマージするたびに値をコピーし、マージして 1 回コピーします。中央の tmp 配列が順番に配置されます。その後、memcpy を使用して tmp を元の配列にコピーします。、次にfree(tmp) (メモリリークを防ぐため) を実行すると、ソート全体が完了します。

3. マージソート再帰バージョンコードの実装

// 归并排序递归实现
// 时间复杂度:O(N*logN)
// 空间复杂度:O(N + logN)
void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin >= end)
		return;

	int mid = (begin + end) / 2;
	//[begin, mid] [mid+1, end]
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid+1, end, tmp);

	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] <= a[begin2])
			tmp[i++] = a[begin1++];
		else
			tmp[i++] = a[begin2++];
	}

	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}

	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}
	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (NULL == tmp)
	{
		perror("malloc fail:");
		return;
	}
	_MergeSort(a, 0, n-1, tmp);
	free(tmp);
}
 
 

3.1 コード分析

このセクションでは再帰を使用しました。再帰の考え方は、配列を左と右の間隔に分割し、その後、左と右の間隔を分割し続けることです。開始 >= 終了の場合、それは最小の間隔であり、その後、並べ替えとマージを続行します。

このセクションはソート + マージです。2 つのサブインターバルをソート + マージします。これがマージのアイデアです。ここで注意すべき点は、tmp の添え字が begin であることです。これは、各レイヤーのマージされた値を最初に tmp に入れてから、それを元の配列にコピーする必要があるためです。0 の場合、コピーバック時にエラーが発生します。 。

3.2 注意事項

左右の区間を分割する場合は、[begin,mid]、[mid+1,end]、

[開始、中 1]、[中、終了] はできません。

ここで間違いやすいのですが、論理的には問題がないように見えますが、並べ替えの際に問題が発生します。

3.2.1 エラーの分割: [begin、mid-1]、[mid、end]

3.2.2 正しい除算: [begin、mid]、[mid+1、end]

概要:無限ループの最初の除算の理由は、mid = begin+end/2 の場合、計算機が切り捨てられるため、切り捨てられた後は、間隔には、発生した丸めを補う中間値が含まれている必要があるためです。切り捨てにより。

4. マージソートテスト

void Print(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}
void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin >= end)
		return;

	int mid = (begin + end) / 2;
	// [begin, mid] [mid+1, end]
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid + 1, end, tmp);

	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] <= a[begin2])
			tmp[i++] = a[begin1++];
		else
			tmp[i++] = a[begin2++];
	}

	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}

	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}
	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (NULL == tmp)
	{
		perror("malloc fail:");
		return;
	}
	_MergeSort(a, 0, n - 1, tmp);

	free(tmp);
}
void test()
{
	int a[] = { 6,3,2,1,5,7,9 };
	Print(&a, sizeof(a) / sizeof(int));
	MergeSort(&a, sizeof(a) / sizeof(int) - 1);
	Print(&a, sizeof(a) / sizeof(int));
}
int main()
{
	test();

	return 0;
}
 
 

試験結果:

5. 時間計算量と空間計算量の解析

5.1 時間計算量

マージとソートの間隔は連続的に分割され、時間計算量は O(logN) になります。その後マージすると、時間計算量は O(N) になります。

全体的な時間計算量は o(N*logN) です。

5.2 空間の複雑性

並べ替え用に一時的なスペースを開いたため、スペースの複雑さは O(N) になります。

おすすめ

転載: blog.csdn.net/Ljy_cx_21_4_3/article/details/131763637