算法思想:
希尔排序又叫缩小增量,也是一种插入排序。希尔排序中有一个步长,通过这个步长将数组分化为步长个序列,然后分别对这些序列进行直接插入排序,然后改变步长,重复操作,直到步长为1,而此时数组基本有序,很快。步长的取法一般是开始为(数组长度/2),然后是对步长一直除2直到步长为1。
算法运行过程图:
代码实现思路:
- 思路1:按照算法思想,有一个循环步长的循环,然后对某个步长划分的那些子序列进行插入排序。
- 思路2:相对于上面的每次直接插入排序从序列的第一个元素开始,直到该序列的最后一个元素,其实直接插入排序时可以从下标为步长的元素开始,然后相当于混合这对划分的每个序列进行直接排序,直接排序其实是从序列的第2个元素开始,然后每次判断是否比该序列的前一个数小,如果小的话就进行插入操作。见shellSort1()注释。
优化方法:
- 优化步长。有点麻烦。
- 优化直接插入排序。见shellSort()注释。
时间复杂度:
根据步长的不同取法不一定。有点复杂。
空间复杂度:
O(1)
稳定性:
不稳定。
在子序列的插入过程中可能打乱稳定性。
代码实现:
package sort;
/**
* @作者:dhc
* @创建时间:20:41 2018/8/14
* @排序方法:希尔排序
* @时间复杂度:有点复杂。
* @空间复杂度:O(1)
* @稳定性:不稳定。
*/
public class ShellSort {
/**
* 直接插入排序说明:这里直接插入排序因为有几种不同的写法,1,常规的先找插入位置,在后移 插入。2,对1插入位置查找采用二分查找。3,不用先查找
* 位置,将两个循环合成一个,直接后移。4,同3一样,用一个循环,不过用交换来实现。因此希尔排序也可有对应的写法。采用不同的写法,对于希尔排序的结果
* 也稍有点点优化。
* 步长说明:取数组长度的一半,然后循环取一半直到步长为1.
* @param nums
*/
public static void shellSort(int[] nums) {
int tem = 0;
for(int ss = nums.length/2;ss >= 1; ss /= 2){
for (int k = 0; k < ss; k++) {
//直接插入排序
for(int i = ss;i < nums.length;i += ss){
tem = nums[i];
int j = i;
for(j = i;j > 0 && nums[j - ss] > tem;j -= ss){
nums[j] = nums[j-ss];
}
nums[j] = tem;
}
}
}
}
/**
* 优化循环次数
* 相对于上面的每次直接插入排序从序列的第一个元素开始,直到该序列的最后一个元素,其实可以直接插入排序时可以从下标为步长的元素开始,然后
* 相当于混合这对划分的每个序列进行直接排序,直接排序其实是从序列的第2个元素开始,然后每次判断是否比该序列的前一个数小,如果小的话就进行
* 插入操作。
* @param nums
*/
public static void shellSort1(int[] nums) {
int tem = 0;
for(int ss = nums.length/2;ss >= 1; ss /= 2){
//这里取i等于步长,相当于直接进入插入排序,用划分的第一个序列的第二个元素开始,当i+1时,如不步长不为1,则相当于
// 进入下一个序列的插入排序,这个i+1同样是第二个元素,直到i+ss,有进入第一个序列的第三个数,循环往复,直到数组最后
for (int i = ss; i < nums.length; i++) {
//这里判断是否比该序列的前一个数小,如果小的话就要插入到该序列的前面
if(nums[i] < nums[i - ss]){
tem = nums[i];
int j = i;
for(j = i - ss;j >=0 && nums[j] > tem;j-=ss){
nums[j+ss] = nums[j];
}
nums[j + ss] = tem;
}
}
}
}
public static void main(String[] args) {
int[] nums = new int[]{5,4,3,2,1,1,7,6};
shellSort1(nums);
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i]+" ");
}
}
}