核心思想
假如有以下一组序列,需要使用希尔排序对其进行升序。
我们按照某个增量将序列分为多列。比如上面这个需要我们可以按照4、2、1将其分为2列、4列、8列。然后分别对其每列上的元素进行插入排序。这个增量是一系列数。希尔本身推荐的增量系数为1、2、4、8、16...等2的k次方。
如果增量序列是1、2、4、8...我取其反序列。优先使用大的。比如说4。我们按照每间隔4个元素分为一列。
如上图我们可以将原序列分为2行,其实就是4列。然后我们对没列进行插入排序。排完原序列就变成
我们再按照增量是2进行分列:
排序完 原序列就是
最后再按照增量为1分为1列,此时其实就是上面的序列。再进行一轮插入排序。
最终得到升序序列
我们看到希尔排序的其实是使用了插入排序。不过插入排序最外层使用的是for循环所以它的平均复杂度是O(N²)。而我们最外层是用的2的k次方来获取增量,所以平均复杂度是O(NlogN)。
也可以认为希尔排序是插入排序的一种优化写法。
代码实现
- 获取增量序列
/**
* shell推荐的Step列
*
* @return step列
*/
private List<Integer> shellStepQueue() {
ArrayList<Integer> queue = new ArrayList<>();
int step = array.length;
while ((step >>= 1) > 0) {
queue.add(step);
}
return queue;
}
- 按着增量分列,然后对每列进行插入排序。
private void sort(Integer step) {
// col:第几列 结尾是step
for (int col = 0; col < step; col++) {
//将原有数据模拟的分为以step列
//需注意欢迎每列在原来数组的位置:
//对每列数据进行插入排序
for (int begin = col + step; begin < array.length; begin += step) {
int cur = begin;
while (cur > col && cmp(cur, cur - step) < 0) {
swap(cur, cur -= step);
}
}
}
}
所有代码如下:
public class ShellSort<E extends Comparable<E>> extends Sort<E> {
@Override
protected void sort() {
//获取步队列
List<Integer> stepQueue = shellStepQueue();
//对所有列执行插入排序
for (Integer step : stepQueue) {
sort(step);
}
}
private void sort(Integer step) {
// col:第几列 结尾是step
for (int col = 0; col < step; col++) {
//将原有数据模拟的分为以step列
//需注意欢迎每列在原来数组的位置:
//对每列数据进行插入排序
for (int begin = col + step; begin < array.length; begin += step) {
int cur = begin;
while (cur > col && cmp(cur, cur - step) < 0) {
swap(cur, cur -= step);
}
}
}
}
/**
* shell推荐的Step列
*
* @return step列
*/
private List<Integer> shellStepQueue() {
ArrayList<Integer> queue = new ArrayList<>();
int step = array.length;
while ((step >>= 1) > 0) {
queue.add(step);
}
return queue;
}
}
优化
希尔排序复杂度是 O(NlogN),其实的logN就是增量队列获取方式导致的。我们只要优化增量序列获取方式就能优化时间复杂对。
下面这种写法是目前公认最优的增量序列获取方式。
/**
* 目前公认最优的Step获取方式
*
* @return step
*/
private List<Integer> sedgewickStepSequence() {
List<Integer> stepSequence = new LinkedList<>();
int k = 0, step = 0;
while (true) {
if (k % 2 == 0) {
int pow = (int) Math.pow(2, k >> 1);
step = 1 + 9 * (pow * pow - pow);
} else {
int pow1 = (int) Math.pow(2, (k - 1) >> 1);
int pow2 = (int) Math.pow(2, (k + 1) >> 1);
step = 1 + 8 * pow1 * pow2 - 6 * pow2;
}
if (step >= array.length) break;
stepSequence.add(0, step);
k++;
}
return stepSequence;
}
只需要将第一种写法中的增量序列获取方法替换即可。当然插入排序部分还可以优化。
优化完的时间复杂度为O(n^(1.3—2))。