不同排序方式的优缺点
三种简单的排序算法冒泡、选择、插入排序算法,它们的时间复杂度大,都是O(N^2),如果数据量少,我们还能忍受,但是数据量大,那么这三种简单的排序所需要的时间有可能会是是我们所不能接受的。
在学习解递归的时候,介绍了归并排序,归并排序需要O(NlogN),这比简单排序要快了很多,但是归并排序有个缺点,它需要的空间是原始数组空间的两倍,当我们需要排序的数据占据了整个内存的一半以上的空间,那么是不能使用归并排序的。
于是就有了两种快速排序:希尔排序和快速排序。
希尔排序
算法原理
直接插入排序存在一个效率问题,如果一个很小的数在很靠近右边的位置,那么想让这个很小的数插入到左边排好序的位置,那么左边排好序的数据项都必须向右移动一位,这个步骤就是将近执行了N次复制,虽然不是每个数据项都必须移动N个位置,但是每个数据项平均移动了N/2次,N个数据项总共就是N*N/2次,因此插入排序的效率是O(N^2)。
如果以某种方式不必一个一个移动中间所有的数据项,就能把较小的数据项移动到左边,那么这个算法的执行效率会有很大的改进。
希尔排序通过加大插入排序中元素的间隔,并在这些有间隔的元素中进行插入排序,从而使数据项能够大跨度的移动。当这些数据项排过一趟后,希尔排序减小数据项的间隔再进行排序,依次进行下去,最后间隔为1时,就是我们上面说的简单的直接插入排序。
排序间隔选取
希尔原稿中,他建议间隔选为N/2,也就是每一趟都讲排序分为两半。但是这已经被证明不是最好的序列。
另一种变形方法是使用2.2整除每一个间隔,这比用2整除会显著改善排序效果。
还有一种很常用的间隔序列:在不小于len/3下间隔序列step*3+1,每次循环后间隔(step-1)/3。
无论是什么间隔序列,最后必须满足一个条件,就是逐渐减少间隔最后一定要等于1,因此最后一趟排序一定是简单的插入排序。
希尔排序图解
初始步长h为4:
比较两个元素大小:
交换两个元素位置:
不断向后移动指针:
走一轮以后,步长h从4变为1:
代码实现
package advancedsort;
import java.util.Arrays;
public class AdvancedSortTest1 {
public static void main(String[] args) {
int[] arr = {99,88,77,66,55,44,33,22,11,9,8,7,6,5,4,3,2,1};
// insertSort(arr);
shellSort3(arr);
}
//实现简单的插入排序
public static void insertSort(int[] data){
System.out.println("排序前:"+ Arrays.toString(data));
int outer;
int inner;
int temp;
for(outer=1;outer<data.length;outer++){
inner = outer;
temp = data[outer];
while (inner>0 && temp<data[inner-1]){
//当前元素比inner指针前一个元素小,需要移动元素
data[inner] = data[inner-1];
inner--;
}
//结束while时,inner-1个元素比temp小,所以将temp放到inner位置
data[inner] = temp;
}
System.out.println("排序后:"+Arrays.toString(data));
}
//实现希尔排序,间隔是上一次间隔的一半
public static void shellSort1(int[] data){
System.out.println("排序前:"+ Arrays.toString(data));
int outer;
int inner;
int temp;
int len = data.length;
for (int step=len/2;step>0;step=step/2){
//对每一个step进行一次循环
for (outer=step;outer<len;outer++){
inner = outer;
temp = data[outer];
while (inner-step>=0 && temp<data[inner-step]){
data[inner] = data[inner-step];
inner = inner-step;
}
//当temp小于等于data[inner-step]时结束循环
data[inner] = temp;
}
System.out.println("间隔数是"+step+"时,数组的排序:"+Arrays.toString(data));
}
System.out.println("排序后:"+Arrays.toString(data));
}
//实现希尔排序,间隔是上一次间隔的2.2
public static void shellSort2(int[] data){
System.out.println("排序前:"+ Arrays.toString(data));
int outer;
int inner;
int temp;
int len = data.length;
for (int step=(int)(len/2.2);step>0;step=(int)(step/2.2)){
//对每一个step进行一次循环
for (outer=step;outer<len;outer++){
inner = outer;
temp = data[outer];
while (inner-step>=0 && temp<data[inner-step]){
data[inner] = data[inner-step];
inner = inner-step;
}
data[inner] = temp;
}
System.out.println("间隔数是"+step+"时,数组的排序:"+Arrays.toString(data));
}
System.out.println("排序后:"+Arrays.toString(data));
}
//实现希尔排序,其中step = (step-1)/3
public static void shellSort3(int[] data){
System.out.println("排序前:"+ Arrays.toString(data));
int outer;
int inner;
int temp;
int len = data.length;
int step = 1;
//计算初始化间隔
while (step<=len/3){
step = step*3+1;
}
while (step>0){
//对每一个step进行一次循环
for (outer=step;outer<len;outer++){
inner = outer;
temp = data[outer];
while (inner-step>=0 && temp<data[inner-step]){
data[inner] = data[inner-step];
inner = inner-step;
}
data[inner] = temp;
}
System.out.println("间隔数是"+step+"时,数组的排序:"+Arrays.toString(data));
step = (step-1)/3;
}
System.out.println("排序后:"+Arrays.toString(data));
}
}