归并排序(递归实现+非递归实现+自然合并排序)

http://www.cnblogs.com/liushang0419/archive/2011/09/19/2181476.html

归并排序的确是分治思想的经典代表。写了很多次,这次又有新的收获,过去用的是递归的实现方式,理论上任何用递归方法实现的代码都可以转换为非递归的形式,所以此例也不例外。然后再用非递归的实现方法上进行改进,完成了“自然归并”算法,这比直接归并效率要高一些。

先给出基础的用递归方法实现的归并排序:

View Code
复制代码
#include<iostream>
using namespace std;
const int SIZE = 100;
int arr[SIZE];
//排序数组arr[fir:end]
void mergeSort(int fir,int end){
    //当子序列就只有一个元素的时候就弹出
    if(fir==end)return;

    //分治
    int mid = (fir+end)/2;
    mergeSort(fir,mid);
    mergeSort(mid+1,end);
    
    //合并
    int tempArr[SIZE];
    int fir1=fir,fir2=mid+1;
    for(int i=fir;i<=end;i++){
        if(fir1>mid)
            tempArr[i]=arr[fir2++];
        else if(fir2>end)
            tempArr[i]=arr[fir1++];
        else if(arr[fir1]>arr[fir2])
            tempArr[i]=arr[fir2++];
        else 
            tempArr[i]=arr[fir1++];
    }
    for(int i=fir;i<=end;i++)
        arr[i]=tempArr[i];

}
int main(){
    //测试
    int n;cin>>n;
    for(int i=0;i<n;i++)cin>>arr[i];
    mergeSort(0,n-1);
    for(int i=0;i<n;i++)cout<<arr[i]<<" ";
    cout<<endl;
    return 0;
}
复制代码

其中归并函数中的合并方法没有单独写开,单独函数merge()及其解释如下:

复制代码
void merge(int fir,int end,int mid){
    //合并
    int tempArr[SIZE];
    int fir1=fir,fir2=mid+1;
    for(int i=fir;i<=end;i++){
        if(fir1>mid)//前半段扫描完毕
            tempArr[i]=arr[fir2++];
        else if(fir2>end)//后半段扫描完毕
            tempArr[i]=arr[fir1++];
        //两端如果都没有扫描完毕的话
        //就选择较小的值插在临时数组的后端
        else if(arr[fir1]>arr[fir2])
            tempArr[i]=arr[fir2++];
        else 
            tempArr[i]=arr[fir1++];
    }
    //将排好的临时数组拷贝到原数组中,返回
    for(int i=fir;i<=end;i++)
        arr[i]=tempArr[i];
}
复制代码

有了merge函数后mergesort1()如下:

复制代码
void mergeSort(int fir,int end){
    //当子序列就只有一个元素的时候就弹出
    if(fir==end)return;

    //分治,现分为两个子段,
    int mid = (fir+end)/2;
    mergeSort(fir,mid);//对左半段递归排序
    mergeSort(mid+1,end);//对右半段递归排序
    
    //合并
    merge();

}
复制代码

 

归并排序的非递归实现如下,思想和递归正好相反,原来的递归过程是将待排序集合一分为二,直至排序集合就剩下一个元素位置,然后不断的合并两个排好序的数组。所以非递归思想为,将数组中的相邻元素两两配对。用merge函数将他们排序,构成n/2组长度为2的排序好的子数组段,然后再将他们排序成长度为4的子数组段,如此继续下去,直至整个数组排好序。

代码如下:(和书上不同,自认为更好理解一些)

复制代码
void mergeSort2(int n){
    int s=2,i;
    while(s<=n){
        i=0;
        while(i+s<=n){
            merge(i,i+s-1,i+s/2-1);
            i+=s;
        }
        //处理末尾残余部分
        merge(i,n-1,i+s/2-1);
        s*=2;
    }
    //最后再从头到尾处理一遍
    merge(0,n-1,s/2-1);
}
复制代码

 

自然合并排序

该排序需要一个叫做pass()的子函数,该函数通过一次扫描,将排序前数组中已经有序的子数组段信息记录在rec[]数组中,然后返回原数组中自然序列的个数。

该算法的实现示意图见课本P23笔记。

复制代码
// 自然归并是归并排序的一个变形,效率更高一些,可以在归并排序非递归实现的基础上进行修改
//对于已经一个已经给定数组a,通常存在多个长度大于1的已经自然排好的子数组段
//因此用一次对数组a的线性扫描就可以找出所有这些排好序的子数组段
//然后再对这些子数组段俩俩合并
//代码的实现如下:
#include<iostream>
using namespace std;
const int SIZE = 100;
int arr[SIZE];
int rec[SIZE];//记录每个子串的起始坐标
//排序数组arr[fir:end]
//合并操作的子函数
void merge(int fir,int end,int mid);
//扫描得到子串的子函数
int pass(int n);
//自然合并函数
void mergeSort3(int n);
/********************************************************************/

void mergeSort3(int n){
    int num=pass(n);
    while(num!=2){
        //num=2说明已经排好序了
        //每循环一次,进行一次pass()操作
        for(int i=0;i<num;i+=2)
            //坐标解释可参加P23页的图示
            merge(rec[i],rec[i+2]-1,rec[i+1]-1);
        num=pass(n);
    }
}
void merge(int fir,int end,int mid){
    //合并
    int tempArr[SIZE];
    int fir1=fir,fir2=mid+1;
    for(int i=fir;i<=end;i++){
        if(fir1>mid)
            tempArr[i]=arr[fir2++];
        else if(fir2>end)
            tempArr[i]=arr[fir1++];
        else if(arr[fir1]>arr[fir2])
            tempArr[i]=arr[fir2++];
        else 
            tempArr[i]=arr[fir1++];
    }
    for(int i=fir;i<=end;i++)
        arr[i]=tempArr[i];
}
int  pass(int n){
    int num=0;
    int biger=arr[0];
    rec[num++]=0;
    for(int i=1;i<n;i++){
        if(arr[i]>=biger)biger=arr[i];
        else {
            rec[num++]=i;
            biger=arr[i];
        }
    }
    //给rec[]加一个尾巴,方便排序
    rec[num++]=n;
    return num;
}
int main(){
    int n;
    while(cin>>n){
        for(int i=0;i<n;i++)cin>>arr[i];
        //测试mergeSort函数
        /**/mergeSort3(n);
        for(int i=0;i<n;i++)cout<<arr[i]<<" ";
        cout<<endl;

        //测试pass函数
        /*int num = pass(n);
        for(int i=0;i<num;i++)cout<<rec[i]<<" ";
        cout<<endl;*/
    }
    return 0;
}
复制代码
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值