哈喽,我是程序员大鹏。
前面我们介绍了冒泡排序、选择排序和插入排序,今天我们来看一下进阶的排序。
1959年Shell发明,第一个突破O(n2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。
插入排序思想
插入排序是一种从序列左端开始依次对数据进行排序的算法。在排序过程中,索引左侧的数据陆续归位,而右侧留下的就是还没有被排序的数据。
插入排序的思路就是从右侧的未排序区域内取出一个数据做为待排序数据,然后将它插入到已排序区域内合适的位置上。一直到未排序清空。
排序原理
- 选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
- 按增量序列个数 k,对序列进行 k 趟排序;
- 每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
希尔排序动画
插入排序代码
public static void shellSort(int[] arr) { int j; for (int gap = arr.length / 2; gap > 0; gap /= 2) { for (int i = gap; i < arr.length; i++) { int temp = arr[i]; for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) { arr[j] = arr[j - gap]; } arr[j] = temp; } } }
让我们来一行一行地分析一下代码:
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
上面代码是生成增量,最后的数字保证为1。
for (int i = gap; i < arr.length; i++) {
在上面代码是外层循环,从下标为增量的开始循环,一直到数组的最后一个数据。
int temp = arr[i];
每一轮开始的时候,i是待排序数据的索引,temp的值就是本轮待排序的数据,每次都是从增量为索引的数据开始。
for (j = i; j >= gap && arr[j - gap] > temp; j -= gap)
开始内层的循环,从将等于i并且大于等于增量,并且数据为逆序的时候,进入循环。
arr[j] = arr[j - gap];
数据平移一个位置,将position做为空隙。
arr[j] = temp;
将待排序的数据temp插入到空隙的位置。
选择排序复杂度分析
希尔排序的核心在于间隔序列的设定。既可以提前设定好间隔序列,也可以动态的定义间隔序列。 希尔排序的时间复杂度与增量(即,步长gap)的选取有关。例如,当增量为1时,希尔排序退化成了直接插入排序,此时的时间复杂度为O(N²),而Hibbard增量的希尔排序的时间复杂度为O(N3/2)。
希尔排序稳定性
希尔排序是不稳定的算法,它满足稳定算法的定义。对于相同的两个数,可能由于分在不同的组中而导致它们的顺序发生变化。
算法稳定性 -- 假设在数列中存在a[i]=a[j],若在排序之前,a[i]在a[j]前面;并且排序之后,a[i]仍然在a[j]前面。则这个排序算法是稳定的!