归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:
- 自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法);
- 自下而上的迭代;
在《数据结构与算法 JavaScript 描述》中,作者给出了自下而上的迭代方法。但是对于递归法,作者却认为:
However, it is not possible to do so in JavaScript, as the recursion goes too deep for the language to handle.
然而,在 JavaScript 中这种方式不太可行,因为这个算法的递归深度对它来讲太深了。
说实话,我不太理解这句话。意思是 JavaScript 编译器内存太小,递归太深容易造成内存溢出吗?还望有大神能够指教。
和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是 O(nlogn) 的时间复杂度。代价是需要额外的内存空间。
归并排序的核心思想是将一个大问题分解成若干个小问题,分别解决这些小问题,然后将结果合并起来,最终得到整个问题的解。具体到排序问题,归并排序的步骤如下:
- 分解(Divide):将待排序的数组分成两个子数组,每个子数组包含大约一半的元素。
- 解决(Conquer):递归地对每个子数组进行排序。
- 合并(Combine):将两个已排序的子数组合并成一个有序的数组。
通过不断地分解和合并,最终整个数组将被排序。
2. 算法步骤
-
申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
-
设定两个指针,最初位置分别为两个已经排序序列的起始位置;
-
比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
-
重复步骤 3 直到某一指针达到序列尾;
-
将另一序列剩下的所有元素直接复制到合并序列尾。
3. 动图演示

假设有一个待排序的列表 [38, 27, 43, 3, 9, 82, 10],归并排序的过程如下:
分解:
将列表分成两半:
[38, 27, 43, 3]和[9, 82, 10]。继续分解:
[38, 27, 43, 3]分成[38, 27]和[43, 3]。[9, 82, 10]分成[9]和[82, 10]。
继续分解:
[38, 27]分成[38]和[27]。[43, 3]分成[43]和[3]。[82, 10]分成[82]和[10]。
最终分解为单个元素的子列表。
合并:
合并
[38]和[27]得到[27, 38]。合并
[43]和[3]得到[3, 43]。合并
[27, 38]和[3, 43]得到[3, 27, 38, 43]。合并
[9]和[10, 82]得到[9, 10, 82]。合并
[3, 27, 38, 43]和[9, 10, 82]得到最终有序列表[3, 9, 10, 27, 38, 43, 82]。
实例
if len(arr) <= 1:
return arr
# 分解:将列表分成两半
mid = len(arr) // 2
left_half = merge_sort(arr[:mid]) # 递归排序左半部分
right_half = merge_sort(arr[mid:]) # 递归排序右半部分
# 合并:将两个有序子列表合并
return merge(left_half, right_half)
def merge(left, right):
sorted_arr = []
i = j = 0
# 比较两个子列表的元素,按顺序合并
while i < len(left) and j < len(right):
if left[i] < right[j]:
sorted_arr.append(left[i])
i += 1
else:
sorted_arr.append(right[j])
j += 1
# 将剩余的元素添加到结果中
sorted_arr.extend(left[i:])
sorted_arr.extend(right[j:])
return sorted_arr
# 示例
arr = [38, 27, 43, 3, 9, 82, 10]
sorted_arr = merge_sort(arr)
print(sorted_arr) # 输出: [3, 9, 10, 27, 38, 43, 82]
时间复杂度
分解:每次将列表分成两半,需要 O(log n) 层递归。
合并:每层递归需要 O(n) 的时间来合并子列表。
总时间复杂度:O(n log n)。
空间复杂度
O(n),归并排序需要额外的空间来存储临时列表。
优缺点
优点:
时间复杂度稳定为 O(n log n),适合大规模数据。
稳定排序算法(相同元素的相对顺序不会改变)。
适合外部排序(如对磁盘文件进行排序)。
缺点:
需要额外的存储空间,空间复杂度为 O(n)。
对于小规模数据,性能可能不如插入排序等简单算法。
代码实现
JavaScript
实例
var len = arr.length;
if(len < 2) {
return arr;
}
var middle = Math.floor(len / 2),
left = arr.slice(0, middle),
right = arr.slice(middle);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right)
{
var result = [];
while (left.length && right.length) {
if (left[0] <= right[0]) {
result.push(left.shift());
} else {
result.push(right.shift());
}
}
while (left.length)
result.push(left.shift());
while (right.length)
result.push(right.shift());
return result;
}
Python
实例
import math
if(len(arr)<2):
return arr
middle = math.floor(len(arr)/2)
left, right = arr[0:middle], arr[middle:]
return merge(mergeSort(left), mergeSort(right))
def merge(left,right):
result = []
while left and right:
if left[0] <= right[0]:
result.append(left.pop(0))
else:
result.append(right.pop(0));
while left:
result.append(left.pop(0))
while right:
result.append(right.pop(0));
return result
Go
实例
length := len(arr)
if length < 2 {
return arr
}
middle := length / 2
left := arr[0:middle]
right := arr[middle:]
return merge(mergeSort(left), mergeSort(right))
}
func merge(left []int, right []int) []int {
var result []int
for len(left) != 0 && len(right) != 0 {
if left[0] <= right[0] {
result = append(result, left[0])
left = left[1:]
} else {
result = append(result, right[0])
right = right[1:]
}
}
for len(left) != 0 {
result = append(result, left[0])
left = left[1:]
}
for len(right) != 0 {
result = append(result, right[0])
right = right[1:]
}
return result
}
Java
实例
@Override
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
if (arr.length < 2) {
return arr;
}
int middle = (int) Math.floor(arr.length / 2);
int[] left = Arrays.copyOfRange(arr, 0, middle);
int[] right = Arrays.copyOfRange(arr, middle, arr.length);
return merge(sort(left), sort(right));
}
protected int[] merge(int[] left, int[] right) {
int[] result = new int[left.length + right.length];
int i = 0;
while (left.length > 0 && right.length > 0) {
if (left[0] <= right[0]) {
result[i++] = left[0];
left = Arrays.copyOfRange(left, 1, left.length);
} else {
result[i++] = right[0];
right = Arrays.copyOfRange(right, 1, right.length);
}
}
while (left.length > 0) {
result[i++] = left[0];
left = Arrays.copyOfRange(left, 1, left.length);
}
while (right.length > 0) {
result[i++] = right[0];
right = Arrays.copyOfRange(right, 1, right.length);
}
return result;
}
}
PHP
实例
{
$len = count($arr);
if ($len < 2) {
return $arr;
}
$middle = floor($len / 2);
$left = array_slice($arr, 0, $middle);
$right = array_slice($arr, $middle);
return merge(mergeSort($left), mergeSort($right));
}
function merge($left, $right)
{
$result = [];
while (count($left) > 0 && count($right) > 0) {
if ($left[0] <= $right[0]) {
$result[] = array_shift($left);
} else {
$result[] = array_shift($right);
}
}
while (count($left))
$result[] = array_shift($left);
while (count($right))
$result[] = array_shift($right);
return $result;
}
C
实例
return x < y ? x : y;
}
void merge_sort(int arr[], int len) {
int *a = arr;
int *b = (int *) malloc(len * sizeof(int));
int seg, start;
for (seg = 1; seg < len; seg += seg) {
for (start = 0; start < len; start += seg * 2) {
int low = start, mid = min(start + seg, len), high = min(start + seg * 2, len);
int k = low;
int start1 = low, end1 = mid;
int start2 = mid, end2 = high;
while (start1 < end1 && start2 < end2)
b[k++] = a[start1] < a[start2] ? a[start1++] : a[start2++];
while (start1 < end1)
b[k++] = a[start1++];
while (start2 < end2)
b[k++] = a[start2++];
}
int *temp = a;
a = b;
b = temp;
}
if (a != arr) {
int i;
for (i = 0; i < len; i++)
b[i] = a[i];
b = a;
}
free(b);
}
递归版:
实例
if (start >= end)
return;
int len = end - start, mid = (len >> 1) + start;
int start1 = start, end1 = mid;
int start2 = mid + 1, end2 = end;
merge_sort_recursive(arr, reg, start1, end1);
merge_sort_recursive(arr, reg, start2, end2);
int k = start;
while (start1 <= end1 && start2 <= end2)
reg[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];
while (start1 <= end1)
reg[k++] = arr[start1++];
while (start2 <= end2)
reg[k++] = arr[start2++];
for (k = start; k <= end; k++)
arr[k] = reg[k];
}
void merge_sort(int arr[], const int len) {
int reg[len];
merge_sort_recursive(arr, reg, 0, len - 1);
}
C++
迭代版:
实例
void merge_sort(T arr[], int len) {
T *a = arr;
T *b = new T[len];
for (int seg = 1; seg < len; seg += seg) {
for (int start = 0; start < len; start += seg + seg) {
int low = start, mid = min(start + seg, len), high = min(start + seg + seg, len);
int k = low;
int start1 = low, end1 = mid;
int start2 = mid, end2 = high;
while (start1 < end1 && start2 < end2)
b[k++] = a[start1] < a[start2] ? a[start1++] : a[start2++];
while (start1 < end1)
b[k++] = a[start1++];
while (start2 < end2)
b[k++] = a[start2++];
}
T *temp = a;
a = b;
b = temp;
}
if (a != arr) {
for (int i = 0; i < len; i++)
b[i] = a[i];
b = a;
}
delete[] b;
}
递归版:
实例
// preconditions:
// Array[front...mid] is sorted
// Array[mid+1 ... end] is sorted
// Copy Array[front ... mid] to LeftSubArray
// Copy Array[mid+1 ... end] to RightSubArray
vector<int> LeftSubArray(Array.begin() + front, Array.begin() + mid + 1);
vector<int> RightSubArray(Array.begin() + mid + 1, Array.begin() + end + 1);
int idxLeft = 0, idxRight = 0;
LeftSubArray.insert(LeftSubArray.end(), numeric_limits<int>::max());
RightSubArray.insert(RightSubArray.end(), numeric_limits<int>::max());
// Pick min of LeftSubArray[idxLeft] and RightSubArray[idxRight], and put into Array[i]
for (int i = front; i <= end; i++) {
if (LeftSubArray[idxLeft] < RightSubArray[idxRight]) {
Array[i] = LeftSubArray[idxLeft];
idxLeft++;
} else {
Array[i] = RightSubArray[idxRight];
idxRight++;
}
}
}
void MergeSort(vector<int> &Array, int front, int end) {
if (front >= end)
return;
int mid = (front + end) / 2;
MergeSort(Array, front, mid);
MergeSort(Array, mid + 1, end);
Merge(Array, front, mid, end);
}
C#
实例
if (lst.Count <= 1)
return lst;
int mid = lst.Count / 2;
List<int> left = new List<int>(); // 定义左侧List
List<int> right = new List<int>(); // 定义右侧List
// 以下兩個循環把 lst 分為左右兩個 List
for (int i = 0; i < mid; i++)
left.Add(lst[i]);
for (int j = mid; j < lst.Count; j++)
right.Add(lst[j]);
left = sort(left);
right = sort(right);
return merge(left, right);
}
/// <summary>
/// 合併兩個已經排好序的List
/// </summary>
/// <param name="left">左側List</param>
/// <param name="right">右側List</param>
/// <returns></returns>
static List<int> merge(List<int> left, List<int> right) {
List<int> temp = new List<int>();
while (left.Count > 0 && right.Count > 0) {
if (left[0] <= right[0]) {
temp.Add(left[0]);
left.RemoveAt(0);
} else {
temp.Add(right[0]);
right.RemoveAt(0);
}
}
if (left.Count > 0) {
for (int i = 0; i < left.Count; i++)
temp.Add(left[i]);
}
if (right.Count > 0) {
for (int i = 0; i < right.Count; i++)
temp.Add(right[i]);
}
return temp;
}
Ruby
实例
return list if list.size < 2
pivot = list.size / 2
# Merge
lambda { |left, right|
final = []
until left.empty? or right.empty?
final << if left.first < right.first; left.shift else right.shift end
end
final + left + right
}.call merge(list[0...pivot]), merge(list[pivot..-1])
end
参考地址:
https://github.com/hustcc/JS-Sorting-Algorithm/blob/master/5.mergeSort.md
https://zh.wikipedia.org/wiki/%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F
ees
429***0967@qq.com
参考地址
分而治之
可以看到这种结构很像一棵完全二叉树,本文的归并排序我们采用递归去实现(也可采用迭代的方式去实现)。分阶段可以理解为就是递归拆分子序列的过程,递归深度为log2n。
合并相邻有序子序列
再来看看治阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤。
import java.util.Arrays; /** * Created by chengxiao on 2016/12/8. */ public class MergeSort { public static void main(String []args){ int []arr = {9,8,7,6,5,4,3,2,1}; sort(arr); System.out.println(Arrays.toString(arr)); } public static void sort(int []arr){ int []temp = new int[arr.length];//在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间 sort(arr,0,arr.length-1,temp); } private static void sort(int[] arr,int left,int right,int []temp){ if(left<right){ int mid = (left+right)/2; sort(arr,left,mid,temp);//左边归并排序,使得左子序列有序 sort(arr,mid+1,right,temp);//右边归并排序,使得右子序列有序 merge(arr,left,mid,right,temp);//将两个有序子数组合并操作 } } private static void merge(int[] arr,int left,int mid,int right,int[] temp){ int i = left;//左序列指针 int j = mid+1;//右序列指针 int t = 0;//临时数组指针 while (i<=mid && j<=right){ if(arr[i]<=arr[j]){ temp[t++] = arr[i++]; }else { temp[t++] = arr[j++]; } } while(i<=mid){//将左边剩余元素填充进temp中 temp[t++] = arr[i++]; } while(j<=right){//将右序列剩余元素填充进temp中 temp[t++] = arr[j++]; } t = 0; //将temp中的元素全部拷贝到原数组中 while(left <= right){ arr[left++] = temp[t++]; } } }ees
429***0967@qq.com
参考地址
kezhuoquan
kez***quan@163.com
rust 版本:
fn merge_sort<T: Ord + Debug + Copy>(array: &mut [T], start: usize, end: usize) { if start >= end { return; } let mid = start + (end - start) / 2; merge_sort(array, start, mid); merge_sort(array, mid + 1, end); merge(array, start, end); } fn merge<T: Ord + Debug + Copy>(array: &mut [T], start: usize, end: usize) { println!("{array:?}"); let mut temp_array = Vec::with_capacity(end - start + 1); for i in start..=end { temp_array.push(array[i]); } let new_start = 0; let new_end = temp_array.len() - 1; let new_mid = (new_end - new_start) / 2; let mut left_index = new_start; let mut right_index = new_mid + 1; let mut array_index = start; while left_index <= new_mid && right_index <= new_end { if temp_array[left_index] <= temp_array[right_index] { array[array_index] = temp_array[left_index]; left_index += 1; } else { array[array_index] = temp_array[right_index]; right_index += 1; } array_index += 1; } while left_index <= new_mid { array[array_index] = temp_array[left_index]; left_index += 1; array_index += 1; } while right_index <= new_end { array[array_index] = temp_array[right_index]; right_index += 1; array_index += 1; } }kezhuoquan
kez***quan@163.com
icekylin
wmi***@yeah.net
Haskell 版本:
merge' :: Ord a => [a] -> [a] -> [a] merge' xs [] = xs merge' [] xs = xs merge' (x : xs) (y : ys) | x > y = y : merge' (x : xs) ys | otherwise = x : merge' xs (y : ys) msort :: Ord a => [a] -> [a] msort [] = [] msort [x] = [x] msort xs = merge' (msort (take half_len xs)) (msort (drop half_len xs)) where half_len = length xs `div` 2icekylin
wmi***@yeah.net
月夜
yan***202826@163.com
参考地址
C#递归版本,仅供参考:
//归并排序算法类 class MergeTest { //方法-:归并排序的如果,提供了辅助数组,调用了后续方法 public void MergeSort(float[] arr) { if( arr.Length <= 1 ) return; float[] temp = new float[arr.Length]; //辅助数组 if (temp != null) MSort(arr, temp, 0, arr.Length - 1); //调用递归拆分 } //方法二: 递归拆分数组 ,并且调用合并的方法 private void MSort(float[] arr , float[] temp ,int left , int right) { if( left < right) //最终会使得递归得到一个元素为一组的多数组 { int mid = (left+right)/2; //left为原数组左边第一个元素下标(就是0),right是原数组右边最大下标(n-1),mid用于拆分数组 MSort(arr, temp, left, mid); //通过递归划分左半区 MSort(arr, temp, mid+1, right); //递归划分右半区 Merge(arr, temp, left, mid, right); //调用排序并合并的方法 } } //方法三:排序合并拆分的数组 private void Merge(float[] arr, float[] temp , int left , int mid , int right) { int l_pos = left; //左半区第一个未排序的元素 int r_pos = mid + 1; //右半区第一个未排序的元素 int pos = left; //辅助数组的起点下标 while(l_pos <= mid && r_pos <= right) { if(arr[l_pos] < arr[r_pos]) //逐一比较大小 { temp[pos++] = arr[l_pos++]; //左边区域元素更小,就把它放入辅助数组的第一个位置,然后左边区域与辅助数组的下标向后移动一位 } else temp[pos++] = arr[r_pos++]; //若右边区域元素更小,将其放入辅助数组,下标后移 } //跳出循环就代表左右中的一边已经全部放入辅助数组了 while( l_pos <= mid) { temp[pos++] = arr[l_pos++]; //若左区域下标还没有到达最大处,说明有剩余,直接全部复制到辅助数组最后边 } while( r_pos <= right) { temp[pos++] = arr[r_pos++];//若右区域下标还没有到达最大处,说明有剩余,直接全部复制到辅助数组最后边 } while(left <= right) { arr[left] = temp[left]; //从第一个下标到最后一个下标,把辅助数组的元素全部复制到原来的数组z left++; } } }月夜
yan***202826@163.com
参考地址
_
224***3644@qq.com
js 版本:
const mergeSort = arr => { if (arr.length === 1) { return arr } const mid = Math.floor(arr.length / 2) return ((left, right) => { const result = [] let i = 0 let j = 0 while (i < left.length && j < right.length) { result.push(left[i] > right[j] ? right[j++] : left[i++]) } while (i < left.length) { result.push(left[i++]) } while (j < right.length) { result.push(right[j++]) } return result })(mergeSort(arr.slice(0, mid)), mergeSort(arr.slice(mid))) }_
224***3644@qq.com