希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
- 插入排序在对几乎已经排好序的数据操作时, 效率高, 即可以达到线性排序的效率
- 但插入排序一般来说是低效的, 因为插入排序每次只能将数据移动一位
基本思想:
先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
算法过程演示如图,只标记了一组,每次按照一个步长进行分组,如第一组步长为5,则对arr[0]、arr[5]、arr[10]、arr[15]该分组内进行“插入排序”,这里只举例一组,arr[1]、arr[6]等不再赘述。每一次步长排序完成后,步长减少,再次进行分组的插入排序,直到步长为1,插入排序就是希尔排序中步长为1的排序,那么为什么还需要希尔排序呢?因为插入排序每一次都需要挪动数组内的元素,效率较差,而希尔排序因为步长在不断变化,导致整个数组中的元素基本已经在其正确的位置,前后差不出去几个位置,所以效率较高一些。算法复杂度为O(n log2 n)。
还有一个影响希尔排序算法效率的关键因素是步长,很多书籍中为了简单起见,都将步长设为了n/2,n为数组长度,这种步长是比较差的效率,现在多采用n=3*n+1的数列作为步长;关于步长:
步长的选择是希尔排序的重要部分。只要最终步长为1任何步长串行都可以工作。算法最开始以一定的步长进行排序。然后会继续以一定步长进行排序,最终算法以步长为1进行排序。当步长为1时,算法变为插入排序,这就保证了数据一定会被排序。
package sortAlgorithm;
public class ShellSort {
private int[] theArray;
private int nElems;
public ShellSort(int max){
theArray = new int[max];
nElems = 0;
}
public void insert(int value){
theArray[nElems++] = value;
}
public void display(){
System.out.print("A=");
for(int j=0;j<nElems;j++){
System.out.print(theArray[j]+" ");
}
System.out.println("");
}
public void Shell(){
int inner,outer,temp;
int h = 1;
while(h <= nElems / 3)
h = h*3 + 1;
while(h > 0){
for(outer=h;outer<nElems;outer++){
temp = theArray[outer];
inner = outer;
while(inner > h - 1 && theArray[inner-h] >= temp){
theArray[inner] = theArray[inner-h];
inner = inner - h;
}
theArray[inner] = temp;
}
h = (h - 1) / 3;
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int maxSize = 10;
ShellSort shell = new ShellSort(maxSize);
for(int j=0;j<maxSize;j++){
int n = (int)(Math.random()*99);
shell.insert(n);
}
shell.display();
shell.Shell();
shell.display();
}
}
其中,摘出其中一段,会发现,当h=1时,即步长为1时,以下这段代码把h换成1,它就是一段插入排序的代码,所以在理解的基础上,只要把插入排序的算法记住,再加上一段步长的算法公式,希尔排序的算法就很轻松的记住了。
for(outer=h;outer<nElems;outer++){
temp = theArray[outer];
inner = outer;
while(inner > h - 1 && theArray[inner-h] >= temp){
theArray[inner] = theArray[inner-h];
inner = inner - h;
}
theArray[inner] = temp;
}
附上插入排序的代码:对比~
/**
* 插入排序
* 默认arr[0]是一个已经排序好的数,虽然只有一个数,
* 第一轮开始,从第二个数arr[1]和arr[0]比较,如果小于arr[0],
* arr[0]就挪到arr[1],跳出内部循环,到外部循坏内部时,arr[0]已经被往后挪了(虽然他的值还有),
* 所以外部循环时再把被挤掉的数据放到arr[0]处
*/
public static int[] insertSort(int[] arr){
for(int i=1;i<arr.length;i++){
int temp = arr[i];
int flag = i;
while((temp < arr[flag-1]) && (flag > 0)){
arr[flag] = arr[flag - 1];
flag--;
}
arr[flag] = temp;
}
return null;
}