一、归并排序思想
- 归并排序就是先把左半边数组排好序,再把右半边数组排好序,然后把两半数组合并,类似二叉树的后序遍历。
// 定义:排序 nums[lo..hi]
void sort(int[] nums, int lo, int hi) {
if (lo == hi) {
return;
}
int mid = (lo + hi) / 2;
// 利用定义,排序 nums[lo..mid]
sort(nums, lo, mid);
// 利用定义,排序 nums[mid+1..hi]
sort(nums, mid + 1, hi);
/****** 后序位置 ******/
// 此时两部分子数组已经被排好序
// 合并两个有序数组,使 nums[lo..hi] 有序
merge(nums, lo, mid, hi);
/*********************/
}
// 将有序数组 nums[lo..mid] 和有序数组 nums[mid+1..hi]
// 合并为有序数组 nums[lo..hi]
void merge(int[] nums, int lo, int mid, int hi);
/* 二叉树遍历框架 */
void traverse(TreeNode root) {
if (root == null) {
return;
}
traverse(root.left);
traverse(root.right);
/****** 后序位置 ******/
print(root.val);
/*********************/
}
2 对比快速排序:快速排序是先将一个元素排好序,然后再将剩下的元素排好序,类似二叉树的前序遍历。
void sort(int[] nums, int lo, int hi) {
if (lo >= hi) {
return;
}
// 对 nums[lo..hi] 进行切分
// 使得 nums[lo..p-1] <= nums[p] < nums[p+1..hi]
int p = partition(nums, lo, hi);
// 去左右子数组进行切分
sort(nums, lo, p - 1);
sort(nums, p + 1, hi);
}
/* 二叉树遍历框架 */
void traverse(TreeNode root) {
if (root == null) {
return;
}
/****** 前序位置 ******/
print(root.val);
/*********************/
traverse(root.left);
traverse(root.right);
}
参考:labuladong.github.io/algo/di-yi-…
二、归并js实现,版本1(使用temp,存入排序后的数组)
// 归并排序
function mergeSort(arr, left, right) {
if (left >= right) return
let mid = Math.floor((left + right) / 2)
mergeSort(arr, left, mid)
mergeSort(arr, mid + 1, right)
merge(arr, left, mid, right)
}
function merge(arr, left, mid, right) {
let temp = []
let i = left
let j = mid + 1
let k = 0
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++]
} else {
temp[k++] = arr[j++]
}
}
while (i <= mid) {
temp[k++] = arr[i++]
}
while (j <= right) {
temp[k++] = arr[j++]
}
for (let i = 0; i < k; i++) {
arr[left + i] = temp[i]
}
}
// 测试
const arr3 = [1, 3, 2, 5, 4, 6, 7, 8, 9, 10]
mergeSort(arr3, 0, arr3.length - 1)
console.log('mergesort', arr3)
三、归并js实现,优化版2(减少temp的创建和释放)
使用temp,存入排序前的数组。只是是变更了merge函数
function mergeSort(arr, left, right) {
if (left >= right) return
let mid = Math.floor((left + right) / 2)
mergeSort(arr, left, mid)
mergeSort(arr, mid + 1, right)
merge(arr, left, mid, right)
}
const temp = []
function merge(arr, left, mid, right) {
for (let i = left; i <= right; i++) {
temp[i] = arr[i]
}
// 比较左右两边的元素,谁小就先放谁
let i = left
let j = mid + 1
while (i <= mid && j <= right) {
if (temp[i] <= temp[j]) {
arr[left++] = temp[i++]
} else {
arr[left++] = temp[j++]
}
}
// 如果左边还有剩余,直接放到结果数组中
while (i <= mid) {
arr[left++] = temp[i++]
}
// 如果右边还有剩余,直接放到结果数组中
while (j <= right) {
arr[left++] = temp[j++]
}
}
四、算法分析
执行的次数是二叉树节点的高度,每次执行的复杂度就是每个节点代表的子数组的长度,所以总的时间复杂度就是整棵树中「数组元素」的个数。
所以从整体上看,这个二叉树的高度是 logN,其中每一层的元素个数就是原数组的长度 N,所以总的时间复杂度就是 O(NlogN)。