一、归并排序
如果两个数组有序,可以使用归并的方法使他们合并为一个有序数组:
归并的的思路是:
将数组拆分成若干个有序的子序列(一般是只有一个数的子序列),然后两两合并为有序的序列,最终得到完全有序的序列。
1.1.归并排序的递归实现
//归并排序的子函数
//这个函数将左右区间进行归并
void _MergeSort(int* a, int left, int right,int*tmp)
{
if (left >= right)
{
return;
}
int mid = (left + right) / 2;
//区间被分成了[left,mid][mid+1,right],如果这两个区间有序,即可以归并
_MergeSort(a, left, mid, tmp);//对左区间进行递归归并
_MergeSort(a, mid + 1, right, tmp);//对右区间进行递归归并
int begin1 = left, end1 = mid;
int begin2 = mid + 1, end2 = right;
int index = left;//tmp数组的下标
将两段子区间进行归并,归并结果放在tmp中
while (begin1 <= end1 && begin2 <= end2)//左右区间有一个结束,循环就结束
{
if (a[begin1] < a[begin2])
{
tmp[index++] = a[begin1++];
}
else
{
tmp[index++] = a[begin2++];
}
}
//当遍历完其中一个区间,将另一个区间剩余的数据直接放到tmp的后面
while (begin1 <= end1)
{
tmp[index++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[index++] = a[begin2++];
}
//拷贝回去
for (int i = left; i <= right; i++)
{
a[i] = tmp[i];
}
}
//归并排序
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(n * sizeof(int));//创建一个临时存放数据的数组
_MergeSort(a, 0, n - 1, tmp);//调用归并函数的子函数
free(tmp);
}
1.2.归并排序的非递归实现
递归采用的是分治思想,即将大问题化成小问题。通过将一个大的左右区间不断拆分成若干小的有序的左右子区间。
那么非递归就可以先将小区间有序合并,然后再合并大区间,最后将整个数组都合并成有序。
通过 增量 (gap)
就可以实现对每个区间的划分:
不过需要注意一些特殊情况:
-
比如有六个元素或者五个元素时,当gap==2,可能会出现右区间不存在的情况,此时就不需要对左区间和右区间进行归并了,因为左区间已经有序。直接跳出此次归并即可
-
当有7个元素时,右区间的数可能会不全,也就是end指针越界了,此时我们需要将end指针改回最后一个位置,也就是将end指向8
//归并排序的非递归
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
int gap = 1;//gap一开始为1,每个子区间只有1个数
while (gap < n)
{
for (int i = 0; i < n; i += 2 * gap)
{
//左右区间:[i,i+gap-1][i+gap,i+2*gap-1]
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
//归并过程中右半区间可能不存在
if (begin2 >= n)
{
break;//如果右半区间不存在,那就不用归并了
}
//归并过程中右半区间算多了
if (end2 >= n)
{
end2 = n - 1;//将右半区间的end2修正到最后
}
int index = i;
while (begin1 <= end1 && begin2 <= end2)
{
//将较小的数放入tmp
if (a[begin1] < a[begin2])
{
tmp[index++] = a[begin1++];
}
else
{
tmp[index++] = a[begin2++];
}
}
//当遍历完其中一个区间,将另一个区间剩余的数据直接放到tmp的后面
while (begin1 <= end1)
{
tmp[index++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[index++] = a[begin2++];
}
//拷贝回去
for (int j = i; j <= end2; j++)//边界修正,防止拷贝进去随机值
{
a[j] = tmp[j];
}
}
gap *= 2;//下一趟需合并的子序列中元素的个数翻倍
}
free(tmp);
}
1.3.时间空间复杂度分析
子函数_MergeSort函数的时间复杂度为O(N),因为代码中有2个长度为n的循环(非嵌套),所以时间复杂度则为O(N);
而_MergeSort函数一共被调用了log2N次。
所以时间复杂度为O(NlogN)
归并的空间复杂度就是那个临时的数组和递归时压入栈的数据占用的空间:N+log2N;
所以空间复杂度为: O(N)