|
|
@ -4,7 +4,7 @@ comments: true
|
|
|
|
|
|
|
|
|
|
|
|
# 归并排序
|
|
|
|
# 归并排序
|
|
|
|
|
|
|
|
|
|
|
|
「归并排序 Merge Sort」是算法中 “分治思想” 的典型体现,其有「划分」和「合并」两个阶段:
|
|
|
|
「归并排序 Merge Sort」是算法中“分治思想”的典型体现,其有「划分」和「合并」两个阶段:
|
|
|
|
|
|
|
|
|
|
|
|
1. **划分阶段:** 通过递归不断 **将数组从中点位置划分开**,将长数组的排序问题转化为短数组的排序问题;
|
|
|
|
1. **划分阶段:** 通过递归不断 **将数组从中点位置划分开**,将长数组的排序问题转化为短数组的排序问题;
|
|
|
|
2. **合并阶段:** 划分到子数组长度为 1 时,开始向上合并,不断将 **左、右两个短排序数组** 合并为 **一个长排序数组**,直至合并至原数组时完成排序;
|
|
|
|
2. **合并阶段:** 划分到子数组长度为 1 时,开始向上合并,不断将 **左、右两个短排序数组** 合并为 **一个长排序数组**,直至合并至原数组时完成排序;
|
|
|
@ -78,13 +78,13 @@ comments: true
|
|
|
|
int i = leftStart, j = rightStart;
|
|
|
|
int i = leftStart, j = rightStart;
|
|
|
|
// 通过覆盖原数组 nums 来合并左子数组和右子数组
|
|
|
|
// 通过覆盖原数组 nums 来合并左子数组和右子数组
|
|
|
|
for (int k = left; k <= right; k++) {
|
|
|
|
for (int k = left; k <= right; k++) {
|
|
|
|
// 若 “左子数组已全部合并完”,则选取右子数组元素,并且 j++
|
|
|
|
// 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++
|
|
|
|
if (i > leftEnd)
|
|
|
|
if (i > leftEnd)
|
|
|
|
nums[k] = tmp[j++];
|
|
|
|
nums[k] = tmp[j++];
|
|
|
|
// 否则,若 “右子数组已全部合并完” 或 “左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++
|
|
|
|
// 否则,若“右子数组已全部合并完”或“左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++
|
|
|
|
else if (j > rightEnd || tmp[i] <= tmp[j])
|
|
|
|
else if (j > rightEnd || tmp[i] <= tmp[j])
|
|
|
|
nums[k] = tmp[i++];
|
|
|
|
nums[k] = tmp[i++];
|
|
|
|
// 否则,若 “左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
|
|
|
|
// 否则,若“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
|
|
|
|
else
|
|
|
|
else
|
|
|
|
nums[k] = tmp[j++];
|
|
|
|
nums[k] = tmp[j++];
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -122,13 +122,13 @@ comments: true
|
|
|
|
int i = leftStart, j = rightStart;
|
|
|
|
int i = leftStart, j = rightStart;
|
|
|
|
// 通过覆盖原数组 nums 来合并左子数组和右子数组
|
|
|
|
// 通过覆盖原数组 nums 来合并左子数组和右子数组
|
|
|
|
for (int k = left; k <= right; k++) {
|
|
|
|
for (int k = left; k <= right; k++) {
|
|
|
|
// 若 “左子数组已全部合并完”,则选取右子数组元素,并且 j++
|
|
|
|
// 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++
|
|
|
|
if (i > leftEnd)
|
|
|
|
if (i > leftEnd)
|
|
|
|
nums[k] = tmp[j++];
|
|
|
|
nums[k] = tmp[j++];
|
|
|
|
// 否则,若 “右子数组已全部合并完” 或 “左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++
|
|
|
|
// 否则,若“右子数组已全部合并完”或“左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++
|
|
|
|
else if (j > rightEnd || tmp[i] <= tmp[j])
|
|
|
|
else if (j > rightEnd || tmp[i] <= tmp[j])
|
|
|
|
nums[k] = tmp[i++];
|
|
|
|
nums[k] = tmp[i++];
|
|
|
|
// 否则,若 “左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
|
|
|
|
// 否则,若“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
|
|
|
|
else
|
|
|
|
else
|
|
|
|
nums[k] = tmp[j++];
|
|
|
|
nums[k] = tmp[j++];
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -166,15 +166,15 @@ comments: true
|
|
|
|
i, j = left_start, right_start
|
|
|
|
i, j = left_start, right_start
|
|
|
|
# 通过覆盖原数组 nums 来合并左子数组和右子数组
|
|
|
|
# 通过覆盖原数组 nums 来合并左子数组和右子数组
|
|
|
|
for k in range(left, right + 1):
|
|
|
|
for k in range(left, right + 1):
|
|
|
|
# 若 “左子数组已全部合并完”,则选取右子数组元素,并且 j++
|
|
|
|
# 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++
|
|
|
|
if i > left_end:
|
|
|
|
if i > left_end:
|
|
|
|
nums[k] = tmp[j]
|
|
|
|
nums[k] = tmp[j]
|
|
|
|
j += 1
|
|
|
|
j += 1
|
|
|
|
# 否则,若 “右子数组已全部合并完” 或 “左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++
|
|
|
|
# 否则,若“右子数组已全部合并完”或“左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++
|
|
|
|
elif j > right_end or tmp[i] <= tmp[j]:
|
|
|
|
elif j > right_end or tmp[i] <= tmp[j]:
|
|
|
|
nums[k] = tmp[i]
|
|
|
|
nums[k] = tmp[i]
|
|
|
|
i += 1
|
|
|
|
i += 1
|
|
|
|
# 否则,若 “左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
|
|
|
|
# 否则,若“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
nums[k] = tmp[j]
|
|
|
|
nums[k] = tmp[j]
|
|
|
|
j += 1
|
|
|
|
j += 1
|
|
|
@ -214,15 +214,15 @@ comments: true
|
|
|
|
i, j := left_start, right_start
|
|
|
|
i, j := left_start, right_start
|
|
|
|
// 通过覆盖原数组 nums 来合并左子数组和右子数组
|
|
|
|
// 通过覆盖原数组 nums 来合并左子数组和右子数组
|
|
|
|
for k := left; k <= right; k++ {
|
|
|
|
for k := left; k <= right; k++ {
|
|
|
|
// 若 “左子数组已全部合并完”,则选取右子数组元素,并且 j++
|
|
|
|
// 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++
|
|
|
|
if i > left_end {
|
|
|
|
if i > left_end {
|
|
|
|
nums[k] = tmp[j]
|
|
|
|
nums[k] = tmp[j]
|
|
|
|
j++
|
|
|
|
j++
|
|
|
|
// 否则,若 “右子数组已全部合并完” 或 “左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++
|
|
|
|
// 否则,若“右子数组已全部合并完”或“左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++
|
|
|
|
} else if j > right_end || tmp[i] <= tmp[j] {
|
|
|
|
} else if j > right_end || tmp[i] <= tmp[j] {
|
|
|
|
nums[k] = tmp[i]
|
|
|
|
nums[k] = tmp[i]
|
|
|
|
i++
|
|
|
|
i++
|
|
|
|
// 否则,若 “左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
|
|
|
|
// 否则,若“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
nums[k] = tmp[j]
|
|
|
|
nums[k] = tmp[j]
|
|
|
|
j++
|
|
|
|
j++
|
|
|
@ -264,13 +264,13 @@ comments: true
|
|
|
|
let i = leftStart, j = rightStart;
|
|
|
|
let i = leftStart, j = rightStart;
|
|
|
|
// 通过覆盖原数组 nums 来合并左子数组和右子数组
|
|
|
|
// 通过覆盖原数组 nums 来合并左子数组和右子数组
|
|
|
|
for (let k = left; k <= right; k++) {
|
|
|
|
for (let k = left; k <= right; k++) {
|
|
|
|
// 若 “左子数组已全部合并完”,则选取右子数组元素,并且 j++
|
|
|
|
// 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++
|
|
|
|
if (i > leftEnd) {
|
|
|
|
if (i > leftEnd) {
|
|
|
|
nums[k] = tmp[j++];
|
|
|
|
nums[k] = tmp[j++];
|
|
|
|
// 否则,若 “右子数组已全部合并完” 或 “左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++
|
|
|
|
// 否则,若“右子数组已全部合并完”或“左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++
|
|
|
|
} else if (j > rightEnd || tmp[i] <= tmp[j]) {
|
|
|
|
} else if (j > rightEnd || tmp[i] <= tmp[j]) {
|
|
|
|
nums[k] = tmp[i++];
|
|
|
|
nums[k] = tmp[i++];
|
|
|
|
// 否则,若 “左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
|
|
|
|
// 否则,若“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
nums[k] = tmp[j++];
|
|
|
|
nums[k] = tmp[j++];
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -309,13 +309,13 @@ comments: true
|
|
|
|
let i = leftStart, j = rightStart;
|
|
|
|
let i = leftStart, j = rightStart;
|
|
|
|
// 通过覆盖原数组 nums 来合并左子数组和右子数组
|
|
|
|
// 通过覆盖原数组 nums 来合并左子数组和右子数组
|
|
|
|
for (let k = left; k <= right; k++) {
|
|
|
|
for (let k = left; k <= right; k++) {
|
|
|
|
// 若 “左子数组已全部合并完”,则选取右子数组元素,并且 j++
|
|
|
|
// 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++
|
|
|
|
if (i > leftEnd) {
|
|
|
|
if (i > leftEnd) {
|
|
|
|
nums[k] = tmp[j++];
|
|
|
|
nums[k] = tmp[j++];
|
|
|
|
// 否则,若 “右子数组已全部合并完” 或 “左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++
|
|
|
|
// 否则,若“右子数组已全部合并完”或“左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++
|
|
|
|
} else if (j > rightEnd || tmp[i] <= tmp[j]) {
|
|
|
|
} else if (j > rightEnd || tmp[i] <= tmp[j]) {
|
|
|
|
nums[k] = tmp[i++];
|
|
|
|
nums[k] = tmp[i++];
|
|
|
|
// 否则,若 “左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
|
|
|
|
// 否则,若“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
nums[k] = tmp[j++];
|
|
|
|
nums[k] = tmp[j++];
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -370,7 +370,7 @@ comments: true
|
|
|
|
|
|
|
|
|
|
|
|
归并排序有一个很特别的优势,用于排序链表时有很好的性能表现,**空间复杂度可被优化至 $O(1)$** ,这是因为:
|
|
|
|
归并排序有一个很特别的优势,用于排序链表时有很好的性能表现,**空间复杂度可被优化至 $O(1)$** ,这是因为:
|
|
|
|
|
|
|
|
|
|
|
|
- 由于链表可仅通过改变指针来实现结点增删,因此 “将两个短有序链表合并为一个长有序链表” 无需使用额外空间,即回溯合并阶段不用像排序数组一样建立辅助数组 `tmp` ;
|
|
|
|
- 由于链表可仅通过改变指针来实现结点增删,因此“将两个短有序链表合并为一个长有序链表”无需使用额外空间,即回溯合并阶段不用像排序数组一样建立辅助数组 `tmp` ;
|
|
|
|
- 通过使用「迭代」代替「递归划分」,可省去递归使用的栈帧空间;
|
|
|
|
- 通过使用「迭代」代替「递归划分」,可省去递归使用的栈帧空间;
|
|
|
|
|
|
|
|
|
|
|
|
> 详情参考:[148. 排序链表](https://leetcode-cn.com/problems/sort-list/solution/sort-list-gui-bing-pai-xu-lian-biao-by-jyd/)
|
|
|
|
> 详情参考:[148. 排序链表](https://leetcode-cn.com/problems/sort-list/solution/sort-list-gui-bing-pai-xu-lian-biao-by-jyd/)
|
|
|
|