归并排序
归并排序不是原地排序,时间复杂度为 O(nlogn)
实现
merge过程:将两个排序数组合并成一个数组的过程,将数组分成两部分:[1,3,5,6]、[2,4,7,8],每次比较i、j两个位置元素的大小;
1. 自顶向下的归并排序
function mergeSort(arr, l = 0, r = arr.length - 1) {
if (l >= r) return arr;
let mid = Math.floor((l + r) / 2);
mergeSort(arr, l, mid);
mergeSort(arr, mid + 1, r);
merge(arr, l, mid, r);
return arr;
}
/** 合并两个有序的区间 arr[l, mid] 和 arr[mid + 1, r] */
function merge(arr, l, mid, r) {
// 拷贝之前的数组
const temp = arr.slice(l, r + 1);
let i = l,
j = mid + 1;
for (let k = l; k <= r; k++) {
// 左侧已经全部排序完毕
if (i > mid) {
arr[k] = temp[j - l];
j++;
// 右侧已经全部排序完毕
} else if (j > r) {
arr[k] = temp[i - l];
i++;
// 左侧的数字小于右侧的数字
} else if (temp[i - l] < temp[j - l]) {
arr[k] = temp[i - l];
i++;
// 右侧的数字小于左侧的数字
} else {
arr[k] = temp[j - l];
j++;
}
}
return arr;
}
2.归并排序的优化
- 判断是否需要merge
- 只创建一个临时空间
function mergeSort2(arr) {
// 优化2: 只创建一个临时空间
const temp = arr.slice();
const sort = (arr, l, r, temp) => {
if (l >= r) return arr;
let mid = Math.floor((l + r) / 2);
sort(arr, l, mid, temp);
sort(arr, mid + 1, r, temp);
// 优化1:当arr本身就是有序的时候,不需要再次排序
if (arr[mid] > arr[mid + 1]) {
merge2(arr, l, mid, r, temp);
}
return arr;
};
return sort(arr, 0, arr.length - 1, temp);
}
function merge2(arr, l, mid, r, temp) {
for (let i = l; i <= r; i++) {
temp[i] = arr[i];
}
let i = l,
j = mid + 1;
for (let k = l; k <= r; k++) {
if (i > mid) {
arr[k] = temp[j];
j++;
} else if (j > r) {
arr[k] = temp[i];
i++;
} else if (temp[i] <= temp[j]) {
arr[k] = temp[i];
i++;
} else {
arr[k] = temp[j];
j++;
}
}
return arr;
}
3.自底向上的归并排序
function merge2(arr, l, mid, r, temp) {
for (let i = l; i <= r; i++) {
temp[i] = arr[i];
}
let i = l,
j = mid + 1;
for (let k = l; k <= r; k++) {
if (i > mid) {
arr[k] = temp[j];
j++;
} else if (j > r) {
arr[k] = temp[i];
i++;
} else if (temp[i] <= temp[j]) {
arr[k] = temp[i];
i++;
} else {
arr[k] = temp[j];
j++;
}
}
return arr;
}
function mergeSort3(arr) {
const temp = arr.slice();
const len = arr.length;
for (let size = 1; size < len; size = 2 * size) {
for (let i = 0; i + size < len; i += 2 * size) {
if (arr[i + size - 1] > arr[i + size]) {
merge2(
arr,
i,
Math.min(i + size - 1, len - 1),
Math.min(i + 2 * size - 1, len - 1),
temp
);
}
}
}
return arr;
}
力扣刷题
题目描述
剑指 Offer 51. 数组中的逆序对
难度为 困难
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
限制:
0 <= 数组长度 <= 50000
分析
利用归并排序,后面区间([2,4,7,8])的元素归并上来时,和前面区间([1,3,5,6])剩余元素形成逆序对;
function reversePairs(arr) {
const temp = arr.slice();
let res = 0;
const sort = (arr, l, r, temp) => {
if (l >= r) return arr;
let mid = Math.floor((l + r) / 2);
sort(arr, l, mid, temp);
sort(arr, mid + 1, r, temp);
if (arr[mid] > arr[mid + 1]) {
merge(arr, l, mid, r, temp);
}
return arr;
};
function merge(arr, l, mid, r, temp) {
for (let i = l; i <= r; i++) {
temp[i] = arr[i];
}
let i = l,
j = mid + 1;
for (let k = l; k <= r; k++) {
if (i > mid) {
arr[k] = temp[j];
j++;
} else if (j > r) {
arr[k] = temp[i];
i++;
} else if (temp[i] <= temp[j]) {
arr[k] = temp[i];
i++;
} else {
res += mid - i + 1;
arr[k] = temp[j];
j++;
}
}
return arr;
}
sort(arr, 0, arr.length - 1, temp);
return res;
}