在本文中使用到的升序,降序,交换函数的代码实现见:这篇博客
在单链表的基本操作中,有一个操作为合并两个有序的单链表使合并后的链表仍然有序。本文中归并排序与这个思想类似。不断使两两有序的序列进行合并。
归并排序(递归实现)
例如,有一待排序序列: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),稳定性为:稳定