排序算法杂谈(一) —— 量化数组的有序程度

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 的概念,增加了冒泡排序的算法效率。

 

posted @ 2018-05-23 00:23  Gerrard_Feng  阅读(1330)  评论(0编辑  收藏  举报