排序算法——归并排序

欢迎大家参观我的新网站,悦来

前面我们了解了鸡尾酒排序和桶排序,今天来学习归并排序,首先了解一下什么是归并排序:归并排序(合并排序)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(分而治之)的一个非常典型的应用将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,使再子序列段间有序看不明白,没关系,用图来直观展示:

首先准备一列待排序的数,将这一列待排序的数就行分割,分为左右两部分,我们这样考虑: 将左右两边的数进行排序,我们比较左右两边最小的数,就可以确定整列数中的最小数,一次这样进行比较次小数,就可以得到从小到大排列的全部数,这是典型的分治思想.将一个打的问题拆分为小的问题,我们这里要解决的是全部数的排序,分解为了左边排序,右边排序,再比较的分问题. 经过递归操作,就得到了上图中的步骤.
我们再来看一下动图:
是否明白了归并排序的原理了呢,就是分而治之,再归并统一.

下面我们看一下Java实现的归并排序代码:

     private static void sort(int[] array) {
        int[] resultArr = new int[array.length];
        merge(0, array.length-1, resultArr, array);
        System.out.println(Arrays.toString(resultArr));
    }
 
private static void merge(int left, int right, int[] resultArr, int[] array) {
        if (left >= right) {
            return;
        }
        // 中间分割点
        int middle = (left + right) / 2;
        // 左边起点与终点
        int leftStart = left;
        int leftEnd = middle;
 
        // 右边起点与终点
        int rightStart = middle + 1;
        int rightEnd = right;
        System.out.println(leftStart+"---"+leftEnd);
        // 左边再次分割
        merge(leftStart, leftEnd, resultArr, array);
        // 右边再次分割
        merge(rightStart, rightEnd, resultArr, array);
         
        // 进行比较归并
        int i = left;
        while (leftStart <= leftEnd && rightStart <= rightEnd) {
            resultArr[i++] = array[leftStart] < array[rightStart] ? array[leftStart++] : array[rightStart++];
        }
        while (leftStart <= leftEnd) {
            resultArr[i++] = array[leftStart++];
        }
        while (rightStart <= rightEnd) {
            resultArr[i++] = array[rightStart++];
        }
        for (i = left; i <= right; i++) {
            array[i] = resultArr[i];
        }
    }

大家可以看出来,是用递归实现的,有一些地方要稍微讲解一下
下面有三个while循环,第一个看判断条件,可以看出来是当左右两边有任意一边没有比较结束,就进行比较

第二个while循环是判断如果右边已经比较结束,则把左边数据依次赋值给结果数组

第三个while循环是判断左边比较已经结束,则把右边数据依次赋值给结果数组,经过三次循环比较,就可以将左右两边的数据比较完全.

还有一个for循环,看样子是把结果数组又赋值给了原数组,其实是因为,结果数组存放的是左右两边比较之后的结果,需要把比较之后的结果重新赋值给原数组,进行下一次的比较,不然的话,原数组永远不会变,比较也就没有了任何意义.

举个列子:

比如: 4 6 7 1

我们要首先比较4 和 6得到4 6,比较7 和 1得到1 7,这使得结果数组是4 6 1 7,原数组是4 6 7 1,下一次进行比较的时候,如果原数组不变,就变成了比较 4和7,6和1了,会得到错误结果4 7 1 6,而我们希望比较的是,4和1,6和7,所以需要把上一次的结果数组替换掉原数组,就得到了正确结果1 4 6 7.

我们不用递归用迭代来实现一下,代码如下:

private static void sort2(int[] array) {
        int[] result = new int[array.length];
        // 分割之后左右两边的数据长度
        for (int i = 1; i < array.length; i *= 2) {
            // 遍历左右两边的数据块
            for (int j = 0; j < array.length; j += i * 2) {
                int middle = (j + i) < array.length ? j + i : array.length;
                int left = j;    // 记录最左边的下标
                int leftBegin = j;//左边数据快左始下标
                int leftEnd = middle;//左边数据快结束下标
                int rightBegin = middle;//右边数据快起始下标
                //右边数据快结束下标
                int rightEnd = (j + 2 * i) < array.length ? (j + 2 * i) : array.length;
                while (leftBegin < leftEnd && rightBegin < rightEnd) {
                    result[left++] = array[leftBegin] < array[rightBegin] ? array[leftBegin++] : array[rightBegin++];
                }
                while (leftBegin < leftEnd) {
                    result[left++] = array[leftBegin++];
                }
                while (rightBegin < rightEnd) {
                    result[left++] = array[rightBegin++];
                }
            }
            int[] temp = array;
            array = result;
            result = temp;
        }
        result = array;
        System.out.println(Arrays.toString(result));
    }

迭代版与递归版的一点不同是,递归进行了数据分割,先排左边,再排右边,最后归并,迭代使用了数据块长度来进行左右分割的,并且是左右排序同时进行,所以迭代中的for循环更新原数组,在这里就变成了数组之间的直接赋值,因为上一步的结果数组是所有的左右数据块排序之后的.

在Java的Arrays.sort()排序函数中,进行了优化,不同的数组,不同的长度选用的排序算法也不一样,如果小于47则使用插入排序,如果数组长度小于286的时候而大于47,优先使用快速排序,而且还有判断数组是否已经接近排好顺序,数组是否高度结构化等等条件,在真实项目中直接用就可以,一般来说比你自己写的要高效得多,但我们还是要知道这些算法的具体实现.

你可能感兴趣的:(算法,Java,sort)