赞
踩
1
则返回(即先把长度1的序列排序,先让它有序)。先处理左边的序列,再处理右边的序列(这里以升序为例)。将长度为1
的序列进行归并(这里使用了一个临时数组tmp
保存当前归并后的有序序列),归并(归并过程看下边解释)结束后就有了一个序列长度为2
的有序序列,然后把这长度为2
的有序序列拷贝回原来的数组,以便于后续归并,然后把右边长度为1
的序列归并为长度为2
的有序序列,然后就把两个长度为2
的有序序列(不一定两个序列长度一样,但是一定都是有序的)归并为长度为4
的有序序列。以此类推,直到得到整个数组长度的有序序列。size1
和size2
的序列,对于此时的两个序列来说,已经分别都有序了(其中当序列长度为1
的时候,即元素个数为1
,每个序列肯定有序),那么直接对两个序列从前往后遍历,找到两个序列中更小的元素放到临时数组tmp
中,直到一个序列遍历完,然后再将未遍历完的序列里的元素依次放到tmp
中,这样就合并完了两个有序序列,合并成了一个有序序列。先将序列分成两半,左边[left,mid]
和右边[mid+1,right]
其中mid = (left+right)/2
。
再先对左边序列继续分成两半,直到分成序列长度小于等于1
,则开始比较,将序列长度为1
的两个序列合并为序列长度为2
的有序序列,重复上面步骤,直到整个序列有序。
采用分治的思想,这里递归的过程类似二叉树的后续遍历(左子树–>右子树–>根)。
void _MergeSort(int *arr, int *tmp, int left, int right) { //剩一个元素或者left<right if (left >= right) return; //二分法 int mid = (left + right) / 2; _MergeSort(arr, tmp, left, mid); _MergeSort(arr, tmp, mid + 1, right); //归并到tmp数组,再拷贝回去 int index = left; int begin1 = left, end1 = mid; int begin2 = mid + 1, end2 = right; while (begin1 <= end1 && begin2 <= end2) { if (arr[begin1] <= arr[begin2]) tmp[index++] = arr[begin1++]; else tmp[index++] = arr[begin2++]; } //还有没排完的 while (end1 - begin1 >= 0) { tmp[index++] = arr[begin1++]; } while (end2 - begin2 >= 0) { tmp[index++] = arr[begin2++]; } //拷贝回去 -- 画图理解 memcpy(arr + left, tmp + left, sizeof(int) * (right - left + 1)); } //归并排序 -- 递归方法 void MergeSort(int *arr, int begin, int end) { int n = end - begin + 1; int *tmp = (int *) malloc(sizeof(int) * n); if (tmp == NULL) { printf("malloc error"); exit(-1); } _MergeSort(arr, tmp, 0, n - 1); free(tmp); }
归并排序(非递归)是采用归并排序(递归)的逆向思维,即对于归并排序(递归)是将一个序列划分,一直划分到序列长度小于等于1
才开始归并,将序列长度为1的两序列归并掉序列长度为2
的有序序列,再将长度为2
的的两个有序序列归并为长度为4
的有序序列,直到整个序列有序。那么采用逆向思维的话,那我们可以先归并长度为1
的两个有序序列,使其变成长度为2
的有序序列,再归并两个长度为2
的有序序列,使其变成长度为4
的有序序列,重复这个过程。
2
的指数倍,所以我们要注意下标越界问题:
[begin1,end1]
)和第二组数据(假设下标范围是[begin2,end2]
),如果第二组数据不存在了(begin2 >= n
),则这次不用归并了。end2 >= n
,为什么end2
有可能大于等于n
?因为两组数据的长度都是按2
的指数倍来修改的),则修改这组数据的最右边元素的下标为n-1(最后一个元素下标)。先把序列长度为1的序列归并为长度为2的有序序列。
再把序列长度为2的序列归并为长度为4的有序序列。
再把序列长度为4的序列归并为长度为8的有序序列。
再把序列长度为8的序列归并为长度为10的有序序列。
2
的指数倍,所以我们要注意下标越界问题:
[begin1,end1]
)和第二组数据(下标范围是[begin2,end2]
),如果第二组数据不存在了(begin2 >= n
),则这次不用归并了。end2 >= n
,为什么end2
有可能大于等于n
?因为两组数据的长度都是按2
的指数倍来修改的),则修改这组数据的最右边元素的下标为n-1
(最后一个元素下标)。tmp
拷贝回原数组的起始点和拷贝回去的元素个数:
tmp+i
。因为i
是每次归并的第一组元素的第一个元素的位置。end2-i+1
。因为end2
指向的是每次归并的第二组元素的最后一个位置,i
指向的是每次归并的第一组元素的第一个元素的位置。2
的指数倍增长。for
循环里的i
在经过一次归并后,应该跳到下一次需要归并的第一组数据的第一个位置上。//归并排序 -- 非递归方法 void MergeSortNonR(int *arr, int begin, int end) { int n = end - begin + 1; int *tmp = (int *) malloc(sizeof(int) * n); int gap = 1; while (gap < n) { for (int i = 0; i < n; i += 2 * gap) { int begin1 = i; int end1 = begin1 + gap - 1; int begin2 = end1 + 1; int end2 = begin2 + gap - 1; //这里得判断越界问题 //第二组不存在,就不用归并了 if (begin2 >= n) {//if(end1 >= n || begin2 >= n)也可以 break; } if (end2 >= n) { end2 = n - 1; } int index = i; while (begin1 <= end1 && begin2 <= end2) { if (arr[begin1] <= arr[begin2]) tmp[index++] = arr[begin1++]; else tmp[index++] = arr[begin2++]; } //还有没排完的 while (end1 - begin1 >= 0) { tmp[index++] = arr[begin1++]; } while (end2 - begin2 >= 0) { tmp[index++] = arr[begin2++]; } //拷贝回去 -- 画图理解 // memcpy(arr + i, tmp + i, sizeof(int) * (2 * gap)); memcpy(arr + i, tmp + i, sizeof(int) * (end2 - i + 1)); } gap *= 2; } }
tmp
数组(长度为n
),故空间复杂度为O(n)。arr[begin1] <= arr[begin2]
,也就是相同的元素先排第一组,再排第二组,所以相同元素的相对位置不会改变。OKOK,归并排序算法的递归版和非递归版的介绍就到这里。其他排序算法可以看看我另一篇博客。下面是我的github主页,里面记录了我的学习代码和leetcode的一些题的题解,有兴趣的可以看看。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。