水文学を拒否せよ!8つのソート (4) 【初心者向け】マージソート


皆さんこんにちは、私の名前はジニンです。
この記事ではマージソートとカウンティングソートについて紹介します。マージ ソートとカウンティング ソートは非常に時間効率の良い 2 つのソートですが、それぞれに長所と短所があるため、使用シナリオも異なります。

マージソート

マージソートは分割統治戦略の並べ替えアルゴリズム。基本的な考え方は、並べ替えるシーケンスを 2 つの順序付けられたサブシーケンスに分割し、次に 2 つのサブシーケンスを 1 つの順序付けられたシーケンスにマージして並べ替えの目的を達成することです。
簡単に言うと、大量のデータを含むシーケンスのソートはより複雑になるため、順序付けされた複数の小さなシーケンスに分割されてからマージされます。合流方法。
再帰的ソートは、再帰的か非再帰的かに関係なく、O(N)大量のスペースを必要とし、これが欠点の 1 つでもあるため、通常はディスク上の外部ソートを処理します。
時間計算量: O(N*logN)

磁盘中的外排序问题

ディスク内の外部ソートの問題とは、 を指します需要对大量数据进行排序,但内存无法一次性容纳所有数据的情况このとき、データを複数の部分に分割し、そのたびにデータの一部をメモリに読み込んでソートし、最後に順序付けられたすべての部分をマージしてソートされた完全なデータを取得する必要があります。

外部並べ替えには通常、次の手順が含まれます。

  1. 生データをメモリ容量を超えない部分 (通常は「ファイル」と呼ばれます) に分割します。

  2. 各ファイルは内部でソートされ、通常はクイック ソートやマージ ソートなどのアルゴリズムが使用されます。

  3. すべての順序付けされたファイルをマージおよび並べ替えて、最終的な順序付けされたデータを取得します。

具体的な実装時には、データ アクセス方法とディスク I/O 効率を考慮する必要があります。通常、マルチウェイ マージやパーティション ソートなどのテクノロジは、ディスク I/O の効率を最適化し、マージ段階のファイル数をできる限り減らして効率を向上させるために使用されます。

外部ソートの問題は、通常、ビッグ データ処理、データベース システムなどの分野で発生し、大規模なデータを処理する場合には避けられない問題です。

マージソートの再帰的な実装

ここに画像の説明を挿入します

具体的なプロセスは次のとおりです。

  1. ソート対象のシーケンスを左右 2 つのサブシーケンスに分割し、再帰的に左右のサブシーケンスをマージしてソートします。
  2. ソートされた左右のサブシーケンスを順序付けされたシーケンスにマージします。具体的な手順は、左右の 2 つのサブシーケンスの最初の要素を比較し、小さい方の要素を取得して、サブシーケンスの 1 つのすべての要素が一時配列に格納されることです。終了したら、残りの要素を一時配列に順番に入れます。
  3. 左と右の両方のサブシーケンスが配置されるまでステップ 2 を繰り返します。この時点で、ソートされたシーケンスは一時配列内にあります。

先分解再合并
ここに画像の説明を挿入します
代码实现

void _MergeSortPart(int* a, int* tmp, int begin, int end)
{
    
    
	if (begin >= end)
	{
    
    
		return;
	}
	int midi = (begin + end) / 2;
	_MergeSortPart(a, tmp, begin, midi);
	_MergeSortPart(a, tmp, midi+1, end);
	int begin1 = begin, end1 = midi;
	int begin2 = midi+1, end2 = end;
	int index = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
    
    
		if (a[begin1] > a[begin2])
		{
    
    
			tmp[index++] = a[begin2++];
		}
		else
		{
    
    
			tmp[index++] = a[begin1++];
		}
	}
	while (begin1 <= end1)
	{
    
    
		tmp[index++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
    
    
		tmp[index++] = 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 (tmp == NULL)
	{
    
    
		perror("malloc fail");
	}
	_MergeSortPart(a, tmp, 0, n - 1);
	free(tmp);
	tmp = NULL;
}

コードの説明

マージ ソートは再帰を使用して実装されます。再帰関数の開始時に条件 begin >= end を設定できます。この条件が満たされない場合、再帰の各グループにはデータが 1 つだけあることを意味します。返された後、2 つの間隔が返されます。数値が 1 つだけの配列はマージされます。元の配列にコピーして再度戻ります。このとき、右側の数字が2つ含まれる区間に入って再帰しますが、再帰継続条件が満たされない場合は、この右側の区間にもデータが1つしかないことを意味し、戻り、戻ります。の 2 つの数値がマージされ、元の配列にコピーされて戻ります。これが、すべてのデータがマージされて元の配列にコピーされるまで繰り返されます。

递归展开图このプロセスは比較的複雑なので、理解するために使用してみてください。

マージソートの非再帰的な実装

非再帰的実装の考え方は、配列内でマージを直接実装し、各グループでマージされる要素の数を示す変数ギャップを定義し、for ループを使用してマージされるグループの数と位置を制御することです。 i は毎回 2*ギャップの長さをスキップします。これは 1 つのグループをスキップすること、つまり次のグループの再帰を開始することと同じです。このレベルのすべてのグループが再帰を完了すると、ギャップが 2 倍になり、次のレベルの再帰が開始されます。今回は、レベルの各グループのデータ数が 2 倍になり、ループは次のレベルまで継続します。ギャップがデータ数より大きい。
しかし、ここにはエラーが発生しやすい点があります。それは、end1、begin2、end2 で国境を越えた問題が発生する可能性があるということです。まず、begin1 は i に等しいので境界を越えることはできず、境界を越えるとループに入ることができません; end1 と begin2 はどちらも境界を越える可能性があるため、begin2 を判定した後、越えた場合はループに入ることができません。境界の場合、ループは直接停止されます (この操作はこのグループに対するものであるため、次のグループとマージします。end2 が範囲外の場合、2 番目のグループがまったく存在しないことを意味するため、再帰する必要はありません)。が範囲外です。これは、2 番目のグループがあることを意味しますが、2 番目のデータ グループは最初のグループほど多くありません。end2 を end に直接代入すると、問題は解決します。

void _MergeSortNonr(int* a, int* tmp, int begin, int end)
{
    
    
	int gap = 1;//归并每组的元素个数
	while (gap <= end)
	{
    
    
		for (int i = 0; i <= end; i += 2 * gap)
		{
    
    
			int index = 0;
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			if (begin2 > end)
			{
    
    
				break;
			}
			if (end2 > end)
			{
    
    
				end2 = end;
			}
			while (begin1 <= end1 && begin2 <= end2)
			{
    
    
				if (a[begin1] > a[begin2])
				{
    
    
					tmp[index++] = a[begin2++];
				}
				else
				{
    
    
					tmp[index++] = a[begin1++];
				}
			}
			while (begin1 <= end1)
			{
    
    
				tmp[index++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
    
    
				tmp[index++] = a[begin2++];
			}
			memcpy(a+i, tmp+i, sizeof(int) * (end2 - i - 1));
		}
		gap *= 2;
	} 
}

カウントソート

计数排序これは非比較ソート アルゴリズムであり、その原理は、一連のデータをカウントし、カウント結果に従ってデータをソートすることです。具体的な操作プロセスは次のとおりです。

  1. ソートする配列の最大値 max と最小値 min を求めます。

  2. max - min + 1各要素の出現数を格納するサイズの count 配列 countを作成します。

  3. 並べ替える配列を走査し、各要素の出現数をカウントし、カウント結果を count 配列に格納します。(配列要素の自己付加添字は、この配列内でデータn-minを添字とする位置です)

  4. 配列 count 要素の添字 + min は元のデータの値であり、この添字は格納されたデータ (元の配列内で元のデータが出現する回数) に対応します。

  5. count 配列を走査し、データが 0 でない場合は、ループを使用して元のデータ (添字 + min) を元の配列にコピーすると、並べ替えが終了します。

代码实现

void CountSort(int* a, int n)//计数排序
{
    
    
	int max = a[0], min = a[0];
	for (int i = 1; i < n; i++)
	{
    
    
		if (a[i] > max)
			max = a[i];
		if (a[i] < min)
			min = a[i];
	}
	int range = max - min + 1;
	int* count = (int*)malloc(sizeof(int) * range);
	memset(count, 0, sizeof(int) * range);
	for (int j = 0; j < n; j++)
	{
    
    
		count[a[j]-min]++;
	}
	int z = 0;
	for (int j = 0; j < range; j++)
	{
    
    
		while (count[j]--)
		{
    
    
			a[z++] = j + min;
		}
	}
	free(count);
}

時間計算量:O(N+range)
空間計算量:O(range)

カウントソートの主な欠点は、スペースの複雑さが価格と厳密に比較されることです。

計数ソートのアプリケーション シナリオには次のものが含まれますが、これらに限定されません。

ランキングなど、要素値の範囲が比較的小さいデータ セットの場合成绩排名、年龄分组、このシナリオではカウントによる並べ替えが驚異的に機能することがよくあります。
基数ソートなどの他のソート アルゴリズムの一部として辅助算法
重複する要素を含むデータセットの場合、カウントソートは簡単になります有效地去除重复项

おすすめ

転載: blog.csdn.net/zyb___/article/details/133514452