归并排序
基本思路:
假设初始序列有n个记录,则可看成是n个有序的子序列,每个序列长度为1,然后两两归并,得到个长度为2或者是1的有序子序列;再两两归并,,,,如此重复,直到得到一个长度为n的有序序列为止,这种排序方法称为2-路归并排序。
详细动态图解(详细图解)-------号外:归并排序发明者竟然是约翰-冯诺伊曼
归并操作的工作原理如下:
第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置
第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
重复步骤3直到某一指针超出序列尾
将另一序列剩下的所有元素直接复制到合并序列尾
//前子序列为从start到index,后子序列从index+1到end,现在需要将2个有序子序列进行归并成一个有序序列
void Merge(SqList *L,int start,int index,int end)
{
int n=end-start+1;//共有n=end-start+1个记录
int *S=(int*)malloc(n*sizeof(int));//生产n个记录大小辅助空间
if(!S) exit(-1);//堆分配失败
int i=start,j=index+1,k=0;//初始化指针,k=0为辅助数组指针,i指向前子序列,j指向后子序列
for(;i<=index&&j<=end;k++){
if(L->r[i]<=L->r[j]) S[k]=L->r[i++];//选择相对小的元素放入到辅助空间,指针后移一个位置
else S[k]=L->r[j++];
}//直到某一指针超出子序列尾部
if(i<=index)//将另一序列剩下的所有元素复制到辅助数组
while(k<n&&i<=index) S[k++]=L->r[i++];
if(j<=end)
while(k<n&&j<=end) S[k++]=L->r[j++];
for(i=start,k=0;i<=end&&k<n;i++,k++)
L->r[i]=S[k];//将排序完成的辅助数组再赋值回SqList->r使之有序
return;
}
void MergeSort(SqList *L)
{
MSort(L,0,L->length-1);//序列从0到L->length-1,共L->length个记录关键字
return;
}
void MSort(SqList *L,int i,int j)
{
if(i<j)//如果i<j,则继续拆分,若i=j,则已经为单个元素序列,已经有序,不继续拆分
{
int m=(i+j)/2;//将头为i,尾为j的序列进行拆分
MSort(L,i,m);//对前子序列进行递归
MSort(L,m+1,j);//对后子序列进行递归
Merge(L,i,m,j);//对已经有序的前序列和后序列进行归并,成为新的有序序列
}
return;
}
时间复杂度:
一趟归并排序的操作是,调用次Merge将前后相邻且长度为h的有序段进行两两归并,得到前后相邻,长度为2h的有序段
整个归并排序需要趟,故时间复杂度为O(nlogn)
空间复杂度:
实现归并排序需要和待排记录等数量的辅助空间,所以时间复杂度为O(n)
归并排序为一种稳定排序,对于相等关键字不改变其记录相对位置