在本文中使用到的升序,降序,交换函数的代码实现见:这篇博客
在单链表的基本操作中,有一个操作为合并两个有序的单链表使合并后的链表仍然有序。本文中归并排序与这个思想类似。不断使两两有序的序列进行合并。
归并排序(递归实现)
例如,有一待排序序列:20 10 9 8 11 10 7 6。

排序过程如上图所示,
(1)首先将待排序序列一分为二,两个红框框起来的序列。将这两个序列排好序后,利用合并算法进行合并。
(2)在对红框进行排序时,再将其一分为二,对应四个绿框。将绿框排好序后,利用合并算法合并成红框。
(3)再对绿框进行排序时,再将其一分为二,对应8个篮框。此时蓝框中只有一个元素,已经排好序,因此只需利用合并算法将两个蓝框合并成绿框。
上述过程是一个递归过程,每一步对应的操作相同,先拆分。将一个大的序列分解为一个个小的序列。待排序序列中只有一个元素时,此时便排序完成。再进行一个个有序序列的合并。
所以,利用递归调用解决该问题,将待排序序列一分为二,使这两部分分别有序后再进行合并。再对这两部分排序时也是一分为二进行排序后在进行合并。
因此,使用递归函数来使待排序序列有序。先将待排序序列一分为二后分别调用递归函数使之有序,在进行有序序列的合并。在递归函数中,如果待排序序列的元素个数为1,表明已经有序,此时,直接返回即可。
代码实现如下:
//递归进行归幷排序
void MergeSort(int arr[],uint64_t size)
{
if(arr == NULL || size <= 1)
{
return;
}
//[left,right)为待排序区间
int left = 0;
int right = size;
//定义temp来保存合并两个已经排好序的区间
int* temp = (int*)malloc(size*sizeof(int));
//使用递归函数来对带排序序列进行排序
_MergeSort(arr,left,right,temp);
free(tmp);
return;
递归函数的实现如下:
void _MergeSort(int arr[],int left,int right,int temp[])
{
if(right - left <= 1)
{
//带排序区间中最多只有一个元素,此时不用在排序
return;
}
//现将待排序序列一分为二,如果某一部分的元素个数小于等于1,则认为已经排好序
int mid = left + (right - left)/2;
//此时拆分的两个待区间为:[left,mid),[mid,right)
//分别对这两部分进行排序
_MergeSort(arr,left,mid,temp);
_MergeSort(arr,mid,right,temp);
//将排好序的两部分进行合并,即合并两个有序数组
MergeArray(arr,left,mid,right,temp);
return;
}
下面就要来实现合并两个有序数组。与合并单链表类似,这里在定义一片内存来保存已经排好序的区间。
定义两个变量index1,index2,分别指向两个有序数组的起始位置。定义变量temp_index遍历新定义的区间,初始为index1:
(1)如果index1处的值小于index2处的值,则将index1处的值赋值到temp_index处,此时index1和temp_index均后移;进行(1)~(2)
(2)不满足(1),则将index2处的值赋值到temp_index处,此时index2和temp_index均后移;进行(1)~(2)
(3)当index1或index2遍历完其对应的待排序区间后。此时判断哪个区间没有遍历完,则将该区间内的元素一一复制到新区间中。
(4)最后,将新区间已经排好序的区间再根据对应下标赋值到原区间中。
代码实现如下:
//合并两个有序数组使之成为新的有序数组
void MergeArray(int arr[],int left,int mid,int right,int temp[])
{
//使合并后的新的有序数组的下标与两个有序数组的下标对应
int index1 = left;
int index2 = mid;
int temp_index = left;//新数组中的有序元素从left到right
//如果两个有序子数组没有遍历完,就一直遍历直到某个数组遍历结束
while(index1 < mid && index2 < right)
{
//使值小的元素先复制到新的数组
if(arr[index1] < arr[index2])
{
temp[temp_index++] = arr[index1++];
}
else
{
temp[temp_index++] = arr[index2++];
}
}
//将还未遍历完子数组的剩余元素一个个的赋值到新数组中
while(index1 < mid)
{
temp[temp_index++] = arr[index1++];
}
while(index2 < right)
{
temp[temp_index++] = arr[index2++];
}
//将temp中排好序的元素复制到arr中
//以便下一次接着对arr进行合并
int i = left;
for(;i < right;i++)
{
arr[i] = temp[i];
}
return;
}
归并排序(非递归实现)
非递归的思想与递归的思想相同,
(1) 首先,取步长gap为1
合并区间:[0*gap,1*gap),[1*gap,2*gap)
[2*gap,3*gap),[3*gap,4*gap)
[4*gap,5*gap),[5*gap,6*gap).......
(2)然后,取步长gap为2
合并区间:[0*gap,1*gap),[1*gap,2*gap)
[2*gap,3*gap),[3*gap,4*gap]......
(3)取步长gap为4,同理合并区间[0*gap,1*gap),[1*gap,2*gap).....
(4)步长的改变为:1,2,4,8,...,直到步长gap最大为size,此时就不需要在合并了
上述过程中,需要两重循环来实现,第一重用于步长的改变,第二重用于区间的合并
void MergeSortByLoop(int arr[],int size)
{
if(arr == NULL || size <= 1)
{
//数组非法或者元素个数小于等于1,此时都不需要进行排序,直接返回即可
return;
}
int* temp = (int*)malloc(size*sizeof(int));
//步长的变化为:1,2,4,8,...后一次的步长是上一次的2倍
int gap = 1;
for(;gap < size;gap = 2*gap)
{
//每两个区间之间的间隔为:2*gap
//即:0,2*gap,4*gap,8*gap,...
//所以区间的增长差2*gap
int index = 0;
for(;index < size;index = index + 2*gap)
{
int left = index;
int mid = index + gap;
//防止最后只剩余一个区间,从而无法进行合并
if(mid >= size)
{
mid = left;
}
int right = index + 2*gap;
if(right >= size)
{
right = left;
}
MergeArray(arr,left,mid,right,temp);
}
}
free(tmp);
}
递归实现归并排序的时间复杂度为:O(nlogn),空间复杂度为:O(N),稳定性为:稳定

本文详细介绍了归并排序的递归和非递归实现方式。在递归实现中,通过不断将序列一分为二并排序后合并,直到序列只剩一个元素。非递归实现则是通过逐步改变步长进行区间合并。两种方法的时间复杂度均为O(nlogn),空间复杂度为O(N),且归并排序是稳定的排序算法。
2439

被折叠的 条评论
为什么被折叠?



