--- comments: true --- # 归并排序 「归并排序 Merge Sort」是算法中 “分治思想” 的典型体现,其有「划分」和「合并」两个阶段: 1. **划分阶段:** 通过递归不断 **将数组从中点位置划分开**,将长数组的排序问题转化为短数组的排序问题; 2. **合并阶段:** 划分到子数组长度为 1 时,开始向上合并,不断将 **左、右两个短排序数组** 合并为 **一个长排序数组**,直至合并至原数组时完成排序; ![merge_sort_preview](merge_sort.assets/merge_sort_preview.png)
Fig. 归并排序两阶段:划分与合并
## 算法流程 **「递归划分」** 从顶至底递归地 **将数组从中点切为两个子数组** ,直至长度为 1 ; 1. 计算数组中点 `mid` ,递归划分左子数组(区间 `[left, mid]` )和右子数组(区间 `[mid + 1, right]` ); 2. 递归执行 `1.` 步骤,直至子数组区间长度为 1 时,终止递归划分; **「回溯合并」** 从底至顶地将左子数组和右子数组合并为一个 **有序数组** ; 需要注意,由于从长度为 1 的子数组开始合并,所以 **每个子数组都是有序的** 。因此,合并任务本质是要 **将两个有序子数组合并为一个有序数组** 。 === "Step1" ![merge_sort_step1](merge_sort.assets/merge_sort_step1.png) === "Step2" ![merge_sort_step2](merge_sort.assets/merge_sort_step2.png) === "Step3" ![merge_sort_step3](merge_sort.assets/merge_sort_step3.png) === "Step4" ![merge_sort_step4](merge_sort.assets/merge_sort_step4.png) === "Step5" ![merge_sort_step5](merge_sort.assets/merge_sort_step5.png) === "Step6" ![merge_sort_step6](merge_sort.assets/merge_sort_step6.png) === "Step7" ![merge_sort_step7](merge_sort.assets/merge_sort_step7.png) === "Step8" ![merge_sort_step8](merge_sort.assets/merge_sort_step8.png) === "Step9" ![merge_sort_step9](merge_sort.assets/merge_sort_step9.png) === "Step10" ![merge_sort_step10](merge_sort.assets/merge_sort_step10.png) 观察发现,归并排序的递归顺序就是二叉树的「后序遍历」。 - **后序遍历:** 先递归左子树、再递归右子树、最后处理根结点。 - **归并排序:** 先递归左子树、再递归右子树、最后处理合并。 === "Java" ```java title="merge_sort.java" /** * 合并左子数组和右子数组 * 左子数组区间 [left, mid] * 右子数组区间 [mid + 1, right] */ void merge(int[] nums, int left, int mid, int right) { // 初始化辅助数组 int[] tmp = Arrays.copyOfRange(nums, left, right + 1); // 左子数组的起始索引和结束索引 int leftStart = left - left, leftEnd = mid - left; // 右子数组的起始索引和结束索引 int rightStart = mid + 1 - left, rightEnd = right - left; // i, j 分别指向左子数组、右子数组的首元素 int i = leftStart, j = rightStart; // 通过覆盖原数组 nums 来合并左子数组和右子数组 for (int k = left; k <= right; k++) { // 若 “左子数组已全部合并完”,则选取右子数组元素,并且 j++ if (i > leftEnd) nums[k] = tmp[j++]; // 否则,若 “右子数组已全部合并完” 或 “左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++ else if (j > rightEnd || tmp[i] <= tmp[j]) nums[k] = tmp[i++]; // 否则,若 “左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ else nums[k] = tmp[j++]; } } /* 归并排序 */ void mergeSort(int[] nums, int left, int right) { // 终止条件 if (left >= right) return; // 当子数组长度为 1 时终止递归 // 递归划分 int mid = (left + right) / 2; // 计算数组中点 mergeSort(nums, left, mid); // 递归左子数组 mergeSort(nums, mid + 1, right); // 递归右子数组 // 回溯合并 merge(nums, left, mid, right); } ``` === "JavaScript" ```js title="merge_sort.js" /** * 合并左子数组和右子数组 * 左子数组区间 [left, mid] * 右子数组区间 [mid + 1, right] */ function merge(nums, left, mid, right) { // 初始化辅助数组 let tmp = nums.slice(left, right + 1); // 左子数组的起始索引和结束索引 let leftStart = left - left, leftEnd = mid - left; // 右子数组的起始索引和结束索引 let rightStart = mid + 1 - left, rightEnd = right - left; // i, j 分别指向左子数组、右子数组的首元素 let i = leftStart, j = rightStart; // 通过覆盖原数组 nums 来合并左子数组和右子数组 for (let k = left; k <= right; k++) { // 若 “左子数组已全部合并完”,则选取右子数组元素,并且 j++ if (i > leftEnd) { nums[k] = tmp[j++]; // 否则,若 “右子数组已全部合并完” 或 “左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++ } else if (j > rightEnd || tmp[i] <= tmp[j]) { nums[k] = tmp[i++]; // 否则,若 “左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ } else { nums[k] = tmp[j++]; } } } /* 归并排序 */ function mergeSort(nums, left, right) { // 终止条件 if (left >= right) return; // 当子数组长度为 1 时终止递归 // 划分阶段 let mid = Math.floor((left + right) / 2); // 计算中点 mergeSort(nums, left, mid); // 递归左子数组 mergeSort(nums, mid + 1, right); // 递归右子数组 // 合并阶段 merge(nums, left, mid, right); } ``` === "C++" ```cpp title="merge_sort.cpp" /** * 合并左子数组和右子数组 * 左子数组区间 [left, mid] * 右子数组区间 [mid + 1, right] */ void merge(vector