归并排序采取的是分治法去实现排序。
思路:将原数组分裂开(一次分裂一半)后,分别对每一部分进行排序然后拷贝回原数组,而每一部分的排序也是采用分治法,归并排序(直到分裂的只剩下一个元素)
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有 序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
纵向展开图:
该方法是先开辟新的空间,传入数据进行排序,之后再拷贝回原数组,所以需要先开辟新空间,并且出该函数后不销毁
递归方法
因每次原数组分裂后的每一部分都需要再分裂再排序,因此可以采用递归实现,具体解释看代码注释↓
void _MergeSort(int* a, int begin, int end, int* tmp)
{
if (begin ==end) //当分裂的只剩一个元素就停止
{
return;
}
int mid = (end + begin) / 2; //每次分裂都是一半分
_MergeSort(a, begin, mid, tmp); //分别对每部分进行排序(递归)
_MergeSort(a, mid+1, end, tmp);
int begin1 = begin, end1 = mid; //对每部分的排序具体方法
int begin2 = mid + 1; int end2 = end;
int i = begin;
while (begin1 <= end1 && begin2 <= end2) //由于每一部分都是有序的,所以分裂的两部分
{ //依次进行比较,谁小谁先进
if (a[begin1] < a[begin2])
{
tmp[i] = a[begin1];
begin1++;
}
else
{
tmp[i] = a[begin2];
begin2++;
}
i++;
} //直到其中一部分没有数据后
while (begin1 <= end1) //把剩余的数据加入tmp中
{
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);
_MergeSort(a,0,n-1,tmp);
free(tmp);
}
非递归方法
非递归方法可以说是递归的逆思路
第一趟:分组每个组有一个元素,每组进行排序并拷贝
第二趟:分组每个组有两个元素,每组进行排序并拷贝
第三趟:分组每个组有四个元素,每组进行排序并拷贝
第四趟:分组每个组有八个元素,每组进行排序并拷贝
...
直到每组有N个元素(N为原数组元素个数)
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
printf("malloc fail\n");
return;
}
int gap = 1;
while (gap < n)
{
for (int j = 0;j<n;j = j+gap*2)
{
int begin1 = j, end1 = j+gap-1;
int begin2 = end1+1 ; int end2 = begin2+gap-1;
int i = j;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[i] = a[begin1];
begin1++;
}
else
{
tmp[i] = a[begin2];
begin2++;
}
i++;
}
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
}
memcpy(a , tmp , sizeof(int) *n);
gap *= 2;
}
free(tmp);
}
当然,这个代码有问题,只能运行满足2^n个元素的数组排序,因为没有考虑越界问题
无妨换个思路,每次比较后就拷贝回去,而不是一趟拷贝回一次,这样的话,当前两种情况就可以直接跳出比较的循环,因为它只有一半,没有另一半进行比较,而且本身已经有序,就在原数组不变即可,不需要拷贝进tmp(因为拷贝到tmp最终会拷贝回原数组中,那一部分并没有变化),而第三种情况只需将end2修改到边界n-1处即可
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
printf("malloc fail\n");
return;
}
int gap = 1;
while (gap < n)
{
for (int j = 0;j<n;j = j+gap*2)
{
int begin1 = j, end1 = j+gap-1;
int begin2 = end1+1 ; int end2 = begin2+gap-1;
int i = j;
if (end1 >= n||begin2 >= n)
{
break;
}
if (end2 >= n)
{
end2 = n - 1;
}
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[i] = a[begin1];
begin1++;
}
else
{
tmp[i] = a[begin2];
begin2++;
}
i++;
}
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
memcpy(a + j, tmp + j, sizeof(int) * (end2 - j + 1));
//每次拷贝回并不是整个tmp,而是每个部分,所以用a+j,tmp+j,end2-j+1也是控制了每部分的元素个数
//当end2越界时,个数会发生改变,所以不是2*gap
}
gap *= 2;
}
free(tmp);
}
时间复杂度:O(nlogn)
空间复杂度:O(n)
稳定性:稳定