准备工作:
希尔排序一般是在插入排序的算法上实现的,如果对此还不熟悉的小伙伴可以先去了解一下插入排序,这样学习希尔排序可以事半功倍。
正文:
希尔排序又叫缩小增量排序,具体怎么定义的,这里就不再赘述了。先举个例子,比如,有一个待排序的数组的长度为len,先取n = len / 2,把数组划分为n个子序列,每隔n个位置的元素为同一个子序列的元素,先对每个子序列的元素进行排序,效率会快出不少,然后再取n = n / 2,重复循环,知道n = 1,此时只有一个序列了,但是由于之前已经将大部分元素排序好了,所以并不会有太大的效率问题。其中,n就是增量,每次循环,n缩小一半,就是所谓的缩小增量。文字说明永远是抽象的,上图:
这是一个待排序的数组,len = 8,先取n = len / 2 = 4,将数组划分为4个子序列,每隔4个位置就是同一子序列中的元素(同种颜色的元素为同一子序列中的)
这是第一次排序后的结果,可以看到,每个子序列的元素都是有序的了,然后再取 n = n / 2 = 2,将整个数组划分为2个子序列,再进行排序
这是第二次排序后的结果,同样的道理,每个子序列中的元素都是有序的,随后n = 1,也就是最后的排序了
如果上面的思维理清了,就可以直接上代码了
package sort;
import java.util.Arrays;
public class SortTest {
public static void main(String[] args) {
int[] arr = {12, 48, 21, 65, 2, 1, 92, 37};
shellSort(arr);
}
private static void shellSort(int[] arr) {
int n = arr.length;
int i, len = n / 2;
int count = 1;
while (len >= 1) {
for (i = len; i < n; i++) {//从当前增量位置开始往后循环,往前每隔len个就是同一个子序列的元素
if (arr[i] < arr[i - len]) {//前面已经有序,若最后一个比a[i]还小,说明a[i]已经在合适的位置了,大大减小循环量
//在排序好的子序列里找位置
int temp = arr[i];//a[i]在while第一次循环值就会改变,所以要用临时变量记录下来,因为之后在子序列里给他找位置肯定是和他比较
int k = i - len;
while (k >= 0 && arr[k] > temp) {
//已经排序好的子序列肯定是有序的,这里只是给a[i]找到合适的位置,每次比a[i]大的元素向后移动一个位置,然后把这个位置留给a[i]
arr[k + len] = arr[k];//这里就是向后移动一个位置
k -= len;//然后下标向前移动,判断是否还是比a[i]大,是的话就继续后移
}
arr[k + len] = temp;//最后把a[i]放到合适的位置
}
}
System.out.println("第" + count++ + "趟排序,结果为" + Arrays.toString(arr));
len /= 2;
}
}
}
到此,希尔排序的实现已经结束了,那么它的效率到底怎么样呢,这里就简单地和冒泡排序比较一下
package sort;
import java.util.Random;
public class SortTest {
public static void main(String[] args) {
int[] arr = new int[200000];
int[] brr = new int[200000];
Random random = new Random();
for (int i = 0; i < 200000; i++) {
arr[i] = random.nextInt(1000);
brr[i] = random.nextInt(1000);
}
shellSort(arr);
bubbleSort(brr);
}
private static void shellSort(int[] a) {
long start = System.currentTimeMillis();
int n = a.length;
int i, len = n / 2;
while (len >= 1) {
for (i = len; i < n; i++) {//从当前增量位置开始往后循环,往前每隔len个就是同一个子序列的元素
if (a[i] < a[i - len]) {//前面已经有序,若最后一个比a[i]还小,说明a[i]已经在合适的位置了,大大减小循环量
//在排序好的子序列里找位置
int temp = a[i];//a[i]在while第一次循环值就会改变,所以要用临时变量记录下来,因为之后在子序列里给他找位置肯定是和他比较
int k = i - len;
while (k >= 0 && a[k] > temp) {
//已经排序好的子序列肯定是有序的,这里只是给a[i]找到合适的位置,每次比a[i]大的元素向后移动一个位置,然后把这个位置留给a[i]
a[k + len] = a[k];//这里就是向后移动一个位置
k -= len;//然后下标向前移动,判断是否还是比a[i]大,是的话就继续后移
}
a[k + len] = temp;//最后把a[i]放到合适的位置
}
}
len /= 2;
}
long end = System.currentTimeMillis();
System.out.println("希尔插入排序完成的时间:" + (end - start) + "毫秒");
}
private static void bubbleSort(int[] arr) {
long start = System.currentTimeMillis();
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
System.out.println("冒泡排序完成的时间:" + (System.currentTimeMillis() - start) + "毫秒");
}
}
两个数组都是20w相同的随机数进行排序,其结果:
效率整整相差了1800多倍,可怕!