排序篇(四)----归并排序
1.归并(递归)
基本思想:
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
归并排序核心步骤:
//归并 void _MergeSort(int* a, int* tmp, int begin, int end) { if (end <= begin) { return; } int mid = (begin + end) / 2; _MergeSort(a, tmp, begin, mid); _MergeSort(a, tmp, mid + 1, end); // a->[begin, mid][mid+1, end]->tmp //归并 int begin1 = begin, end1 = mid; int begin2 = mid + 1, end2 = end; int index = begin; while (begin1 <= end1 && begin2 <= end2) { if (a[begin1] <= a[begin2]) { tmp[index++] = a[begin1++]; } else { tmp[index++] = a[begin2++]; } } while (begin1 <= end1) { tmp[index++] = a[begin1++]; } while (begin2 <= end2) { tmp[index++] = a[begin2++]; } memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int)); } //归并(递归版) void MergeSort(int* a, int n) { int* tmp = malloc(sizeof(int) * n); if (tmp == NULL) { perror("malloc fail"); exit(-1); } _MergeSort(a, tmp, 0, n - 1); free(tmp); }
归并需要开一个同样大的tmp数组来存放数据,相当于是拿空间来换时间了.
代码详解:
- 首先,将整个序列分为两部分,分别递归调用_MergeSort函数对左右两部分进行排序。
- 在_MergeSort函数中,首先判断递归终止条件,如果end小于等于begin,则表示当前子序列只有一个元素或者为空,无需排序,直接返回。
- 然后,计算中间位置mid,并分别递归调用_MergeSort函数对左右两部分进行排序。
- 接下来,进行归并操作。首先,设置四个指针begin1、end1、begin2、end2分别指向左右两部分的起始和结束位置,以及一个指针index指向当前归并结果的位置。
- 然后,使用两个循环比较左右两部分的元素大小,并将较小的元素放入tmp数组中,同时移动相应的指针。
- 最后,将剩余的元素复制到tmp数组中。
- 最后,将tmp数组中的元素复制回原数组a中,完成归并排序。
2.归并(非递归)
非递归版是在递归上进行了修改,将其递归改为了循环.
//归并(非递归版) void _MergeSortNotR(int* a, int n) { int* tmp = (int*)malloc(sizeof(int) * n); if (tmp == NULL) { perror("malloc fail"); exit(-1); } int gap = 1; while (gap < n) { for (int i = 0; i < n; i += 2 * gap) { int begin1 = i, end1 = i + gap - 1; int begin2 = i + gap, end2 = i + 2 * gap - 1; if (begin2 >= n) { break; } if (end2 >= n) { end2 = n - 1; } int index = i; while (begin1 <= end1 && begin2 <= end2) { if (a[begin1] <= a[begin2]) { tmp[index++] = a[begin1++]; } else { tmp[index++] = a[begin2++]; } } while (begin1 <= end1) { tmp[index++] = a[begin1++]; } while (begin2 <= end2) { tmp[index++] = a[begin2++]; } memcpy(a+i, tmp+i, (end2-i+1) * sizeof(int)); } gap *= 2; } free(tmp); }
代码详解:
分配一个临时数组tmp,用于存储归并结果。
然后,设置一个变量gap为1,表示归并的间隔大小。
接下来,循环执行以下操作,直到gap大于等于n:
- 遍历整个数组,每次取两个相邻的子序列进行归并。
- 计算左右两个子序列的起始和结束位置。
- 判断右子序列的结束位置是否超过了数组的长度,如果超过,则将结束位置设置为数组的最后一个元素的下标。
- 使用两个指针begin1和begin2分别指向左右两个子序列的起始位置,使用指针end1和end2分别指向左右两个子序列的结束位置。
- 使用一个指针index指向当前归并结果的位置。
- 使用一个循环,比较左右两个子序列的元素大小,并将较小的元素放入临时数组tmp中,同时移动相应的指针。
- 如果左子序列还有剩余元素,则将剩余元素复制到tmp数组中。
- 如果右子序列还有剩余元素,则将剩余元素复制到tmp数组中。
- 将tmp数组中的元素复制回原数组a中。
- 将gap乘以2,进行下一轮归并。
最后,释放临时数组tmp的内存空间。
归并排序的特性总结:
- 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
- 时间复杂度:O(N*logN)
- 空间复杂度:O(N)
- 中的元素复制回原数组a中。
- 将gap乘以2,进行下一轮归并。
最后,释放临时数组tmp的内存空间。
归并排序的特性总结:
- 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
- 时间复杂度:O(N*logN)
- 空间复杂度:O(N)
- 稳定性:稳定