希尔排序原理如下(纯手打):
假设有下面一个数组
99, 38, 65, 82, 26, 13, 27, 49, 55, 1
第一趟排序
首先设置一个增量inc,比如说最开始的增量为数组长度的一半即 arr.length/2 = 5,则上面的数组可以分成如下几组(相同颜色的为一组):
将每组中的元素进行比较,小的放在前面,大的放在后面(即进行直接插入排序)则第一趟排序后的数组为
第二趟排序
将增量设置为第一趟排序时增量的一半,即inc此时为5/2 = 2,则上面的数组可以分成如下几组(相同颜色的为一组)
然后将分好组的数据分别按照直接插入排序的方式进行比较,排序后的结果为
第三趟排序
将增量设置为第一趟排序时增量的一半,即inc此时为2/2 = 1,则上面的数组可以分成如下几组(相同颜色的为一组)
此时数组变为1组(基本有序),对这一组进行直接插入排序,得到的结果就是原始数组排序后的数组:
总结一下:
由上面的原理介绍可以看出:
①当增量为inc时,共可以分成inc-1个小组,且每个小组的第一个元素分别为0,1,2…,inc-1,inc
②在每个小组内其实就是分别进行了直接插入排序
③该算法的终止条件为inc>0或者说inc = 1
结论4是代码2的理论基础
④从索引inc开始,之后的每一个数都至少是所在小组中的第二个数,且inc是第一小组的第2个,inc+1是第二小组(如果有的话)的第二个…..所以遍历inc及其之后的每一个数时,都可以通过该数所在索引i找到该小组中前面的数,分别为i-1*inc,i-2*inc….i-n*inc直到i-n*inc>=0时为止.由此可以通过对比遍历到的数arr[i]与前面已经排好序(这里一定要注意)的arr[i-1*inc],arr[i-2*inc]….,arr[i-n*inc]进行一一比较,如果遍历到的数字arr[i]小于前面的数,前面的数就往后移动inc个位置,直到找到一个不大于arr[i]的数,也就找到了arr[i]应该插入的正确位置.
按照上诉思路我写出的代码如下:
代码1:
package cn.nrsc.sort;
public class ShellSort {
public static void main(String[] args) {
int[] arr = { 49, 38, 65, 97, 23, 22, 76, 1, 5, 8, 2, 0, -1, 22 };
shellSort(arr);
System.out.println("排序后:");
for (int i : arr) {
System.out.println(i);
}
}
private static void shellSort(int[] arr) {
int inc = arr.length / 2;// 初始增量
while (inc > 0) {
for (int i = 0; i < inc; i++) { // 拆分后数组的首个元素
for (int j = i; j < arr.length; j += inc) {//遍历获得以i为首个元素的小组
//下面其实就是直接插入排序算法
int k;
int tmp = arr[j];// 新遍历的值等待插入到前面的有序数组中
// 通过下面的循环将比tmp大的数字往后移inc位,并找到不比tmp大的数字的下标j
// 因为比tmp大的数字都往后移了一位,则第j+inc位的数字移动到了j+2inc位,j+inc位给空出来了
// 并且由该算法的思想可知,tmp正好应该放在j的后inc位,即j+inc位
for (k = j - inc; k >= i; k -= inc) {
if (arr[k] > tmp) {
arr[k + inc] = arr[k];
} else {
break;
}
}
arr[k + inc] = tmp;
}
}
inc /= 2; // 增量减半
}
}
}
代码2:
package cn.nrsc.sort;
public class ShellSort {
public static void main(String[] args) {
int[] arr = { 49, 38, 65, 97, 23, 22, 76, 1, 5, 8, 2, 0, -1, 22 };
shellSort(arr);
System.out.println("排序后:");
for (int i : arr) {
System.out.println(i);
}
}
private static void shellSort(int[] arr) {
// 控制增量
for (int inc = arr.length / 2; inc > 0; inc /= 2) {
// 从按照增量所分小组的第2个数开始进行遍历----inc为第一小组的第2个数,inc+1为第2小组(如果有的话)的第二个数....
for (int i = inc; i < arr.length; i++) {
int tmp = arr[i];// 新遍历的值等待插入到前面的有序数组
int j = i; // 这里之所以要将i赋给一个新的变量,
// 是因为在下面的循环中要通过该变量的变化,来找到arr[i]之前的数组
// 通过下面的循环①将大于tmp的数都向后移动了inc位,②找到tmp需要插入的位置j
// tmp的位置是j是因为先用arr[j-inc]与tmp进行了比较
while (j - inc >= 0 && arr[j - inc] > tmp) {
arr[j] = arr[j - inc];
j = j - inc;
}
arr[j] = tmp;
}
}
}
}
由总结4还可以得到另一种形式的直接插入排序算法:
package cn.nrsc.sort;
public class InsertSort {
public static void main(String[] args) {
int[] arr = { 49, 38, 65, 97, 23, 22, 76, 1, 5, 8, 2, 0, -1, 22 };
insertSort(arr);
System.out.println("排序后:");
for (int i : arr) {
System.out.println(i);
}
}
private static void insertSort(int[] arr) {
// 从数组中的第二个数开始往前看
for (int i = 1; i < arr.length; i++) {
int tmp = arr[i];// 新遍历的值等待插入到前面的有序数组
int j = i; // 这里之所以要将i赋给一个新的变量,
// 是因为在下面的循环中要通过该变量的变化,来找到arr[i]之前的数组
//通过下面的循环①将大于tmp的数都向后移动了一位,②找到tmp需要插入的位置j
//tmp的位置是j是因为先用arr[j-1]与tmp进行了比较
while (j - 1 >= 0 && arr[j - 1] > tmp) {
arr[j] = arr[j - 1];
j = j - 1;
}
//将tmp插入到位置j
arr[j] = tmp;
}
}
}
最后说一点
希尔排序的最差情况下的时间复杂度为Θ(N²),即最好最坏情况下都是N²,比如说对于下面的数组,前几个增量内的循环没起到任何作用,只有增量为1时才起到了排序的效果,则前几个增量相当于白跑了好几趟—-具体的更深入的研究大家可以自行探索.