归并排序

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。归并排序是一种稳定的排序方法。

归并排序的工作原理如下描述:

  1. 创建一个和待排序数组登场的数组(用于临时存放数据)
  2. 将待排序数组对半划分,直至每个划分后的数组只有两个元素
  3. 将划分后的数组按大小排序
  4. 将划分后的数组合并

是不是很简单ヽ(゚∀゚)メ(゚∀゚)ノ (并不

我们来一步一步的讨论如何实现这个算法
假设我们有个数组arr,根据第一步,创建一个和它等长的数组temp

int[] temp=new int[arr.length];

然后就是对原数组进行划分了。我们不需要真的对原数组进行分割,只是在逻辑上进行划分。我们用一个指针left来标识数组的左侧,right来标识数组的右侧,这样我们就可以得到这个数组的中间数mid=(left+right)/2,然后以mid为划分,把数组划分为左右两个子数组。图中黄色方块标识的就是mid所指向的位置

这时左侧数组可以以left为左指针,mid为右指针,而右侧数组可以以mid+1为左指针,right为右指针,然后一直这样划分下去,直到划分出来数组中只有两个成员,如图所示。同时,可以从图中看出,到最后子数组只有两个元素的时候,再分出子数组,就会使子数组的left等于right。所以我们可以用leftright的关系作为判断数组划分是否结束的标准

这样就可以写出一个递归的方法:

public void Divide(int[] arr,int left,int right,int[] temp){
    if(left<right){
        int mid=(left+right)/2;
        Divide(arr,left,mid);
        Divide(arr,mid+1,right);
        //用于对子数组进行合并
        Conquer(arr,left,mid,right,temp);
    }
}

分完之后,就该对子数组做合并操作了,也就是上面的代码出现的Conquer()方法。合并的操作就要用到之前定义的temp数组了。以上图第三行的49、39两个数所在的子数组举例。用一个指针i代替left,在这个例子里i=0。指针j设置为mid+1,在这个例子里j=1。指针t设置为0(作为遍历temp数组的指针)。要求i<=midj<=right

int i=left;
int j=mid+1;
int t=0

然后遍历利用ij遍历该子数组,并比较ij所指向的数的的大小,把比较小的那个填入temp。在这个例子中,将38填入temp中后循环结束。

while(i<=mid && j<=right){
    if(arr[i]<arr[j])
        temp[t++]=arr[i++];
    else if(arr[j]<arr[i])
        temp[t++]=arr[j++];
}

循环结束时有可能i还没有遍历到mid,或者j还没有遍历到right,这时再剩下的数填入到temp中。在这个例子中,是前一种情况,把从imid(即0到0)的所有数填入temp(即49)

while(i<=mid)
    temp[t++]=arr[i++];
while(j<=right)
    temp[t++]=arr[j++];

此时temp=[38,49,0,0,0,0,0,0]

然后把t置0,把这一段子数组的值覆盖到原始数据中:

t=0;
while(left<=right>)
    arr[left++]=temp[t++];

覆盖后的原始数据arr=[38,49,65,97,23,22,76,1]

完整的代码:

public void Conquer(int[] arr,int left,int mid,int right,int[] temp){
    int i=left;
    int j=mid+1;
    int t=0;
    while(i<=mid && j<=right){
        if(arr[i]<arr[j])
            temp[t++]=arr[i++];
        else if(arr[j]<arr[i])
            temp[t++]=arr[j++];
    }
    while(i<=mid)
        temp[t++]=arr[i++];
    while(j<=right)
        temp[t++]=arr[j++];
    t=0;
    while(left<=right)
        arr[left++]=temp[t++];
}

有了上面的例子,我们可以再看看数组划分的第二行[23,22,76,1]这个子数组该如何进行数组的合并。因为这个子数组的两个子数组已经分别做过了合并,所以实际上此时的子数组内容为[22,23,1,76]。开始遍历这个子数组,一开始i指向22,j指向1。1小于22,故向temp中插入1,right向右移动1位,指向76;22小于76,故向temp中插入22,left向右移动1位,指向23;23小于76,故向temp中插入23,left向右移动1位,结束循环。然后把生下的76插入temp中。此时temp=[1,22,23,76,0,0,0,0],因为left=4right=7,所以把temp中(7-4+1)个内容覆盖到原始数据中从4到7的位置上

整个归并排序的完整代码如下:

public class MergeSort{
    public void sort(int[] arr){
        int[] temp=new int[arr.length];
        Divide(arr,0,arr.length-1,temp)
    }

    public void Divide(int[] arr,int left,int right,int[] temp){
        if(left<right){
            int mid=(left+right)/2;
            Divide(arr,left,mid,temp);
            Divide(arr,mid+1,right,temp);
            Conquer(arr,left,mid,right,temp);
        }
    }

    public void Conquer(int[] arr,int left,int mid,int right,int[] temp){
        int i=left;
        int j=mid+1;
        int t=0;
        while(i<=mid && j<=right){
            if(arr[i]<arr[j])
                temp[t++]=arr[i++];
            else if(arr[j]<arr[i]>)
                temp[t++]=arr[j++];
        }
        while(i<=mid)
            temp[t++]=arr[i++];
        while(j<=right)
            temp[t++]=arr[j++];
        t=0;
        while(left<=right)
            arr[left++]=temp[t++];
    }

    public static void main(String[] args){
        //测试数据
        int[] arr={49,38,65,97,23,22,76,1};
        MergeSort ms=new MergeSort();
        ms.sort(arr);
        for(int i:arr)
            System.out.print(i+" ");
    }
}

输出结果:

1 22 23 38 49 65 76 97

posted @ 2020-04-22 21:12  maurrinho  阅读(112)  评论(0编辑  收藏  举报