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