十大经典排序算法——归并排序
归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
归并排序(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 中这种方式不太可行,因为这个算法的递归深度对它来讲太深了。
和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是 O(nlogn) 的时间复杂度。代价是需要额外的内存空间。归并排序的核心思想是将一个大问题分解成若干个小问题,分别解决这些小问题,然后将结果合并起来,最终得到整个问题的解。
具体到排序问题,归并排序的步骤如下:
- 分解(Divide):将待排序的数组分成两个子数组,每个子数组包含大约一半的元素。
- 解决(Conquer):递归地对每个子数组进行排序。
- 合并(Combine):将两个已排序的子数组合并成一个有序的数组。
通过不断地分解和合并,最终整个数组将被排序。
一、分治
顾名思义,分而治之。
可以看到这种结构很像一棵完全二叉树,本文的归并排序我们采用递归去实现(也可采用迭代的方式去实现)。分阶段可以理解为就是递归拆分子序列的过程,递归深度为log2n。
合并相邻有序子序列
再来看看治阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤。
二、算法步骤
-
申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
-
设定两个指针,最初位置分别为两个已经排序序列的起始位置;
-
比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
-
重复步骤 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]
。
-
四、代码实现
实例
def merge_sort(arr):
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]
JavaScript
function mergeSort(arr) { // 采用自上而下的递归方法
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
def mergeSort(arr):
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
func mergeSort(arr []int) []int {
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
public class MergeSort implements IArraySort {
@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
function mergeSort($arr)
{
$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
int min(int x, int y) {
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);
}
递归:
void merge_sort_recursive(int arr[], int reg[], int start, int end) {
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++
迭代:
template<typename T> // 整數或浮點數皆可使用,若要使用物件(class)時必須設定"小於"(<)的運算子功能
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;
}
递归:
void Merge(vector<int> &Array, int front, int mid, int end) {
// 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#
public static List<int> sort(List<int> lst) {
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;
}
递归:
//归并排序算法类
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++;
}
}
}
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;
}
}
Ruby
def merge list
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
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` 2
五、复杂度
1、时间复杂度
-
分解:每次将列表分成两半,需要 O(log n) 层递归。
-
合并:每层递归需要 O(n) 的时间来合并子列表。
-
总时间复杂度:O(n log n)。
2、空间复杂度
-
O(n),归并排序需要额外的空间来存储临时列表。
六、优缺点
-
优点:
-
时间复杂度稳定为 O(n log n),适合大规模数据。
-
稳定排序算法(相同元素的相对顺序不会改变)。
-
适合外部排序(如对磁盘文件进行排序)。
-
-
缺点:
-
需要额外的存储空间,空间复杂度为 O(n)。
-
对于小规模数据,性能可能不如插入排序等简单算法。
-

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐
所有评论(0)