数据结构和算法分析 排序

交换次数和比较次数是衡量一部分排序算法的复杂度的

排序例子 O(N2)

这是一个人人都能实现的例子。通过循环判断,将最小的元素放在最前面。

  1. public static void sort(int[] arr) {
  2.       // 每次对比下标i到arr.length-1的值 然后将最小值放在i下标的位置
  3.       for (int i = 0; i < arr.length; i++) {
  4.          // 记录最小值的下标
  5.          int minIndex = i;
  6.          for (int j = i; j < arr.length; j++) {
  7.             if (arr[j] < arr[minIndex]) {
  8.                minIndex = j;
  9.             }
  10.          }
  11.          // 交换最小值与i
  12.          if(i != minIndex)
  13.          {
  14.             System.out.println("交换");
  15.             int temp = arr[i];
  16.             arr[i] = arr[minIndex];
  17.             arr[minIndex] = temp;
  18.          }
  19.       }
  20.    }

这个排序比较的次数是N*(N-1)/2次,交换的次数取决于原始数组和排序后的数组的重合程度(下标相同且数值相同),这基本是随机的,实际应用中不会有巧合的重合。时间复杂度为O(N2)。这种排序基本书上都不会讲,没有意义。

冒泡排序O(N2)

  1. public static void bubblingSort(int[] arr)
  2.    {
  3.       for(int i = 0; i < arr.length; i++)
  4.       {
  5.          for(int j = 0; j < arr.length - i - 1; j++)
  6.          {
  7.             if(arr[j] > arr[j+1])
  8.             {
  9.                //交换
  10.                int temp = arr[j];
  11.                arr[j] = arr[j+1];
  12.                arr[j+1] = temp;
  13.             }
  14.          }
  15.  
  16.       }
  17.    }

这个排序比较的次数是N*(N-1)/2次,交换的次数取决于原始数组的有序程度。如果原始数组就是有序的,那么交换次数为0。如果原始数组是随机的,交换次数也不会太大,因为每一个循环都在讲数组趋于有序,越往后交换的次数越少。

时间复杂度为O(N2)。

插入排序O(N2)

插入排序(Insertion Sort)是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

  1. public static void insertSort(int[] arr) {
  2.       int j, key;
  3.       int len = arr.length;
  4.       for (int i = 1; i < len; i++) {
  5.          key = arr[i];
  6.          //将第i个元素 插入到已经有序的前i-1个序列中
  7.          //查询插入位置的时候 从i-1往前进行遍历 不能反过来 因为此处不仅需要遍历 还需要往后平移
  8.          for (j = i - 1; j >= 0; j--) {
  9.             //没有找到插入位置 证明插入位置在前面 那么需要往后平移一位 方便前面的插入
  10.             //第一次平移会平移到i处
  11.             if (arr[j] > key)
  12.             {
  13.                arr[j + 1] = arr[j];
  14.             }
  15.             else
  16.             {
  17.                //找到插入位置 不再往前遍历了
  18.                break;
  19.             }
  20.          }
  21.          //插入到位置
  22.          arr[j + 1] = key;
  23.       }
  24.    }

 

可以写的更简单一点:

  1. public static void insertSort(int[] arr) {
  2.       int j, key;
  3.       int len = arr.length;
  4.       for (int i = 1; i < len; i++) {
  5.          key = arr[i];
  6.          for (j = i - 1; j >= 0&&arr[j] > key; j--) {
  7.             arr[j + 1] = arr[j];
  8.          }
  9.          arr[j + 1] = key;
  10.       }
  11.    }

这个排序比较的次数取决于原始数组的有序程度,最差是N*(N-1)/2次,如果原始数组是有序的,那么可以达到N。

交换的次数也取决于原始数组的有序程度。如果原始数组就是有序的,那么交换次数为0。

所以如果预先就是排好序的,那么它的时间时间复杂度可以达到O(N)。

相对于复杂的Nlog(N)排序算法,数据量比较小而且基本有序的集合比较适合这些排序算法。

上面的这种插入排序也叫直接插入排序,还有:

折半插入排序:其它部分不变,只是寻找插入位置的时候,使用折半查找算法。

希尔算法:下一章节讲述。

希尔算法O(N2)

希尔算法是基于插入排序的一下两点性质而提出改进方法的:
    *插入排序在对几乎已经排好的数据操作时,效率高,即可达到线性排序的效率;
    *但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;

希尔排序的基本思想是:

把记录按步长 gap 分组,对每组记录采用直接插入排序方法进行排序
随着步长逐渐减小,所分成的组包含的记录越来越多,当步长的值减小到 1 时,整个数据合成为一组,构成一组有序记录,则完成排序。

  1. public static void shellSort(int[] arr) {
  2.       int len = arr.length;
  3.       int i,j;
  4.       //循环定义步长
  5.       for (int gap = len / 2; gap > 0; gap = gap / 2) {
  6.          //从i=gap开始 因为gap之前的序列都只有一个值 是排序好的
  7.          for(i = gap; i < len; i++)
  8.          {
  9.             int temp = arr[i];
  10.             for(j = i ; j>=gap&&temp < arr[j-gap]; j = j - gap)
  11.             {
  12.                arr[j] = arr[j-gap];
  13.             }
  14.             arr[j] = temp;
  15.          }
  16.       }
  17.    }

希尔排序相对于直接插入排序的优势:

{ 2, 5, 8, 4, 6, 1, 9 } 我们要把1移动到最前面,如果使用直接插入排序和折半插入排序(折半插入排序只能减少插入插入点的对比次数,不能减少交换次数),都需要交换5次之多。

如果使用希尔排序,第一次交换后1和8互换,第二次交换后就会到首位。只需要两次交换。

希尔排序的增量序列和时间复杂度:

  • 希尔增量序列:首先,我们上面演示的例子使用的增量序列叫希尔增量序列。它的最坏情况下的复杂度为O(N2)。
  • Hibbard 增量序列:hi=2i−1,Hibbard 增量序列的递推公式为: h1=1,hi=2hi−1+1,Hibbard 增量序列的最坏时间复杂度为 Θ(N3/2);平均时间复杂度约为 O(N5/4)。
  • Sedgewick 增量序列,通项公式为: hi=max(94j−92j+1,4k−32k+1),Sedgewick 增量序列的最坏时间复杂度为 O(N4/3);平均时间复杂度约为 O(N7/6)。

Sedgewick 增量序列是目前表现最好的增量序列,但是随着数学的研究,可能会有更好的增量序列出现。

对希尔排序的不同增量序列的复杂度研究设计比较多的数学公式,我就不研究了。

希尔算法的性能在实践中是可以完全接受的,即使对数以万计的N也是如此,所以如果数据量不是非常大,希尔算法的使用还是非常广泛的。

 

posted on 2017-07-15 18:18  张小贱1987  阅读(160)  评论(0编辑  收藏  举报

导航