希尔排序(Shell‘s Sort)
希尔排序又称“缩小增量排序”(Diminishing Increment Sort),是一种基于插入排序的快速的排序算法。对于大规模的乱序数组插入排序很慢,因为它只会交换相邻的元素。如果最小的元素正好在数组的尽头,要将它插入到正确的位置就得进行 N - 1 次移动。希尔排序为了加快速度简单地改进了插入排序,交换不相邻的元素以对数组的局部排序,并最终使用插入排序将局部有序的数组排序。
基本思想:
将原始待排数组划分为若干大小为 S (即增量)的子数组,分别对它们进行插入排序,待各子数组基本有序时(见图1),也就是整个数组基本有序时,最后再对全体进行一次直接插入排序。要注意,此时的直接插入排序相较于一开始就对原始数组进行直接插入排序要快很多,因为此时整个数组已经相对有序,移动次数会有对应的减少。
图1
算法的详细图解步骤如下:
增量序列可以有各种取法,但是要注意:应使增量序列中的值没有除 1 之外的公因子,并且最后一个增量值必须等于 1。
算法实现:
/**
* 希尔排序
* @param arr 待排序数组
*/
private static void shellSort(int[] arr) {
int N = arr.length;
int h = 1;
while (h < N / 3)
h = 3 * h +1; // 计算递增序列
while (h >= 1) {
for (int i = h; i < N; i++) {
for (int j = i; j >= h && arr[j - h] > arr[j]; j -= h) // 将 a[i] 插入到 a[i - h]、a[i - 2 * h]、 a[i - 3 * h]...
swap(arr, j, j - h);
}
h /= 3; // 改变增量直至最后为 1
}
}
/**
* 交换数组中的两个元素
* @param arr 数组
* @param i 元素下标
* @param j 另一个元素下标
*/
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
算法测试:
package algorithms;
import java.util.Random;
/**
* 希尔排序测试
* @author Wll
*
*/
public class ShellSortTest {
public static void main(String[] args) {
int size = 100000000;
int limit = 100000000;
int[] arr = getRandomArray(size, limit);
long start = System.currentTimeMillis();
shellSort(arr);
long end = System.currentTimeMillis();
System.out.println("======耗时 " + (end - start) + " ms======");
printArray(arr);
}
/**
* 希尔排序
* @param arr 待排序数组
*/
private static void shellSort(int[] arr) {
int N = arr.length;
int h = 1;
while (h < N / 3)
h = 3 * h +1; // 计算递增序列
while (h >= 1) {
for (int i = h; i < N; i++) {
for (int j = i; j >= h && arr[j - h] > arr[j]; j -= h) // 将 a[i] 插入到 a[i - h]、a[i - 2 * h]、 a[i - 3 * h]...
swap(arr, j, j - h);
}
h /= 3; // 改变增量直至最后为 1
}
}
/**
* 获得一个随机整型数组
* @param size 数组大小
* @param limit 数组元素大小的上限
* @return 一个随机数组
*/
private static int[] getRandomArray(int size, int limit) {
int[] arr = new int[size];
Random random = new Random();
for (int i = 0; i < size; i++)
arr[i] = random.nextInt(limit);
return arr;
}
/**
* 打印数组,默认只打印前一百个
* @param arr 待打印数组
*/
private static void printArray(int[] arr) {
int length = arr.length;
int count = 1;
for (int i = 0; i < length; i++) {
System.out.printf("%-6d", arr[i]);
if (count++ % 10 == 0)
System.out.println();
if (count == 101 && count < length) {
System.out.print("......");
break;
}
}
System.out.println("\n");
}
/**
* 交换数组中的两个元素
* @param arr 数组
* @param i 元素下标
* @param j 另一个元素下标
*/
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}