不要停止奔跑,不要回顾来路,来路无可眷恋,值得期待的只有前方。 – 《马男波杰克》
一.归并排序
1.基本思想及动图演示:
基本思想:
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
动图演示:
2.递归代码实现:
- 下面是主函数MergeSort,先开辟一个和待排数组a大小相同的临时数组,我们要用递归实现,不可能每次递归调用都开辟一次空间。所以我们在主函数里创建。
- 然后就去调用我们的子函数
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail");
exit(-1);
}
_MergeSort(a,0,n-1,tmp);
free(tmp);
tmp = NULL;
}
下面就是子函数_MergeSort的具体实现
void _MergeSort(int* a, int begin, int end,int* tmp)
{
if (begin >= end)
return;
int mid = (begin + end) / 2;
_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));
}
- 这个过程就是对数组进行不断的二分
if (begin >= end)
return;
int mid = (begin + end) / 2;
_MergeSort(a, begin, mid,tmp);
_MergeSort(a, mid + 1, end,tmp);
如图:当begin >= end 时,递归调用结束
10 6这个子序列的左右递归调用返回后,就可以对这个子序列进行归并排序。
下面是具体归并的代码:
//归并
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));
画了个图,大家可以看看。
这里的memcpy函数需要注意一下
因为每次数组拷贝的起始地址不同,所以我们要加上begin
- 例如:我们要拷贝tmp数组里的1和7,我们需要使用1的起始地址即(tmp+begin)拷贝到a数组的起始地址即(a+begin)
然后就是对6 10 1 7 进行归并排序,过程和上面类似。
3.非递归实现
使用到一个【rangeN】的变量来控制每次归并区间的大小
3.1break版
代码实现:
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail");
exit(-1);
}
int rangeN = 1;
while (rangeN < n)
{
for (int i = 0; i < n; i += 2 * rangeN)
{
int begin1 = i, end1 = begin1 + rangeN - 1;
int begin2 = begin1 + rangeN, end2 = begin2 + rangeN - 1;
int j = i;
printf("[%d,%d][%d,%d]\n", begin1, end1, begin2, end2);
if (end1 >= n)
{
break;
}
else if (begin2 >= n)
{
break;
}
else if (end2 >= n)
{
end2 = n - 1;
}
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] <= a[begin2])
tmp[j++] = a[begin1++];
else
tmp[j++] = a[begin2++];
}
while (begin1 <= end1)
{
tmp[j++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[j++] = a[begin2++];
}
memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
}
rangeN *= 2;
}
free(tmp);
tmp = NULL;
}
- 需要注意下面代码的处理:
if (end1 >= n)
{
break;
}
else if (begin2 >= n)
{
break;
}
else if (end2 >= n)
{
end2 = n - 1;
}
当我们没有上面的代码时,我们来观察一下边界情况。可以分为三种
三种情况分别处理。
- 当end1 > n 时,我们可以直接break。
- 当begin2 > n时,我们也可以直接break。
- 当end2 > n时,我们需要修正end2的区间。
- 注意:
break版本只能部分拷贝,不可以全部拷贝,因为break后,没有数据进tmp数组,整体拷贝会让原数组丢失数据。
3.2修区间版
//修正版
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail");
exit(-1);
}
int rangeN = 1;
while (rangeN < n)
{
for (int i = 0; i < n; i += 2 * rangeN)
{
int begin1 = i, end1 = begin1 + rangeN - 1;
int begin2 = begin1 + rangeN, end2 = begin2 + rangeN - 1;
int j = i;
printf("[%d,%d][%d,%d]\n", begin1, end1, begin2, end2);
if (end1 >= n)
{
end1 = n - 1;
// 不存在区间
begin2 = n;
end2 = n - 1;
}
else if (begin2 >= n)
{
// 不存在区间
begin2 = n;
end2 = n - 1;
}
else if (end2 >= n)
{
end2 = n - 1;
}
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] <= a[begin2])
tmp[j++] = a[begin1++];
else
tmp[j++] = a[begin2++];
}
while (begin1 <= end1)
{
tmp[j++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[j++] = a[begin2++];
}
//可以部分拷贝
memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
}
//也可以整体归并完了再拷贝
//memcpy(a, tmp, sizeof(int) * (n));
rangeN *= 2;
}
free(tmp);
tmp = NULL;
}
注意体会下面代码的含义
if (end1 >= n)
{
end1 = n - 1;
// 不存在区间
begin2 = n;
end2 = n - 1;
}
else if (begin2 >= n)
{
// 不存在区间
begin2 = n;
end2 = n - 1;
}
else if (end2 >= n)
{
end2 = n - 1;
}
- 当end1 > n 时,我们将end1修到最后一位,begin2和end2修为不存在的区间。
- 当begin2 > n时,我们将begin2和end2修为不存在的区间。
- 当end2 > n时,我们将end2修到最后一位。