本文主要介绍归并排序的基本原理,基于递归调用,一步一步分析,结合流程图和文字,给读者清晰地解释。另外给出伪代码,和C++核心代码。并和插入排序相比较,最后结合插入排序,介绍一种升级的归并排序。
基本原理
归并排序利用分治法的思想,具体算法框架如下:
step1: 将待排序列 A 分为两个子序列,再将子序列一分为二,一直分到每个子序列只含有一个元素为止,这个时候,每个子序列(都只包含一个元素)已经是有序的。
step2: 根据分解的路径,对每一对子序列进行排序
step3: 将已经排序的两个子序列合并,最后合成整个序列。
分解示意图:
合并示意图:
具体的合并方式为:将两个已经排好序的子序列合并为一个新的长序列。从左到右,依次浏览两个待合并序列的第一个元素,将较小的元素移入到新序列中,当其中一个序列全部排到新序列里,则将另一个序列直接复制到新序列的末尾
伪代码
两个子序列合并的伪代码如下:
整个归并排序是一个递归过程,不断解决规模较小的子问题,然后将这些子问题的解合并得到原问题的解。伪代码如下。
本文给出的实例,递归调用步骤:
C++ 代码:
void myMerge(vector<int> & vec, int low, int mid, int high)
{
vector<int> vecLow, vecHigh;
int locL = 0, locH = 0;
int lengthLow = mid - low + 1, lengthHigh = high - mid;
int loc1;
for (loc1 = low; loc1 <= mid; loc1++)
{
vecLow.push_back(vec[loc1]);
}
for (; loc1 <= high; loc1++)
{
vecHigh.push_back(vec[loc1]);
}
int loc;
for (loc = low; loc <= high; loc++)
{
if (vecLow[locL] <= vecHigh[locH])
{
vec[loc] = vecLow[locL];
locL ++;
if (locL == lengthLow)break;
}
else
{
vec[loc] = vecHigh[locH];
locH ++;
if (locH == lengthHigh)break;
}
}
if (loc < high) // 没有排满
{
if (locL < lengthLow)
{
for (; locL < lengthLow ; locL++)
{
loc++;
vec[loc] = vecLow[locL];
}
}
else
{
for (; locH < lengthHigh ; locH++)
{
loc++;
vec[loc] = vecHigh[locH];
}
}
}
}
void mergeSort(vector<int> & vec, int low, int high)
{
if (low < high)
{
int mid = (high + low) / 2;
mergeSort(vec, low, mid);
mergeSort(vec, mid + 1, high);
myMerge(vec, low, mid, high);
}
}
性能分析
复杂度分析
由于将两个子数组合并成长度为 n 的子数组需要的时间为
Θ(n)
,根据合并示意图,假设序列长度为
N
,则递归调用 Merge 函数的级数为
根据上一篇 插入排序 的时间复杂度为
Θ(n2)
,归并排序优于插入排序。
由于归并排序在归并过程中需要与原始记录序列相等的存储空间,另外递归调用时需要深度为
⌈log2(N)⌉
的栈空间,因此空间复杂度为
c1∗n+c2∗⌈log2(N)⌉
,其中
c1,c2
为与系统相干的常数。即空间复杂度为
O(n)
。而插入排序不需要建立待排序列的副本,即不需要额外的存储空间,因此,在空间复杂度方面,插入排序占优势。
稳定性分析:
根据合并的具体操作,当两个数相等时,它们的相对位置不会改变,因此归并排序具有稳定性。
升级版:(结合插入排序)
由于当
n
较小时,
具体C++代码如下:
void insertSort1(vector<int> &vec,int low,int high)
{
int subVecSize = high - low + 1;
int j;
for (int i = low + 1; i <= high; i++) // i 表示已经排好序的子数组大小减一
{
int insertValue = vec[i]; // 将这个值插入已经排好序的数组
for (j = i - 1; j >= low; j--)
{
if (insertValue >= vec[j])
break; // 找到了要插入 insertValue 的位置 j
vec[j + 1] = vec[j];
}
vec[j + 1] = insertValue;
}
}
//当子序列的长度小于或等于 n0 时用插入排序
void mergeSort1(vector<int> & vec, int low, int high, int n0)
{
if (low < high - n0 + 1)
{
int mid = (high + low) / 2;
mergeSort1(vec, low, mid,n0);
mergeSort1(vec, mid + 1, high,n0);
myMerge(vec, low, mid, high);
}
else
{
insertSort1(vec, low, high);
}
}
插入排序,归并排序,升级版归并排序 分别用运行时间比较:
输入序列为 10000 点的随机序列, 当子序列长度小于等于
n0=64
时, 用插入排序。
由此可见,归并排序的时间远远小于插入排序,主要原因由于插入排序需要大量的元素搬移操作,而且升级版的归并排序略好于普通版的归并排序。