在本教程中,您将学习合并排序算法。此外,您还将找到C语言的合并排序示例。
合并排序是基于分治算法原理的最流行的排序算法之一。
在这里,一个问题被分为多个子问题。每个子问题都是单独解决的。最后,结合子问题形成最终的解决方案。
利用分治技术,我们把问题分成子问题。当每个子问题的解决方案准备就绪时,我们“组合”子问题的结果来解决主要问题。
假设我们必须对数组 A 进行排序,子问题是对这个数组的一个子部分进行排序,从索引 p 开始,到索引 r 结束,表示为 A[p…r]。
拆分
如果 q 是 p 和 r 之间的中点,那么我们可以将子阵列 A[p…r] 分成两个阵列 A[p…q] 和 A[q+1,r] 。
处理
在处理步骤中,我们尝试对子阵列 A[p…q] 和 A[q+1,r] 进行排序。如果我们还没有划分到基本层面,我们再次划分这两个子数组并尝试对它们进行排序。
组合
经过处理步骤,我们得到两个已排序的子数组 A[p…q] 和 A[q+1,r] ,将这两个数组组合之后得到一个已排序的数组 A[p…r]。
合并排序函数反复地将数组分成两半,直到我们到达一个阶段,在这个阶段中,我们尝试对大小为1的子数组执行合并排序,即 p==r。
之后,合并函数开始发挥作用,将排序后的数组合并为更大的数组,直到合并整个数组。
MergeSort(A, p, r):
if p > r
return
q = (p+r)/2
mergeSort(A, p, q)
mergeSort(A, q+1, r)
merge(A, p, q, r)
要对整个数组进行排序,需要调用 MergeSort(A, 0, length(A)-1)。
如下图所示,合并排序算法递归地将数组分成两半,直到我们得到包含1个元素的数组的基本情况。之后,合并函数会选取已排序的子数组并将它们合并,以逐渐对整个数组排序。
每一个递归算法都依赖于一个基本情况以及将基本情况的结果组合的能力。合并排序也一样。合并排序算法最重要的部分是合并步骤。
合并步骤解决了将两个排序列表(数组)合并为一个大型排序列表(数组)的简单问题。
该算法维护三个指针,一个用于两个数组中的每一个,另一个用于维护最终排序数组的当前索引。
我们已经到达任何阵列的末端了吗?
否:
比较两个数组的当前元素
将较小的元素复制到排序的数组中
移动包含较小元素的元素的指针
是:
复制非空数组的所有剩余元素
上面描述的合并步骤和用于合并排序的合并步骤之间的一个明显区别是,我们只在连续的子数组上执行合并函数。
这就是为什么我们只需要数组,第一个位置,第一个子数组的最后一个索引(我们可以计算第二个子数组的第一个索引)和第二个子数组的最后一个索引。
我们的任务是合并两个子数组 A[p…q] 和 A[q+1…r] ,以创建排序数组 A[p…r]。所以函数的输入是A,p,q和r。
合并函数的工作原理如下:
在代码中,这看起来像:
// Merge two subarrays L and M into arr
void merge(int arr[], int p, int q, int r) {
// Create L ← A[p..q] and M ← A[q+1..r]
int n1 = q - p + 1;
int n2 = r - q;
int L[n1], M[n2];
for (int i = 0; i < n1; i++)
L[i] = arr[p + i];
for (int j = 0; j < n2; j++)
M[j] = arr[q + 1 + j];
// Maintain current index of sub-arrays and main array
int i, j, k;
i = 0;
j = 0;
k = p;
// Until we reach either end of either L or M, pick larger among
// elements L and M and place them in the correct position at A[p..r]
while (i < n1 && j < n2) {
if (L[i] <= M[j]) {
arr[k] = L[i];
i++;
} else {
arr[k] = M[j];
j++;
}
k++;
}
// When we run out of elements in either L or M,
// pick up the remaining elements and put in A[p..r]
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
while (j < n2) {
arr[k] = M[j];
j++;
k++;
}
}
这个函数发生了很多事情,所以让我们举个例子来看一下它是如何工作的。
数组 A [0…5] 包含两个排序的子数组 A [0…3] 和 A [4…5]。让我们看看merge函数如何合并两个数组。
void merge(int arr[], int p, int q, int r) {
// Here, p = 0, q = 4, r = 6 (size of array)
步骤1:创建要排序的子数组的副本
// Create L ← A[p..q] and M ← A[q+1..r]
int n1 = q - p + 1 = 3 - 0 + 1 = 4;
int n2 = r - q = 5 - 3 = 2;
int L[4], M[2];
for (int i = 0; i < 4; i++)
L[i] = arr[p + i];
// L[0,1,2,3] = A[0,1,2,3] = [1,5,10,12]
for (int j = 0; j < 2; j++)
M[j] = arr[q + 1 + j];
// M[0,1,2,3] = A[4,5] = [6,9]
int i, j, k;
i = 0;
j = 0;
k = p;
步骤3:在到达L或M的末端之前,从元素L和M中选取较大的元素,并将它们放置在A[p…r]的正确位置
while (i < n1 && j < n2) {
if (L[i] <= M[j]) {
arr[k] = L[i]; i++;
}
else {
arr[k] = M[j];
j++;
}
k++;
}
步骤4:当L或M中的元素用完时,选取剩余的元素并放入[p…r]。
// We exited the earlier loop because j < n2 doesn't hold
while (i < n1)
{
arr[k] = L[i];
i++;
k++;
}
// We exited the earlier loop because i < n1 doesn't hold
while (j < n2)
{
arr[k] = M[j];
j++;
k++;
}
}
如果M的值大于L,则需要此步骤。
在merge函数的末尾,子数组A[p…r]被排序。
// Merge sort in C
#include
// Merge two subarrays L and M into arr
void merge(int arr[], int p, int q, int r) {
// Create L ← A[p..q] and M ← A[q+1..r]
int n1 = q - p + 1;
int n2 = r - q;
int L[n1], M[n2];
for (int i = 0; i < n1; i++)
L[i] = arr[p + i];
for (int j = 0; j < n2; j++)
M[j] = arr[q + 1 + j];
// Maintain current index of sub-arrays and main array
int i, j, k;
i = 0;
j = 0;
k = p;
// Until we reach either end of either L or M, pick larger among
// elements L and M and place them in the correct position at A[p..r]
while (i < n1 && j < n2) {
if (L[i] <= M[j]) {
arr[k] = L[i];
i++;
} else {
arr[k] = M[j];
j++;
}
k++;
}
// When we run out of elements in either L or M,
// pick up the remaining elements and put in A[p..r]
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
while (j < n2) {
arr[k] = M[j];
j++;
k++;
}
}
// Divide the array into two subarrays, sort them and merge them
void mergeSort(int arr[], int l, int r) {
if (l < r) {
// m is the point where the array is divided into two subarrays
int m = l + (r - l) / 2;
mergeSort(arr, l, m);
mergeSort(arr, m + 1, r);
// Merge the sorted subarrays
merge(arr, l, m, r);
}
}
// Print the array
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++)
printf("%d ", arr[i]);
printf("\n");
}
// Driver program
int main() {
int arr[] = {6, 5, 12, 10, 9, 1};
int size = sizeof(arr) / sizeof(arr[0]);
mergeSort(arr, 0, size - 1);
printf("Sorted array: \n");
printArray(arr, size);
}
时间复杂度
最佳情况复杂度: O(n * log n)
最坏情况复杂度: O(n * log n)
平均情况复杂度: O(n * log n)
空间复杂度
合并排序的空间复杂度为 O(n)。
[1]Parewa Labs Pvt. Ltd.Merge Sort Algorithm[EB/OL].https://www.programiz.com/dsa/merge-sort,2020-01-01.