算法简介:希尔插入排序,是在直接插入排序的基础上进行优化
直接插入排序的优点:对于小数量的数据排序较快
算法描述:将待排序数组在逻辑上分成一定步长的子数组,对这些子数组进行直接插入排序。然后在重新划分子数组,再进行直接插入排序,直到整个数组有序(步长为1)
假设数组长度为N
那么排序的轮数n最佳为满足 2^n < N
每轮排序的步长 step = 2^n -1
为什么这样设置,我也不知道
经过实践
当N很大的时候,希尔插入排序数据比直接插入排序所需时间更多,应该是频繁的方法调用耗费时间,所以时间上并不一定有优势,但是数组需要进行移动的次数少了好几个数量级
上代码
package com.ysb;
import java.util.Random;
/**
* @author Administrator 测试各种排序方法 全部按 升序排列
*/
public class Sort {
// 数组长度
public static final int ARRAYLENGTH = 50000;
public static long moveCount = 0;
public static void main(String[] args) {
int[] randomArray = new int[ARRAYLENGTH];
for (int i = 0; i < ARRAYLENGTH; i++) {
randomArray[i] = new Random().nextInt(ARRAYLENGTH);
}
int[] sortArray = new int[ARRAYLENGTH];
for (int i = 0; i < ARRAYLENGTH; i++) {
sortArray[i] = i;
}
System.out.println("使用插入排序对无序数组进行排序");
long startTime1 = System.currentTimeMillis();
insertSort(randomArray,0,randomArray.length-1,1);
long endTime1 = System.currentTimeMillis();
System.out.println("使用插入排序 排序" + ARRAYLENGTH + "个无序数中使用的时间为"
+ (endTime1 - startTime1) + "ms");
System.out.println("移动次数="+moveCount);
moveCount = 0;
long startTime2 = System.currentTimeMillis();
System.out.println("使用插入排序对有序数组进行排序");
insertSort(sortArray,0,sortArray.length-1,1);
long endTime2 = System.currentTimeMillis();
System.out.println("使用插入排序 排序" + ARRAYLENGTH + "个有序数中使用的时间为"
+ (endTime2 - startTime2) + "ms");
System.out.println("移动次数="+moveCount);
long startTime3 = System.currentTimeMillis();
System.out.println("使用希尔插入排序对无序数组进行排序");
for (int i = 0; i < ARRAYLENGTH; i++) {
randomArray[i] = new Random().nextInt(ARRAYLENGTH);
}
insertShellSort(randomArray);
long endTime3 = System.currentTimeMillis();
System.out.println("使用希尔插入排序 排序" + ARRAYLENGTH + "个无序数中使用的时间为"
+ (endTime3 - startTime3) + "ms");
System.out.println("移动次数="+moveCount);
}
/**
* 插入排序的改进 -希尔排序
*/
private static void insertShellSort(int[] array) {
int temp= 1;
//希尔排序的轮数
int index = 0;
// 希尔排序每轮的步长 9 4 2^3 = 8
int step = 0;
while(true ) {
temp = temp*2;
if(temp > ARRAYLENGTH)
break;
index++;
}
for(int i=index;i>0;i-- ) {
//每轮的步长
step = (int)Math.pow(2,i) - 1;
int startIndex = 0;
int endIndex = 0;
//每个子序列进行插入排序
while(true) {
//所有的子序列都排好了
int flag = 0;
while(true) {
//获取每个子序列的endIndex
endIndex = startIndex+endIndex+step;
if(endIndex >= ARRAYLENGTH) {
endIndex = endIndex-(startIndex+step);
break;
}
flag++;
}
//此序列只有1个元素
if(flag == 0)
endIndex = startIndex;
// System.out.println("第"+(index-i+1)+"轮排序"+"第"+(startIndex+1)+"个子序列,步长为"+step);
insertSort(array,startIndex,endIndex,step);
startIndex++;
endIndex = 0;
if(startIndex == step)
break;
}
}
}
/**
* @param array 排序的数组
* @param from 排序起点
* @param to 排序终点
* @parm step 步长
* 为了希尔排序子序列需要的插入排序,改造此方法
*/
private static void insertSort(int[] array,int from ,int to,int step) {
// System.out.print("排序子序列:");
// for(int i=from;i<=to;i+=step)
// System.out.print(" "+array[i]);
// System.out.println();
// 不包括最后1个,因为数组元素是从0开始的
for (int originalArrayIndex= from+step; originalArrayIndex <= to; originalArrayIndex+=step) {
int waitToSort = array[originalArrayIndex];
for (int sortedArrayIndex = from; sortedArrayIndex < originalArrayIndex; sortedArrayIndex+=step) {
if (waitToSort < array[sortedArrayIndex]) {
moveArray(array,sortedArrayIndex,originalArrayIndex,step-1);
array[sortedArrayIndex] = waitToSort;
break;
}
}
}
//验证一下
// System.out.println("有序数组");
// for(int j=from;j<=to;j+=step) {
// if(j>100)
// break;
//System.out.print(array[j]+" ");
//}
//System.out.println();
}
/**
* 移动数组
* @param array
* @param startIndex
* @param endIndex
*/
private static void moveArray(int[] array, int startIndex, int endIndex,int step) {
for(int i=endIndex;i>startIndex;i=i-1-step) {
array[i] = array[i-1-step];
moveCount ++;
}
}
}
运行测试
数组长度为 10W
使用插入排序对无序数组进行排序
使用插入排序 排序100000个无序数中使用的时间为43437ms
移动次数=2501035799
使用插入排序对有序数组进行排序
使用插入排序 排序100000个有序数中使用的时间为33453ms
移动次数=0
使用希尔插入排序对无序数组进行排序
使用希尔插入排序 排序100000个无序数中使用的时间为51078ms
移动次数=2652651
数组长度为1W
使用插入排序对无序数组进行排序
使用插入排序 排序10000个无序数中使用的时间为390ms
移动次数=24875947
使用插入排序对有序数组进行排序
使用插入排序 排序10000个有序数中使用的时间为344ms
移动次数=0
使用希尔插入排序对无序数组进行排序
使用希尔插入排序 排序10000个无序数中使用的时间为360ms
移动次数=145196