1. 基本有序
在众多排序算法中,有一个概念被多次提及:数组基本有序。
例如:
- 直接插入排序(Insertion Sort)在面对数组基本有序时,体现出良好的性能。
- 平滑排序(Smooth Sort)在数组趋向有序时,其时间复杂度趋向于 O(n)。
- 快速排序(Quick Sort)和堆排序(Heap Sort),在面对基本有序的数组时,并不会对其排序的速度有所增长。
正是由于这个性质,使得直接插入排序,相比于时间复杂度同为 O(n^2) 的冒泡排序(Bubble Sort),拥有了更多的使用场景:
- 希尔排序(Shell Sort)优化直接插入排序的基础。
- 归并排序(Merge Sort)在合并小规模的数组时,采取直接插入排序来优化效率。
那么现在问题就来了:
- 如何去评价“基本有序”这个词?
- 如果现在有两个数组,哪一个是相对来说更加有序的?
- 有没有方法去量化这个特性?
2. 逆序对(Inversion)
https://en.wikipedia.org/wiki/Inversion_(discrete_mathematics)
在数学上,有一个被称之为“逆序对”的性质,它可以用来量化数组的有序程度,定义如下:
对于数组 A,如果存在正整数 i 和 j,且 i < j,存在 A[i] > A[j]。
那么数字对 <i, j> 或 <A[i], A[j]> 可以称之为数组 A 的一个逆序对。
例如:
对于数组 A = {4, 2, 3},<4, 2> 和 <4, 3> 都是这个数组的逆序对,数组 A 的逆序对个数为 2。
对于数组 B = {4, 3, 2},<4, 2>,<4, 3> 和 <3, 2> 都是这个数组的逆序对,数组 B 的逆序对个数为 3。
那么我们可以称:数组 A 比 数组 B 更加有序。
计算逆序对的代码:
public static int calculateInversion(int[] array) { int counter = 0; for (int i = 0; i < array.length - 1; ++i) { for (int j = i + 1; j < array.length; ++j) { if (array[i] > array[j]) { counter++; } } } return counter; }
3. 希尔排序和梳排序(Comb Sort)的优化原因
冒泡排序和直接插入排序,在众多时间复杂度为 O(n^2) 的算法中,也是最低效的一类。
其根本原因在于:排序过程中的每一次交换/移位,只能使原数组的逆序对个数减 1。
例如:
数组 A = { 1, 3, 4, 2 } ,针对元素 2 进行直接插入排序。
- 排序前:A = { 1, 3, 4, 2 },Inversion = 2。
- 排序过程中,整个数组进行了 2 次移位操作。
- 排序后:A = { 1, 2, 3, 4 },Inversion = 0。
而在希尔排序中,每一次的移位操作,会带来更多的逆序对个数减少。
例如,利用之前介绍希尔排序时的例子: http://www.cnblogs.com/jing-an-feng-shao/p/6169690.html
数组 A = { 89, 45, 54, 29, 90, 34, 68 },初始增量 gap = 3。
- 排序前:A = { 89, 45, 54, 29, 90, 34, 68 },Inversion = 10。
- 排序过程中,整个数组进行了 2 次移位操作。
- 排序后:A = { 29, 45, 34, 68, 90, 54, 89 },Inversion = 4。
所以说,在这个案例中,希尔排序利用 2 次移位操作,消除了 6 对逆序对,从而提高了整体的算法效率。
梳排序中同样引进了 gap 的概念,增加了冒泡排序的算法效率。