总体思想
假设初始序列含有n个元素,则可以看成n个有序的子序列,每个子序列只有一个元素,然后两两归并,得到(n/2+0.5)个长度为2或者1的有序子序列,再两两归并……如此反复执行,直到得到一个长度为n的有序序列为止,这种排序方法称之为2路归并排序。
技巧
- 先要将原始序列每次按照“二分法”分成两个子序列,4个子序列,8个子序列,直到n个子序列
再按照两两归并的步骤往上归并,子序列又从n个变为(n/2+0.5)个,在原来的数量上缩减为原来的一半,但每一个子序列都变成了有序的,且有序子序列的长度逐步翻倍,最终只有一个子序列,就是排好序的序列。
有点像“先向下开花,再向上收拢”的感觉
#include <iostream>
using namespace std;
#define MAXSIZE 100
typedef int Status;
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
template <typename T>
class SqList
{
public:
T r[MAXSIZE+1];
int length;
public:
SqList(T * var,int len);
SqList();
void printList();
Status swapElement(int i, int j);
void MergeSort();
void MSort(T src[],T dst[],int s,int t);
void Merge(T src[],T dst[],int i,int m,int n);
void MergeSort_2();
};
template <typename T>
SqList<T>::SqList(T * var,int len)
{
length = len/sizeof(T);
memcpy(&(r[1]), var, len);
}
template <typename T>
SqList<T>::SqList()
{
memset(this, 0, sizeof(SqList));
}
template <typename T>
Status SqList<T>::swapElement(int i,int j)
{
T tmp =this->r[i];
this->r[i] = this->r[j];
this->r[j] = tmp;
return OK;
}
template <typename T>
void SqList<T>::printList()
{
for (int i = 1; i <= length; i++)
{
cout << this->r[i] << "\t";
}
cout << endl;
}
/************************************************************************/
/*归并排序 */
/************************************************************************/
/*为了统一接口--内部调用实际的归并排序函数*/
template <typename T>
void SqList<T>::MergeSort()
{
MSort(r, r, 1, length);//调用实际的归并排序
}
/*归并排序的递归算法
将src[s..t]归并排序为dst[s..t]*/
template <typename T>
void SqList<T>::MSort(T src[], T dst[], int s, int t)
{
int m = 0;
T tmp[MAXSIZE+1];//开头是一个哨兵--辅助变量存放各阶段归并的结果
memset(tmp, 0, sizeof(tmp));
if (s == t)//递归结束的条件--意味着两两归并的子序列中都只有一个元素
{
dst[s] = src[s];//由于只有一个元素--直接存放元素到目标表
//表示两个有序子序列,只不过每个子序列只有一个元素
//执行完这条语句表示递归结束,该返回了
//紧接着执行Merge函数完成对这两个子序列的归并操作
}
else{
m = (s + t) / 2;//将src[s..t]评分为src[s..m]和src[m+1..t]
MSort(src, tmp, s, m);//递归将src[s..m]归并为tmp[s..m]
MSort(src, tmp, m + 1, t);//递归将src[m+1..t]归并为tmp[m+1..t]
Merge(tmp, dst, s, m, t);//将tmp[s..m]和tmp[m+1..t]归并到dst[s..t]
//每次递归返回的时候都会执行这条语句,
//里面完成了两个局部有序子序列的归并操作
//这条语句执行完以后就会得到局部的有序序列以供外层递归返回时
}
}
/*将有序的src[i..m]和src[m+1..n]归并为dst[i..n]
默认序号小的子序列比序号大的子序列要小--升序排列
*/
template <typename T>
void SqList<T>::Merge(T src[], T dst[], int i, int m, int n)
{
int j, k, l;//j是目标序列的下标,从i到n
//k是第二个有序子序列的下标,从m+1开始到n
//第一个有序子序列的下标就用i来表示,从i到m
//l是用来处理有序子序列剩下的元素的辅助变量
//因为最后可能子序列的个别元素会在循环结束后还没有存放到目标表/序列中
for (j = i, k = m + 1; i <= m && k <= n;j++)
{
if (src[i] < src[k])
dst[j] = src[i++];
else
dst[j] = src[k++];
}
if (i <= m)//将剩余的src[i..m]复制到dst中
{
for (l = 0; l <= m - i;l++)//这里循环m-i+1次,因为剩下的元素下标从i开始,
//直到m结束,一共就是m-i+1个
{
dst[j + l] = src[i + l];
}
}
if (k <= n)//将剩余的src[k..n]复制到dst中
{
for (l = 0; l <= n - k; l++)//这里循环n-k+1次,因为剩下的元素下标从k开始,
//直到n结束,一共就是m-i+1个
{
dst[j + l] = src[k + l];
}
}
}
template <typename T>
void SqList<T>::MergeSort_2()
{
}
int main(void)
{
int myList[9] = {90,10,50,80,30,70,40,60,20};
SqList<int> list(myList,sizeof(myList));
cout << "before sort:"<< endl;
list.printList();
list.MergeSort();
cout << "after sort:" << endl;
list.printList();
cout<<"Hello!"<<endl;
system("pause");
return 0;
}
复杂度
堆排序和现在介绍的归并排序的时间复杂度都是O( nlogn ),而对于递归思想的归并排序空间复杂度是O( n+logn ),比较耗费内存,但不管如何他稳定(没有跳跃式的比较和交换),高效,时间复杂度不到O( n2 )。
非递归的归并排序
#include <iostream>
using namespace std;
#define MAXSIZE 100
typedef int Status;
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
template <typename T>
class SqList
{
public:
T r[MAXSIZE+1];
int length;
public:
SqList(T * var,int len);
SqList();
void printList();
Status swapElement(int i, int j);
void Merge(T src[],T dst[],int i,int m,int n);
void MergeSort_2();
void MergePass(T src[], T dst[], int s, int n);
};
template <typename T>
SqList<T>::SqList(T * var,int len)
{
length = len/sizeof(T);
memcpy(&(r[1]), var, len);
}
template <typename T>
SqList<T>::SqList()
{
memset(this, 0, sizeof(SqList));
}
template <typename T>
Status SqList<T>::swapElement(int i,int j)
{
T tmp =this->r[i];
this->r[i] = this->r[j];
this->r[j] = tmp;
return OK;
}
template <typename T>
void SqList<T>::printList()
{
for (int i = 1; i <= length; i++)
{
cout << this->r[i] << "\t";
}
cout << endl;
}
/************************************************************************/
/*非递归的归并排序 */
/************************************************************************/
template <typename T>
void SqList<T>::MergeSort_2()
{
T tmp[MAXSIZE];//辅助变量存放临时归并的结果
memset(tmp, 0, sizeof(tmp));//初始化
int k = 1;//有序子序列的长度--从1开始,逐渐翻倍
/*每一次循环需要进行两次归并*/
while (k < length)//只要有序子序列长度没有达到原始序列的长度表示排序未结束
{
MergePass(r, tmp, k, length);//第一步从原始序列归并到辅助序列
//每一次归并结束以后,子序列长度都会翻倍
k *= 2;//子序列长度翻倍
MergePass(tmp, r, k, length);//第二部又从辅助序列归并到原始序列
//保证没一次循环结束以后归并结果存放到原始序列
k *= 2;//子序列长度翻倍
}
}
/*将src中相邻长度为s的子序列两两归并到dst*/
template <typename T>
void SqList<T>::MergePass(T src[], T dst[], int s, int n)
{
int i = 1;//要归并的子序列中元素的下标
int j = 0;//用来处理剩余单个子序列的辅助变量
/*归并的范围是1到n
但是i的范围是1到(n-2*s+1)
因为(n-2*s+1)表示的是从最后一个元素往前数,
最后两个子序列的起始下标,比如原始序列是1-9,每个子序列的长度s为2,
则9-2*2+1 = 6表示最后两个子序列的起始下标,6-7和8-9表示最后两个子序列
如果在循环过程中i没有超过这个下标,表示后面还有至少两个子序列
还可以进行一轮循环,否则表示后面不足两个子序列,结束循环
比如只要i不大于6表示后面至少还有4个元素,还可以组成至少两个子序列,
在循环里可以对两个相邻的子序列进行处理*/
while (i <= n-2*s+1)
{
Merge(src, dst, i, i + s - 1, i + 2 * s - 1);//两两子序列归并
//第一个子序列从i到i+s-1
//第二个子序列从i+s到i+2*s-1
i += 2 * s;//根据增量更新下标,这里的增量是两个子序列的总长度
}
if (i < n - s + 1)//如果i小于最后一个子序列的起始下标,表示剩余两个子序列,
//如i小于8,即使i=7,后面还有7-9三个元素,而每个子序列只有俩元素
//所以还可以将三个元素组成两个子序列,只不过有可能有一个子序列的长度为s-1
Merge(src, dst, i, i + s - 1, n);//将最后的两个子序列进行归并
else{//最后只剩下单个子序列,有可能剩下的这个子系列刚好有s个元素,也可能不足s个元素,
//但最多就s个元素,然后就逐个将这些元素复制到目标序列完成一次总的归并
for (j = i; j <= n;j++)//从i到n逐个复制,此时n-i+1<= s,(n-i+1)表示剩下的元素个数
{
dst[j] = src[j];
}
}
}
int main(void)
{
int myList[9] = {90,10,50,80,30,70,40,60,20};
SqList<int> list(myList,sizeof(myList));
cout << "before sort:"<< endl;
list.printList();
list.MergeSort_2();
cout << "after sort:" << endl;
list.printList();
cout<<"Hello!"<<endl;
system("pause");
return 0;
}
非递归的归并算法,用了迭代的方法完成归并排序,避免了递归时候深度为 logn 的栈空间,辅助空间只用到了辅助空间tmp,所以空间复杂度只是O( n <script type="math/tex" id="MathJax-Element-5">n</script>),而且没用递归,在时间性能上也优化了很多,尽量使用非递归的归并排序。