Java常用算法解析及示例(二)

这一篇主要讲希尔排序与归并排序,希望大家都可以通过算法的思想、思路,亲自动手写出来,而并不是随意贴一下代码,运行,成功就掌握,只有自己亲自写出来,才更能接近算法的思想和真谛。

1. 希尔排序

希尔排序是对直接插入排序(不知道的同学请参考我上一篇算法)的一种优化,直接排序是把不符合条件的每个元素一步一步向后移动,插入, 而我们希望这些元素不要一个一个的移动,而是大浮动的移动,不然针对[5,4,3,2,1]这种倒序序列,比较和移动元素均需n-1次,空间复杂度和时间复杂度都会大大的增加。 
希尔排序的思想先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。 所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序; 然后,取第二个增量d2

例如[5,4,3,2,1,0] ,首次增量设gap=length/2=3,则为3组[5,2] [4,1] [3,0],组内比较并交换。经过一次排序后为 [2,1,0,5,4,3],gap = gap/2 =1 则执行直接插入排序,如果使用直接排序 5后退一位 4前进一次 赋值2次 则交换:2+3+4+5+6 =20次 希尔排序:6(第一轮)+2+3+0+2+3 =16次,少了四次,这个计算方法并不精确,只针对例子 如果更多的元素效率则会更明显。

按照惯例先贴出自己的打印方法,次数可能不太精准,主要是看数组的移动过程

    /**
     * 我是输出方法
     *
     * @param count 排序轮数
     * @param arr   数组
     */
    public static void print(int count, int[] arr) {

        StringBuilder result = new StringBuilder();
        result = result.append("排序第").append(count).append("次,数组为:");

        for (int num : arr) {
            result = result.append(num).append(" ");
        }

        System.out.println(result);
    }

希尔排序代码

    /**
     * 希尔排序
     * 希尔排序是对直接插入排序(不知道的同学请参考我上一篇算法)的一种优化,
     * 直接排序是把不符合条件的每个元素一步一步向后移动,插入,
     * 而我们希望这些元素不要一个一个的移动,而是大浮动的移动,不然针对[5,4,3,2,1]这
     * 种倒序序列,比较和移动元素均需n-1次,空间复杂度和时间复杂度都会大大的增加。
     * 希尔排序的思想
     * 先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。
     * 所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;
     * 然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量  =1(  <  …<d2<d1),
     * 即所有记录放在同一组中进行直接插入排序为止。
     * <p>
     * 例如[5,4,3,2,1,0] ,首次增量设gap=length/2=3,则为3组[5,2] [4,1] [3,0],
     * 组内比较并交换。经过一次排序后为 [2,1,0,5,4,3],gap = gap/2 =1 则执行直接插入排序
     * 如果使用直接排序 5后退以为 4前进一次 赋值2次 则交换:2+3+4+5+6 =20次 希尔排序:6(第一轮)+2+3+0+2+3 =16次
     * 少了四次,这个计算方法并不精确,只针对例子 如果更多的元素效率则会更明显
     */
    public static void shellSort(int[] arr) {

        // 随意分数组  此次从中间分开
        int gap = arr.length / 2;
        int i, count = 1;

        while (gap >= 1) {

            for (int j = 0; j < arr.length; j++) {

                //后面如果指针移动  那么数据将会变化 所以暂时存储起来
                int temp = arr[j];

                //直接插入排序,只不过是大步的移动
                for (i = j; i - gap >= 0 && temp < arr[i - gap]; i -= gap) {
                    //满足条件 往后推元素
                    arr[i] = arr[i - gap];
                }
                arr[i] = temp;

            }

            print(count, arr);
            gap /= 2;
            count++;
        }

    }

2. 归并排序

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用(分治法思想请看下面解释)。将已有序的子序列合并,得到完全有序的序列;

即先使每个子序列有序,再使子序列段间有序。 
若将两个有序表合并成一个有序表,称为二路归并。

归并操作(merge),也叫归并算法,指的是将两个顺序序列合并成一个顺序序列的方法。 
如 设有数列{6,202,100,301,38,8,1} 
初始状态:6,202,100,301,38,8,1 
第一次归并后:{6,202},{100,301},{8,38},{1},比较次数:3; 
第二次归并后:{6,100,202,301},{1,8,38},比较次数:4; 
第三次归并后:{1,6,8,38,100,202,301},比较次数:4; 
总的比较次数为:3+4+4=11,; 
逆序数为14

分治法可以通俗的解释为:把一片领土分解,分解为若干块小部分,然后一块块地占领征服, 
被分解的可以是不同的政治派别或是其他什么,然后让他们彼此异化。 
分治法的精髓:

分—-将问题分解为规模更小的子问题;

治—-将这些规模更小的子问题逐个击破;

合—-将已解决的子问题合并,最终得出“母”问题的解

看下面代码示例

    /**
     * 归并排序
     * 归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,
     * 该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。(分治法思想请看下面解释)
     * 将已有序的子序列合并,得到完全有序的序列;
     * 即先使每个子序列有序,再使子序列段间有序。
     * 若将两个有序表合并成一个有序表,称为二路归并。
     * 归并操作(merge),也叫归并算法,指的是将两个顺序序列合并成一个顺序序列的方法。
     * 如 设有数列{6,202,100,301,38,8,1}
     * 初始状态:6,202,100,301,38,8,1
     * 第一次归并后:{6,202},{100,301},{8,38},{1},比较次数:3;
     * 第二次归并后:{6,100,202,301},{1,8,38},比较次数:4;
     * 第三次归并后:{1,6,8,38,100,202,301},比较次数:4;
     * 总的比较次数为:3+4+4=11,;
     * 逆序数为14
     * <p>
     * 分治法可以通俗的解释为:把一片领土分解,分解为若干块小部分,然后一块块地占领征服,
     * 被分解的可以是不同的政治派别或是其他什么,然后让他们彼此异化。
     * 分治法的精髓:
     * 分--将问题分解为规模更小的子问题;
     * 治--将这些规模更小的子问题逐个击破;
     * 合--将已解决的子问题合并,最终得出“母”问题的解
     *
     * @param arr 待排序数组
     * @param start 待排序数组的起始角标
     * @param end 待排序数组的结束角标
     * @param temp 临时数组 主要是存储排序过的数组
     *
     */
    public static void mergeSort(int[] arr, int start, int end, int[] temp) {

        //分裂到最小级别  结果分裂成单个
        if (start < end) {

            //折中分裂
            int tempFlag = (start + end) / 2;

            //把分开的两段数组继续递归分裂  知道分裂成单个的
            mergeSort(arr, start, tempFlag, temp);
            mergeSort(arr, tempFlag + 1, end, temp);

            //两个数组比较的时候  因为是折中  所以后一段数组加一
            int j = tempFlag + 1, t = 0, i = start;

            //遍历两段数组 并从两段数组的起始位比较
            while (i <= tempFlag && j <= end) {
                //如果 前段数据的值比 后面数组的值小  则交换位置  并自增较小数组的角标
                //继续与 另一数组同脚本比较   直到比较完一方的数据
                if (arr[i] < arr[j]) {
                    temp[t++] = arr[i++];
                } else {
                    temp[t++] = arr[j++];
                }
            }

            // 将前一段剩余元素放进temp中
            while (i <= tempFlag) {
                temp[t++] = arr[i++];
            }
            // 将后一段剩余元素放进temp中
            while (j <= end) {
                temp[t++] = arr[j++];
            }

            //  还原角标 因为temp数组是从零开始存储
            t = 0;
            /*
            将temp中的元素全部拷贝到原数组中
            TODO 这一段比较重要  因为我们合并的时候是合并原数组的数据  如果不拷贝的话
            那么我们合并的还是无序数组,所以每次排序完都拷贝到原数组
            */
            while (start <= end) {
                arr[start++] = temp[t++];
            }
        }
        print(1,arr);

    }

下面贴出我的测试类

package com.study.studysummary.algorithm;import org.junit.Before;import org.junit.Test;public class JavaAlgorithmTest {

    private static int[] args = {9, 1, 489, 79452, 4, 7, 214, 45, 12, 2, 3, 5, 87, 798, 13};

    @Before
    public void before(){
        JavaAlgorithm.print(0,args);
    }

    /**
     * 希尔排序
     */
    @Test
    public void testShellSort() {
        JavaAlgorithm.shellSort(args);
    }

    /**
     * 归并排序
     */
    @Test
    public void testMergeSort() {
        JavaAlgorithm.mergeSort(args,0,args.length-1,new int[args.length]);
    }}

第二章我们先学到这里,如果大家有问题可以留言,或者到https://gitee.com/only-one-one/studysummary下载源码

点赞