归并排序(Merge Sort)
【摘要】 归并排序(Merge Sort)
归并排序(Merge Sort)
认识
归并排序(Merge Sort)是一种基于分治法(Divide and Conquer)的高效排序算法。它将数组分成两半,分别对两半进行排序,然后将排序好的两半合并成一个有序数组。归并排序是稳定排序算法,时间复杂度为O(n log n)。
大致实现如下
function mergeSort(arr) {
if(arr.length === 1) return arr
let mid = Math.floor(arr.length / 2)
let left = arr.slice(0, mid)
let right = arr.slice(mid)
return merge(mergeSort(left), mergeSort(right))
}
function merge(a, b) {
let res = []
while (a.length && b.length) {
if (a[0] < b[0]) {
res.push(a[0])
a.shift()
} else {
res.push(b[0])
b.shift()
}
}
if(a.length){
res = res.concat(a)
} else {
res = res.concat(b)
}
return res
}
// 功能测试
const arr = [4, 3, 6, 2, 5, 7, 9, 8, 1]
console.log(mergeSort(arr)) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
原理
归并排序的核心思想是"分而治之",主要包含两个步骤:
- 分:将数组不断二分,直到每个子数组只有一个元素(单个元素自然是有序的)
- 治:将有序的子数组两两合并,直到合并成一个完整的有序数组
主要就是分为两步
- 分割:将待排序的线性表不断地切分成若干个子表,直到每个子表只包含一个元素,这时,可以认为只包含一个元素的子表是有序表。
- 归并:将子表两两合并,每合并一次,就会产生一个新的且更长的有序表,重复这一步骤,直到最后只剩下一个子表,这个子表就是排好序的线性表。
算法步骤
- 分解阶段:
- 将数组从中间分成两个子数组
- 递归地对每个子数组进行分解,直到每个子数组只有一个元素
- 合并阶段:
- 将两个有序的子数组合并成一个有序数组
- 比较两个子数组的元素,按顺序取出较小的元素放入结果数组
- 如果其中一个子数组已经全部取出,则将另一个子数组的剩余元素直接复制到结果数组
时间复杂度分析
- 最好时间复杂度:O(n log n)
- 平均时间复杂度:O(n log n)
- 最坏时间复杂度:O(n log n)
- 空间复杂度:O(n) - 需要额外的空间来存储合并的数组
优缺点分析
优点
- 时间复杂度稳定:在各种情况下都是O(n log n)
- 稳定排序:相等元素的相对顺序保持不变
- 适合大规模数据:对于大数据集效率较高
- 可并行化:分解阶段可以并行处理
缺点
- 空间复杂度高:需要O(n)的额外空间
- 小规模数据性能不如插入排序:对于小数组,插入排序可能更高效
- 不是原地排序:需要额外的存储空间
JavaScript实现
基础实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>归并排序</title>
</head>
<body>
<script>
// 解法1 归并排序
const merge=(left,right)=>{
let result = [];
let leftIndex = 0;
let rightIndex = 0;
while (leftIndex < left.length && rightIndex < right.length) {
if(left[leftIndex]<right[rightIndex]){
result.push(left[leftIndex])
leftIndex++;
}else{
result.push(right[rightIndex])
rightIndex++;
}
}
return result.concat(left.slice(leftIndex)).concat(right.slice(rightIndex));
};
const mergeSort=(arr)=> {
// 数组长度小于等于1,则已经有序
if(arr.length<=1){
return arr;
}
// 分解阶段:将数组分成两半
const middle=Math.floor(arr.length / 2);
const left =arr.slice(0,middle);
const right =arr.slice(middle);
return merge(mergeSort(left),mergeSort(right))
}
// 示例使用
const arr = [38, 27, 43, 3, 9, 82, 10];
console.log("原始数组:", arr);
console.log("排序后数组:", mergeSort(arr));
</script>
</body>
</html>
优化1-对小数组使用插入排序
归并排序的方式比较适合于大数组,这种时候小数组我们可以额外使用插入排序进行优化
当数组长度小于某个阈值(如10-20)时,使用插入排序而不是继续递归归并排序
插入排序在小数组上表现通常比归并排序更好
设置阈值以后
// 2 优化-小数组使用插入排序
const INSERTION_SORT_THRESHOLD = 10;
const insertionSort = (arr) => {
for (let i = 1; i < arr.length; i++) {
let key = arr[i];
let j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
return arr;
};
const merge=(left,right)=>{
let result = [];
let leftIndex = 0;
let rightIndex = 0;
while (leftIndex < left.length && rightIndex < right.length) {
if(left[leftIndex]<right[rightIndex]){
result.push(left[leftIndex])
leftIndex++;
}else{
result.push(right[rightIndex])
rightIndex++;
}
}
return result.concat(left.slice(leftIndex)).concat(right.slice(rightIndex));
};
const mergeSort=(arr)=> {
// 数组长度小于等于1,则已经有序
if(arr.length<=INSERTION_SORT_THRESHOLD){
return insertionSort([...arr]);
}
// 分解阶段:将数组分成两半
const middle=Math.floor(arr.length / 2);
const left =arr.slice(0,middle);
const right =arr.slice(middle);
return merge(mergeSort(left), mergeSort(right));
}
优化合并过程
检查左右数组是否已经有序,避免不必要的比较
// 3-优化合并过程
// 检查左右数组是否已经有序,避免不必要的比较
// 这里感觉其实就是各种排序之间的特性进行优化
const INSERTION_SORT_THRESHOLD = 10;
const insertionSort = (arr) => {
for (let i = 1; i < arr.length; i++) {
let key = arr[i];
let j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
return arr;
};
const merge=(left,right)=>{
// 检查是否可以直接合并
if (left[left.length - 1] <= right[0]) {
return left.concat(right);
}
if (right[right.length - 1] <= left[0]) {
return right.concat(left);
}
let result = [];
let leftIndex = 0;
let rightIndex = 0;
while (leftIndex < left.length && rightIndex < right.length) {
if(left[leftIndex]<right[rightIndex]){
result.push(left[leftIndex])
leftIndex++;
}else{
result.push(right[rightIndex])
rightIndex++;
}
}
return result.concat(left.slice(leftIndex)).concat(right.slice(rightIndex));
};
const mergeSort=(arr)=> {
// 数组长度小于等于1,则已经有序
if(arr.length<=INSERTION_SORT_THRESHOLD){
return insertionSort([...arr]);
}
// 分解阶段:将数组分成两半
const middle=Math.floor(arr.length / 2);
const left =arr.slice(0,middle);
const right =arr.slice(middle);
return merge(mergeSort(left), mergeSort(right));
}
优化-预先分配结果数组空间
// 预先分配结果数组空间
const INSERTION_SORT_THRESHOLD = 10;
const insertionSort = (arr) => {
for (let i = 1; i < arr.length; i++) {
let key = arr[i];
let j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
return arr;
};
const merge=(left,right)=>{
// 检查是否可以直接合并
if (left[left.length - 1] <= right[0]) {
return left.concat(right);
}
if (right[right.length - 1] <= left[0]) {
return right.concat(left);
}
// 预先分配结果数组空间
const result = new Array(left.length + right.length);
let leftIndex = 0, rightIndex = 0, resultIndex = 0;
while (leftIndex < left.length && rightIndex < right.length) {
result[resultIndex++] = left[leftIndex] < right[rightIndex]
? left[leftIndex++]
: right[rightIndex++];
}
// 处理剩余元素
while (leftIndex < left.length) {
result[resultIndex++] = left[leftIndex++];
}
while (rightIndex < right.length) {
result[resultIndex++] = right[rightIndex++];
}
return result;
};
const mergeSort=(arr)=> {
// 数组长度小于等于1,则已经有序
if(arr.length<=INSERTION_SORT_THRESHOLD){
return insertionSort([...arr]);
}
// 分解阶段:将数组分成两半
const middle=Math.floor(arr.length / 2);
const left =arr.slice(0,middle);
const right =arr.slice(middle);
return merge(mergeSort(left), mergeSort(right));
}
优化5-避免不必要的数组复制-使用索引而不是slice来减少内存分配
const mergeSort = (arr, start = 0, end = arr.length) => {
// 数组长度小于阈值,使用插入排序
if (end - start <= INSERTION_SORT_THRESHOLD) {
return insertionSort(arr.slice(start, end));
}
const middle = Math.floor((start + end) / 2);
const left = mergeSort(arr, start, middle);
const right = mergeSort(arr, middle, end);
return merge(left, right);
};
优化-使用迭代而非递归实现
const INSERTION_SORT_THRESHOLD = 10;
const insertionSort = (arr) => {
for (let i = 1; i < arr.length; i++) {
let key = arr[i];
let j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
return arr;
};
const merge = (arr, temp, left, mid, right) => {
// 检查是否已经有序
if (arr[mid - 1] <= arr[mid]) {
return;
}
// 合并两个有序区间
let i = left, j = mid, k = left;
while (i < mid && j < right) {
temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
}
while (i < mid) {
temp[k++] = arr[i++];
}
while (j < right) {
temp[k++] = arr[j++];
}
// 将合并结果复制回原数组
for (let idx = left; idx < right; idx++) {
arr[idx] = temp[idx];
}
};
const mergeSort = (arr) => {
const n = arr.length;
const temp = new Array(n);
// 使用插入排序处理小数组
for (let i = 0; i < n; i += INSERTION_SORT_THRESHOLD) {
const end = Math.min(i + INSERTION_SORT_THRESHOLD, n);
insertionSort(arr, i, end);
}
// 自底向上的归并排序
for (let size = INSERTION_SORT_THRESHOLD; size < n; size *= 2) {
for (let left = 0; left < n - size; left += 2 * size) {
const mid = left + size;
const right = Math.min(left + 2 * size, n);
merge(arr, temp, left, mid, right);
}
}
return arr;
};
最终版本
const INSERTION_SORT_THRESHOLD = 10;
const insertionSort = (arr, start = 0, end = arr.length) => {
for (let i = start + 1; i < end; i++) {
let key = arr[i];
let j = i - 1;
while (j >= start && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
return arr;
};
const merge = (left, right) => {
// 检查是否可以直接合并
if (left[left.length - 1] <= right[0]) {
return left.concat(right);
}
if (right[right.length - 1] <= left[0]) {
return right.concat(left);
}
// 预先分配结果数组空间
const result = new Array(left.length + right.length);
let leftIndex = 0, rightIndex = 0, resultIndex = 0;
while (leftIndex < left.length && rightIndex < right.length) {
result[resultIndex++] = left[leftIndex] < right[rightIndex]
? left[leftIndex++]
: right[rightIndex++];
}
// 处理剩余元素
while (leftIndex < left.length) {
result[resultIndex++] = left[leftIndex++];
}
while (rightIndex < right.length) {
result[resultIndex++] = right[rightIndex++];
}
return result;
};
const mergeSort = (arr) => {
// 数组长度小于阈值,使用插入排序
if (arr.length <= INSERTION_SORT_THRESHOLD) {
return insertionSort([...arr]);
}
// 分解阶段:将数组分成两半
const middle = Math.floor(arr.length / 2);
const left = mergeSort(arr.slice(0, middle));
const right = mergeSort(arr.slice(middle));
return merge(left, right);
};
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)