目次
3.2.1 間違った分割: [begin、mid-1]、[mid、end]
3.2.2 正しい除算: [begin、mid]、[mid+1、end]
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) になります。