归并排序(Merge Sort)
归并,即将两个或两个以上的有序表组成一个新的有序表。
2-路归并排序的基本思想:将序列视为n个组,将这些组两两归并归并为n/2个组,然后将这些组再两两归并,生成n/4个组,以此类推,直到只剩下一个组为止。
归并排序使用的就是分治思想,即先把序列从中间分成前后两部分,然后对前后两部分分别排序,再将排好序的两部分归并在一起,这样整个序列就都有序了。
关于递归与分治:
- 分治是一种解决问题的处理思想,而递归是一种编程技巧。
- 分治算法一般都是用递归来实现的。
#include <stdio.h>
#include <stdlib.h>
// 归并排序递归c/c++实现
/**
* Author: gamilian
*/
const int maxn =100;
//将数组a的[L1,R1]与[L2,R2]区间合并为有序区间(此处L2即为R1+1)
void merge(int a[], int L1, int R1, int L2, int R2){
int i = L1, j = L2;
int temp[maxn], index = 0;
while(i <= R1 && j <= R2){
if(a[i] <= a[j])
temp[index++] = a[i++];//将a[i]加入序列temp
else
temp[index++] = a [j++];
}
while(i <= R1)
temp[index++] = a[i++];//将[L1,R1]的剩余元素加入序列temp
while(j <= R2)
temp[index++] = a[j++];//将[L2,R2]的剩余元素加入序列temp
for(i = 0; i < index; i++){
a[L1 + i] = temp[i];//将合并后的序列赋值回数组A
}
}
void merge_sort_between(int a[], int left, int right){
if (left < right){
int mid = left + (right - left) / 2;
merge_sort_between(a, left, mid); //左区间归并排序
merge_sort_between(a, mid + 1, right); //右区间归并排序
merge(a, left, mid, mid + 1, right);
} //将左、右区间归并
}
void merge_sort(int a[], int n){
merge_sort_between(a, 0 ,n - 1);
}
// 归并排序非递归c/c++实现
/**
* Author: gamilian
*/
#define min(x, y) (x) < (y) ? (x) : (y)
void merge_sort(int a[], int n){
for(int step = 2; step / 2 <= n; step *= 2){ //step为组内元素个数,step/2为左子区间元素的个数
for(int i = 0; i < n; i += step){ //每step个元素一组,组内前step/2个元素与后step/2个元素归并
int mid = i + step / 2 - 1;
if (mid + 1 < n){ //右区间存在则合并
merge(a, i ,mid, mid + 1, min(i + step - 1 , n - 1));
}
}
}
}
# 归并排序递归python实现
"""
Author: gamilian
"""
from typing import List
def merge_sort(a: List[int]):
merge_sort_between(a, 0, len(a) - 1)
# 对[left, right]区间内的元素归并排序
def merge_sort_between(a: List[int], left: int, right: int):
if left < right:
mid = left + (right - left) // 2
# 左区间归并排序
merge_sort_between(a, left, mid)
# 右区间归并排序
merge_sort_between(a, mid + 1, right)
# 将左、右区间归并
merge(a, left, mid, mid + 1, right)
def merge(a: List[int], L1: int, R1: int, L2: int, R2: int):
# 默认L2 = R1 + 1
i, j = L1, L2
tmp = []
while i <= R1 and j <= R2:
if a[i] <= a[j]:
tmp.append(a[i])
i += 1
else:
tmp.append(a[j])
j += 1
start = i if i <= R1 else j
end = R1 if i <= R1 else R2
tmp.extend(a[start:end + 1])
length = len(tmp)
a[L1:L1 + length] = tmp
# 归并排序非递归python实现
"""
Author: gamilian
"""
from typing import List
def merge_sort(a: List[int]):
length = len(a)
step = 2
for step in range(2, length * 4, 2):
for i in range(0, length, step):
mid = i + step // 2 - 1
if mid + 1 < length:
merge(a, i, mid, mid + 1, min(i + step - 1 , length - 1))
算法的稳定性:值相同的元素,在归并前后的先后顺序不变。所以,归并排序是一个稳定的排序算法。
空间复杂度 :在归并两个有序序列为一个有序序列时,需要借助额外的存储空间。尽管每次归并操作都需要申请额外的内存空间,但在合并完成之后,临时开辟的内存空间就被释放掉了。在任意时刻,CPU 只会有一个函数在执行,也就只会有一个临时的内存空间在使用。临时内存空间最大也不会超过 n 个数据的大小,所以空间复杂度是 O(n)。
时间复杂度:归并排序采用分治的思想:将一个大问题a分为多个子问题b、c。如果我们定义求解问题 a 的时间是 T(n),求解问题 b、c 的时间分别是 T(n/2) 和 T(n/2),而merge() 函数合并两个有序子数组的时间复杂度是 O(n)。那我们就可以得到这样的递推关系式:
T(1) = C; // n = 1时,只需要常量级的执行时间,所以表示为C。
T(n) = 2 * T(n/2) + n; //n > 1
故:
T(n) = 2*T(n/2) + n
= 2*(2*T(n/4) + n/2) + n
= 4*T(n/4) + 2*n
= 4*(2*T(n/8) + n/4) + 2*n
= 8*T(n/8) + 3*n = 8*(2*T(n/16) + n/8) + 3*n
= 16*T(n/16) + 4*n ......
= 2^k * T(n/2^k) + k * n ......
当 T(n/2^k) =T(1) 时,也就是 n/2^k=1,我们得到 k=log2n 。T(n)=Cn+nlog2n 。
归并排序的执行效率与要排序的原始数组的有序程度无关,所以其时间复杂度是非常稳定的,不管是最好情况、最坏情况,还是平均情况,时间复杂度都是 O(nlogn)。