Java数据结构与算法(三)归并排序法

一、归并排序法分析

O(n * log n)的排序算法。
那么nlogn比n^2快多少:
这里写图片描述
画图分析归并排序法:
这里写图片描述
将数组分割的时候,分割了三次,那么如果是n个元素,分割需要log(N)次。每层需要处理N个数,所有此算法为O(n * log n)的排序算法。
现在要解决的问题是,如何将一个左边以及右边都排好序的数组,合并为一个完整的排好序的数组:
首先,将数组复制一份,并定义三个索引:
这里写图片描述
1比2小,所以1放到最终的数组中,同时移动索引:
这里写图片描述
2比4小,所以2放入到最终的数组中,同时移动索引:
这里写图片描述
3比4小,所以3放入到最终的数组中,同时移动索引:
这里写图片描述
4比6小,所以4放入到最终的数组中,同时移动索引:
这里写图片描述
后面,这样依次类推。
我们将这三个索引分别定义为i、j、k、l、r、m:
这里写图片描述

二、代码实现归并排序法

package paixu;

import java.util.Arrays;

/*
    k表示最终i和j比较之后最终需要放的位置
    i和j用来表示当前需要考虑的元素
    left表示最左边的元素
    right表示最右边的元素
    middle表示中间位置元素,放在第一个已经排好序的数组的最后一个位置
*/
public class GuiBingPaiXu {
    /*******************测试************************/
    public static void main(String[] args) {
          int[] nums = { 2, 7, 8, 3, 1, 6, 9, 0, 5, 4 };

          mergeSort(nums , 0 , nums.length - 1 );
          System.out.println(Arrays.toString(nums));
    }
    /********************算法************************/
    /*
        arr:要处理的数组
        l:开始位置
        r:结束位置
        递归对arr[ l ... r ]范围的元素进行排序
     */
    private static void mergeSort(int[] arr,int left,int right){
        if( left >= right )    //表示已经排序完毕了
            return;
        int middle = ( left + right ) / 2;  //计算中点位置
        mergeSort( arr , left , middle );   //不断地对数组的左半边进行对边分
        mergeSort( arr , middle+1 , right );   //不断地对数组的右半边进行对半分
        merge( arr , left , middle , right );     //最后将已经分好的数组进行归并
    }
    //将arr[ l... mid ]和arr[ mid ... r ]两部分进行归并
    /*
        |2, 7, 8, 3, 1  |  6, 9, 0, 5, 4|
     */
    private static void merge(int[] arr, int left, int mid, int right) {
        int arr1[] = new int[ right - left + 1 ];   //定义临时数组
        for( int i = left ; i <= right ; i++ )      //将数组的元素全部复制到新建的临时数组中
            arr1[ i - left ] = arr[ i ];
        int i = left;
        int j = mid + 1;     //定义两个索引
        for( int k = left;k <= right ; k++){
             if( i > mid )   //如果左边都比较完了
             {
                 arr[ k ] = arr1[ j - left ];   //直接将右边的元素都放进去
                 j++;
             }
             else if( j > right ){   //右边都比较完了
                 arr[ k ] = arr1 [i - left ];   //直接将左边的元素放进去
                 i++;
             }
             else if( arr1[ i-left ] < arr1[ j-left ] ){
                 arr[ k ] = arr1[ i - left];
                 i++;
             }
            else
             {
                 arr[ k ] = arr1[ j - left];
                 j++;
             }
        }
    }
}

对近乎有序的数组排序,归并排序的速度比插入排序法慢一些。
那么如何优化呢?我们发现:当左边最大的元素都比右边最小的元素还小的时候就不用归并了。于是代码修改成如下:

public class GuiBingPaiXu {
    /*******************测试************************/
    public static void main(String[] args) {
          int[] nums = { 2, 7, 8, 3, 1, 6, 9, 0, 5, 4 };

          mergeSort(nums , 0 , nums.length - 1 );
          System.out.println(Arrays.toString(nums));
    }
    /********************算法************************/
    /*
        arr:要处理的数组
        l:开始位置
        r:结束位置
        递归对arr[ l ... r ]范围的元素进行排序
     */
    private static void mergeSort(int[] arr,int left,int right){
        if( left >= right )    //表示已经排序完毕了
            return;
        int middle = ( left + right ) / 2;  //计算中点位置
        mergeSort( arr , left , middle );   //不断地对数组的左半边进行对边分
        mergeSort( arr , middle+1 , right );   //不断地对数组的右半边进行对半分
        if( arr[middle] > arr[middle+1] )      //当左边最大的元素都比右边最小的元素还小的时候就不用归并了
            merge( arr , left , middle , right );     //最后将已经分好的数组进行归并
    }
    //将arr[ l... mid ]和arr[ mid ... r ]两部分进行归并
    /*
        |2, 7, 8, 3, 1  |  6, 9, 0, 5, 4|
     */
    private static void merge(int[] arr, int left, int mid, int right) {
        int arr1[] = new int[ right - left + 1 ];   //定义临时数组
        for( int i = left ; i <= right ; i++ )      //将数组的元素全部复制到新建的临时数组中
            arr1[ i - left ] = arr[ i ];
        int i = left;
        int j = mid + 1;     //定义两个索引
        for( int k = left;k <= right ; k++){
             if( i > mid )   //如果左边都比较完了
             {
                 arr[ k ] = arr1[ j - left ];   //直接将右边的元素都放进去
                 j++;
             }
             else if( j > right ){   //右边都比较完了
                 arr[ k ] = arr1 [i - left ];   //直接将左边的元素放进去
                 i++;
             }
             else if( arr1[ i-left ] < arr1[ j-left ] ){
                 arr[ k ] = arr1[ i - left];
                 i++;
             }
            else
             {
                 arr[ k ] = arr1[ j - left];
                 j++;
             }
        }
    }
}

当数据足够少的时候,有序的可能性比较大,采用插入排序法,效果会更好。
为此,对代码进行如下的修改:

//if( left >= right )    //表示已经排序完毕了
//    return;
if( right - left <= 10 ){
     ChaRuPaiXu.ChaRuPaiXuFa2( arr , left ,right);
     return;
 }

转载请标明出处,原文地址:https://blog.csdn.net/weixin_41835916
总结整理不容易,如果觉得本文对您有帮助,请点击支持一下,您的支持是我写作最大的动力,谢谢。
这里写图片描述

猜你喜欢

转载自blog.csdn.net/weixin_41835916/article/details/80937207
今日推荐