归并排序采用了分而治之的思想,所以主要分为“分”和“治”两部分
- 分:对数组取中点,分为两部分,两个子数组同理取中点,各自分为两部分…
- 治:分到数组长度为 1 时自然就可以返回了,然后开始往上一步走,将左右两个子数组排序合并
- 比如数组 [4,3,2,1] 会先分出 [4,3] 和 [2,1] ,然后 [4,3] 继续分为 [4],和 [3],这时数组长度为 1 了,向上进行合并,把 [4] 和 [3] 合并为 [3,4] ,[2,1] 同理最终得到 [1,2],此时来到递归最外层,数组为 [3,4,1,2] 然后得到 [1,2,3,4]。排序的原理则是双指针,因为你会发现这样我们会不断得到左右两个有序数组,比如最后的 [3,4,1,2] 左边的 [3,4] 和 右边的 [1,2] 都是有序的,所以让 i,j 分别指向左右子数组的首位。首先比较 3 和 1,取 1 为 [1],然后 j+1 ,此时比较 3 和 2,取 2 为 [1,2],由于右边的子数组取完了,所以之后不用比较了,把左边的继续按顺序填充进去就行,最后得到 [1,2,3,4]。
-
int[] tmp = new int[nums.length]; void mergeSort(int[] nums, int l, int r) { if (l >= r) return; int m = (l + r) / 2; mergeSort(l,m); mergeSort(m+1,r); // 复制当前要治的数组 for(int k=l; k<=r; k++)tmp[k] = nums[k]; // 双指针排序,i:左数组首位,j:右数组首位(或者说当前总数组的中点位后一位) int i=l,j=m+1; for (int k = l; k <= r; k++) { // 遍历合并左/右子数组 if (i == m + 1) // 如果左数组用完了那就直接填充左数组了 nums[k] = tmp[j++]; else if (j == r + 1) // 如果右数组用完了 nums[k] = tmp[i++]; else if (tmp[i] <= tmp[j]) // 正常比较大小,先放小的 nums[k] = tmp[i++]; else { // tmp[i]>tmp[j] nums[k] = tmp[j++]; } } }
- 其实右数组用完和右数组当前位更小能合并到一起
-
for (int k = l; k <= r; k++) { // 遍历合并左/右子数组 if (i == m - l + 1) // 如果左数组用完了那就直接填充左数组了 nums[k] = tmp[j++]; // 如果右数组用完了或右数组当前位更小 else if (j == r - l + 1 || tmp[i] <= tmp[j]) nums[k] = tmp[i++]; else { // tmp[i]>tmp[j] nums[k] = tmp[j++]; } }
- 你可能会想既然这样,为什么不直接
-
if (j == r - l + 1 || tmp[i] <= tmp[j]) nums[k] = tmp[i++]; else { nums[k] = tmp[j++];
- 因为这样会在 i 被用完但是 tmp[i] <= tmp[j] 时继续用 i,但是其实这时候 i 都已经指到右数组去了,以 [3, 4, 1, 5] 为例模拟最后一轮排序就知道了,最后会得到 [1,3,4,1]
-
def merge_sort(nums, l, r): m = (l + r) // 2 tmp = nums[l:r + 1] i, j = 0, m - l + 1 for k in range(l, r + 1): if j == r - l + 1 or tmp[i] <= tmp[j]: nums[k] = tmp[i] i += 1 else: nums[k] = tmp[j] j += 1 # 调用 nums = [3,4,1,5] merge_sort(nums, 0, len(nums) - 1) print(nums)