目录
1.基本思想
1.1 插入排序的问题分析
比如数组arr={2,3,4,5,6,1},这时需要插入的数为1(最小),这样的过程是:
- {2,3,4,5,6,6}
- {2,3,4,5,5,6}
- {2,3,4,4,5,6}
- {2,3,3,4,5,6}
- {2,2,3,4,5,6}
- {1,2,3,4,5,6}
结论:当需要插入的数是较小的数时,后移的次数明显增加,对效率有影响
1.2 希尔排序基本思想
- 希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序
- 而以下的希尔排序就是基于插入排序的以下两点性质而提出改进方法:
- 1.插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率
- 2.插入排序每次只能将数据移动一位,导致它很低效
- 基本思想:
- 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;
- 随着增量逐渐减少,每组包含的元素越来越多,当增量减至1时,整个数组恰被分成一组,再进行一次插入排序,算法便终止
- 目的:
- 尽量让小的数快速调整到前面去,尽量把大的数快速调到后面去
2.图解原理
3.代码实现
import java.util.Arrays;
public class ShellSort extends Sort{
@Override
public void sort(Comparable[] arr) {
/**
* 希尔排序就是设置了增量的多轮插入排序
*/
//记录排序轮数
int roundNum = 0;
//设置增量来确保每一轮交换,优先将后面的小元素移动到前面来,最终只是做微调
for(int gap = arr.length/2;gap > 0;gap /= 2){
for (int i = gap; i < arr.length; i++) {
//此方法即使用插入排序中的插入值的方法按照步长插入
insertVal(arr,gap,i);
}
System.out.println("Shell排序的第"+(++roundNum)+"轮后数组为:");
System.out.println(Arrays.toString(arr));
}
}
//将索引位置的值按照指定步长插入到数组索引中去
private void insertVal(Comparable[] arr,int gap,int index){
//*************以下是插入排序的逻辑,只不过是按照步长为gap进行插入排序***************
//保存要插入的元素的值
Comparable insertVal = arr[index];
//从要插入的元素的前一个位置开始寻找当前要插入元素的位置,并将它后面的元素后移
int j = index-gap;
for (; j >= 0; j-=gap) {
//插入值小于当前遍历元素
if(insertVal.compareTo(arr[j]) < 0){
//当前元素右移
arr[j+gap] = arr[j];
}else {
//插入值大于等于当前遍历元素,结束比较
break;
}
}
//通过上述移动,最后确定j+gap为插入的位置,进行插入
arr[j+gap] = insertVal;
//*************************************************************************
}
}
import java.util.Arrays;
public class SortTest {
public static void main(String[] args) {
Integer[] arr = new Integer[]{8,9,1,7,2,3,5,4,6,0};
System.out.println("未排序的数组为:");
System.out.println(Arrays.toString(arr));
ShellSort shellSort = new ShellSort();
shellSort.sort(arr);
}
}
-
希尔排序就是将原序列进行分组,然后对每一组使用插入排序,如此每一轮都会让各个分组变得局部有序
-
到最终增量减到1,再使用插入排序,由于插入排序本身对有序的数组排序效率很高,此时只是微调
-
希尔排序只是在插入排序的基础上增加一个控制增量(gap)的循环罢了
4.性能分析
- 希尔排序的性能取决于增量序列,不同的增量序列,所得到的时间复杂度不同,
- {1,2,4,8,...}这种序列并不是很好的增量序列,使用这个增量序列的时间复杂度(最坏情形)是O(n^2)
- Hibbard提出了另一个增量序列{1,3,7,...,2^k-1},这种序列的时间复杂度(最坏情形)为O(n^1.5),
- Sedgewick提出了几种增量序列,其最坏情形运行时间为O(n^1.3),其中最好的一个序列是{1,5,19,41,109,...}
我们这里只测试以下我们使用{1,2,4,8,...}序列所实现的代码与直接插入排序的性能比较:
import CompletedSort.HalfInsertionSort;
public class SortPerformanceTest {
public static void main(String[] args) {
//创建100000个随机数据
Double[] arr1 = new Double[100000];
for (int i = 0; i < arr1.length; i++) {
arr1[i] = (Double) (Math.random() * 10000000); //这里使用10000000是为了让数据更分散
}
//赋值上述创建的数组arr1的值到数组arr2
Double[] arr2 = new Double[100000];
for (int i = 0; i < arr1.length; i++) {
arr2[i] = arr1[i];
}
//创建两种排序类的对象
ShellSort shellSort = new ShellSort();
DirectInsertionSort_Move directInsertionSort= new DirectInsertionSort_Move();
//使用希尔排序对arr1进行排序
long shellSort_start = System.currentTimeMillis();
shellSort.sort(arr1);
long shellSort_end = System.currentTimeMillis();
System.out.println("希尔排序所用的时间为:"+(shellSort_end - shellSort_start)+"ms");
//使用直接插入排序对arr2进行排序
long directInsertionSort_start = System.currentTimeMillis();
directInsertionSort.sort(arr2);
long directInsertionSort_end = System.currentTimeMillis();
System.out.println("直接插入排序所用的时间为:"+(directInsertionSort_end - directInsertionSort_start)+"ms");
}
}
可以发现,希尔排序要比直接插入排序快很多
5.希尔排序的稳定性分析
排序算法不稳定性的含义:
- 假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
希尔排序是不稳定的
- 由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。
示例:
- 6,3(1),3(2),9
- 第一组:6 3(2)
- 第二组: 3(1) 9
- 希尔排序第一轮之后,
- 3(2),3(1),6,9
- 可以看到原来序列中3的位置发生了变化,所以希尔排序是不稳定的