思想:不断二分长序列,直到子序列的长度为1(1个数本身是有序序列),将2个子序列合并,得到一个有序的父序列,继续合并,最终得到一个有序的原序列。
实现代码:
void mergeSort1(int num[], int start, int end)//自顶向下的归并排序
{
//如果序列长度小于5则用插入法
if((end-start)<5)
{
insertSort(num, start, end);
return;
}
int med=(end+start+1)/2;
mergeSort1(num, start, med-1);//整理左边
mergeSort1(num, med, end);//整理右边
merge(num, start,med, end);//左右合并
}
注意点: 当序列较小的时候可以采用其他的排序方法,比如这里采用插入排序,对于小序列来说归并排序并没有优势,反而需要很多的递归调用开销。
其中插入排序实现代码:
int insertSort(int num[], int start, int end)//插入排序
{
if(end<start) return -1;
int swapCount=0;//记录交换次数
for(int i=start+1;i<=end;i++)
for(int j=i;j>start&&(num[j]<num[j-1]);j--)
{
swap(num[j],num[j-1]);
swapCount++;
}
return swapCount;
}
void merge(int num[], int start, int med, int end)
{
//如果第二个有序序列的第一个数比第一个有序序列的最后一个数还要大,则不需要整理
if(num[med]>=num[med-1]) return;
//复制左半边
int leftLen=med-start;
int *numLeft=new int[leftLen];
for(int i=0; i<leftLen; i++)
{
numLeft[i]=num[start+i];
}
int leftIndex=0,rightIndex=med,numIndex=start;
while(leftIndex<leftLen && rightIndex<=end)
{//取两个序列中较小的那个放到num中
if(numLeft[leftIndex]<num[rightIndex]) num[numIndex++]=numLeft[leftIndex++];
else num[numIndex++]=num[rightIndex++];
}
while(leftIndex<leftLen)
{//如果左边序列还没取完,则添加到num后面
num[numIndex++]=numLeft[leftIndex++];
}
while(rightIndex<=end)
{//如果右边序列没取完
num[numIndex++]=num[rightIndex++];
}
delete[] numLeft;
}
注意点:
1.如果右边有序序列的第一个数比左边有序序列的最后一个数还要大,则不需要整理。
这一点对于原本就已经排好序的序列可以节省很多时间。
2.合并需要额外开辟空间来存放副本,这个空间大小为左边序列的大小。右边序列可以不需要副本,直接用原序列就可以。
上面是自顶向下的归并排序,另外还可以自底向上进行归并。
思想:两两合并,四四合并...直到合并后长度超过序列长度,刚好与自顶向下相反。
实现代码:
void mergeSort2(int num[], int start, int end)//自下向顶的归并排序
{
int tmpEnd=0;
int len=end-start+1;
for(int gap=2;gap<len;gap*=2)
for(int i=0;(tmpEnd=i+gap-1)<len;i+=gap)
{
merge(num,i,(i+tmpEnd+1)/2,tmpEnd);
}
}
**************************************************************分隔符*********************************************************************
今天看书的时候看到说如果在内循环里进行内存申请和释放会导致大量的时间消耗,所以最好是在外面先申请好一段固定大小(N*sizeof(type))的内存。
先前说到合并时需要先将数据复制到副本,《算法》书里提到一种优化的方法来优化这个额外的损耗:
在递归时不停交换数组和副本的位置,先将数组排序到副本,再将副本排序到数组,如此反复。
要注意的是最后一次合并有可能是从数组排序到副本,所以还需要将数组从副本拷贝回数组。
实现代码如下:
void mergeSort3(int num[], int len)//自顶向下的归并排序
{
int *numTmp=new int[len];//在外面申请好内存
bool flag=mergeSortInside(num, numTmp, 0, len-1);
if(!flag) merge2(numTmp, num, 0, (len+1)/2, len-1);//如果最后一次是从数组合并到副本,还需要复制回数组
delete[] numTmp;
}
bool mergeSortInside(int num[], int numTmp[], int start, int end)
{
static bool flag=true;
//如果序列长度小于5则用插入法
if((end-start)<5)
{
insertSort(num, start, end);
return true;
}
int med=(end+start+1)/2;
mergeSort1(num, start, med-1);//整理左边
mergeSort1(num, med, end);//整理右边
if(flag)
{
merge2(num,numTmp, start,med, end);//从数组合并到副本
flag=false;
}else
{
merge2(numTmp,num, start,med, end);//从副本到数组
flag=true;
}
return flag;
}
void merge2(int in[],int out[], int start, int med, int end)//合并数组的左右两边,med为右边有序序列的开始
{
//如果第二个有序序列的第一个数比第一个有序序列的最后一个数还要大
if(in[med]>=out[med-1])
{//注意这里与之前有所不同,不能直接跳过,但只直接将数据复制到输出
for(int i=start;i<=end;i++)
out[i]=in[i];
return;
}
int leftIndex=start,rightIndex=med, outIndex=start;
while(leftIndex<med && rightIndex<=end)
{//取两个序列中较小的那个放到out中
if(in[leftIndex]<in[rightIndex]) out[outIndex++]=in[leftIndex++];
else out[outIndex++]=in[rightIndex++];
}
while(leftIndex<med)
{//如果左边序列还没取完,则添加到out后面
out[outIndex++]=in[leftIndex++];
}
while(rightIndex<=end)
{//如果右边序列没取完
out[outIndex++]=in[rightIndex++];
}
}
没测试过这样做性能提高多少,试了网上几种时间计算方法都测不到时间,但是从理论上来说对于大数据应该还是有较大的提升。