归并排序是采用分治法的一个非常典型的应用。其基本思想是:将已有序的子序合并,从而得到完全有序的序列,即先使每个子序有序,再使子序列段间有序。
1.递归实现
基本思想:先使左区间有序再使右区间有序,然后归并左区间和右区间。
我们需要一直向下分解,直到每个区间只剩下一个元素时,此时该区间可以看成是有序的,然后再一一归并,两两归并,四四归并,,,,,使得整个序列有序。
此外我们还得创建一份与待排数组大小相同的空间,用于保存归并后的元素,然后再将其拷贝回元素组。(为何要创建这么一份空间?因为我们在归并两段序列时,无论是交换两个序列的元素还是将其中一个元素放到另一个序列中,都会产生错误。)
代码如下:
void _MergeSort(int* arr, int* tmp, int begin, int end)
{
//递归终止条件:序列中只剩下一个元素或序列不存在
if (begin >= end)
return;
//使左右区间有序
int mid = (begin + end) / 2;//中间元素下标
_MergeSort(arr, tmp, begin, mid);//使左区间有序
_MergeSort(arr, tmp, mid + 1, end);//使右区间有序
//将左区间和右区间归并到tmp中:依次比较取最小的尾插
int begin1 = begin, end1 = mid;//第一个区间中首尾元素的下标
int begin2 = mid + 1, end2 = end;//第二个区间中首尾元素的下标
int i = begin;//i是arr中的区间归并到对应的tmp中的区间的起始位置的下标
while (begin1 <= end1 && begin2 <= end2)
{
if (arr[begin1] <= arr[begin2])
tmp[i++] = arr[begin1++];
else
tmp[i++] = arr[begin2++];
}
//将左区间或右区间中剩余的元素拷贝到tmp中
while (begin1 <= end1)
tmp[i++] = arr[begin1++];
while (begin2 <= end2)
tmp[i++] = arr[begin2++];
//将tmp中归并的元素拷贝回arr中
memcpy(arr + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
void MergeSort(int* arr, int n)
{
//申请一份临时空间
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail");
exit(-1);
}
//用子函数递归
_MergeSort(arr, tmp, 0, n - 1);
//释放掉申请的空间
free(tmp);
tmp = NULL;
}
时间复杂度:O(N * logN),空间复杂度:O(N)
2.非递归实现
我们发现归并排序的递归实现,目的是将区间不断分解直到区间元素只剩下一个,然后进行一一归并,二二归并,四四归并,,,。
非递归实现正好与这种做法相反,它是直接一一归并,二二归并,四四归并,,,省去了分解的过程。
但是该方法也需要注意三点:
归并最后两个区间时,
1.如果第一个区间有一部分超出数组范围,则不需要归并。
2.如果第一个区间在数组范围之内,但第二个区间全部元素超出数组范围,则不需要归并。
3.如果第一个区间在数组范围之内,但第二个区间有一部分元素超出数组范围,这时我们需要修改第二个区间的终止下标为n - 1;
代码如下:
void MergeSortNonR(int* arr, int n)
{
//申请一份临时空间
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail");
exit(-1);
}
int rangeN = 1;//一个元素和一个元素归并
while (rangeN < n)//区间元素个数大于或等于数组元素个数时归并完毕,终止循环。
{
//归并两组区间元素
int i = 0;//i是所要归并的两组元素中,第一组的第一个元素的下标。
for (i = 0; i < n; i = i + rangeN)
{
int begin1 = i, end1 = i + rangeN - 1;//第一组起始元素下标,终止元素下标
int begin2 = i + rangeN, end2 = i + 2 * rangeN - 1;//第二组起始元素下标,终止元素下标
if (end1 >= n)//第一个区间元素个数不足rangeN个
break;
if (begin2 >= n)//第二个区间不存在
break;
if (end2 >= n)//第二个区间元素个数不足rangeN个
end2 = n - 1;
int j = i;
while (begin1 <= end1 && begin2 <= end2)
{
if (arr[begin1] <= arr[begin2])
tmp[j++] = arr[begin1];
else
tmp[j++] = arr[begin2];
}
while (begin1 <= end1)
tmp[j++] = arr[begin1];
while (begin2 <= end2)
tmp[j++] = arr[begin2];
memcpy(arr + i, tmp + i, sizeof(int) * (end2 - i + 1));
}
rangeN *= 2;//2,2归并;4,4归并;8,8归并......
}
//释放申请的空间
free(tmp);
tmp = NULL;
}
时间复杂度:O(N * logN),空间复杂度:O(N)