第七章 高级排序

原创 2015年11月21日 19:37:46
一、希尔排序
     希尔排序是因为插入排序有个严重的问题。假设一个很小的数据项在很靠近右端的位置上,这里本来应该是比较大的数据项所在的位置。然而要把它移到正确的位置,所有中间的数据项都要右移一位。效率大打折扣。因此希尔排序通过加大插入排序中元素之间的间隔,并在这些有间隔的元素中进行插入排序,从而使数据项能大跨度地移动。当这些数据项排过一趟序后,希尔排序算法减小数据项的间隔再进行排序,以h表示该间隔(增量)。
     
     通过这样一轮排序后,所有元素离它最终有序序列中的位置相差不到两个单元,这就是基本有序。然后再通过1-增量排序,就可以很快得到最终的有序结果。
     一般我们使用的间隔序列满足h=3*h+1,即1,4,13,40,121,364...

二、希尔排序的Java代码
class ArraySh {
     private long[] theArray;
     private int nElems;
     
     public ArraySh(int max) {
          theArray = new long[max];
          nElems = 0;
     }

     public void insert(long value) {
          theArray[nElems++] = value;
     }

     public void shellSort() {
          int inner;
          int outer;
          
          //求得最大的合适间隔
          int h = 1;
          while(h <= nElems/3)
               h = h*3 + 1;

          while(h > 0) {
               for(outer = h;outer < nElems;outer++) {
                    temp = theArray[outer];
                    inner = outer;
     
                    while(inner > h-1 && theArray[inner-h] >= temp) {
                         theArray[inner] = theArray[inner-h];
                         inner -= h
                    }
                    theArray[inner] = temp;
               }
          h = (h-1)/3;
          }
     }
}
     除了一些特殊情况,还没有办法从理论上分析希尔排序的效率。基于试验的评估,它的时间效率从O(N^(3/2))到O(N^(7/6)),通常表示O(N*(logN)^2)。

三、划分
     划分数据就是把数据分成两组,使所有关键字大于特定值的数据项在一组,使所有关键字小于特定值的数据项在另一组。

public int partitionIt(int left, int right, long pivot) {
     int leftPtr = left - 1;
     int rightPtr = right + 1;
     
     while(true) {
          while(leftPtr < right && theArray[++leftPtr] < pivot);
          while(rightPtr > left && theArray[—rightPtr] > pivot);
          if(leftPtr >= rightPtr)
               break;
          else
               swap(leftPtr, rightPtr);//如果遇到不满足左边小,右边大的数据项,交换
     }
return leftPtr;//划分的位置
}

public void swap(int dex1, int dex2) {
     long temp = theArray[dex1];
     theArray[dex1] = theArray[dex2];
     theArray[dex2] = temp;
}
     划分算法由两个指针开始工作,分别指向数组的两头。左边的指针向右边移动,而右边的指针向左边移动。当左边的指针遇到比比较值小的时候,继续右移,但是遇到比比较值大的数据的时候,停下来。同样的右边的指针遇到比比较值小的数据的时候停下来。然后两者进行交换(前提是左边的指针还在右边的指针的左边)。
     划分算法的效率是O(N)。两个指针开始时分别在数组的两端,然后以恒定的速度相向移动,停止移动并且交换。当指针相遇时,划分完成。如果要划分两倍数目的数据项,指针以同样的速率移动,但是需要比较和交换两倍数目的数据项,因此耗时也是两倍的。从而,划分算法的运行时间和N成正比。

四、快速排序
     在大多数情况下,快速排序都是最快的,O(N*logN)。快速排序算法本质上通过把一个数组划分为两个子数组,然后递归地调用每一个子数组进行划分算法,再进行快速排序来实现。
public void recQuickSort(int left, int right) {
     if(right - left <= 0)
          return;
     else {
          int partition = partitionIt(left, right);
          recQuickSort(left, partition-1);
          recQuickSort(partition+1, right);
     }
}
  1. 把数组或子数组划分为左边一组和右边一组;
  2. 调用自身对左边的一组进行排序;
  3. 调用自身对右边的数组进行排序;

     那现在要讨论一个很重要的问题——partitionIt()要选取什么样的枢纽呢?以下是一些相关的思想:
  • 以具体的数据项的关键字的值作为枢纽,称为pivot
  • 为了简便,假设总是选择待划分的子数组最右端的数据项为pivot
  • 划分完成后,如果pivot被插入到左右子数组之间的分界处,那么它就落在排序的最终位置
     那pivot一直在数组最右端,那何时把它放入合适的位置呢?如下图,我们只要交换枢纽pivot和右边子数组的最左端的数据项就可以。
     让我们一起来看下最终的代码:

class ArrrayIns
{
     private long[] theArray;
     private int nElems;

     public ArrayIns(int max)
     {
          theArray = new long[max];
          nElems = 0;
     }

     public void insert(long value)
     {
          theArray[nElems++] = value;
     }

     public void quickSort()
     {
          recQuickSort(0, nElems-1);
     }

     public void recQuickSort(int left, int right)
     {
          if(right <= left)
               return
          else
          {
               long pivot = theArray[right];

               int partition = partitionIt(left, right, pivot);
               recQuickSort(left, partition-1);
               recQuickSort(partition+1, right);
          }
     }

     public int partitionIt(int left, int right, long pivot)
     {
          int leftPtr = left-1;
          int rightPtr = right;
          while(true)
          {
               while(theArray[++leftPtr] < pivot);
               while(rightPtr > 0 && theArray[—rightPtr] > pivot);

               if(leftPtr >= rightPtr)
                    break;
               else
                    swap(leftPtr, rightPtr);
          }
     swap(leftPtr, right);//把pivot移动到最终适合的位置
     return leftPtr;
     }

     public void swap(int dex1, int dex2)
     {
          long temp = theArray[dex1];
          theArray[dex1] = theArray[dex2];
          theArray[dex2] = temp;
     }
}

     让我们讨论下算法复杂度。在理想状态下,应该选择被排序的数据项的中值数据项作为枢纽,这样使得被划分的两个子数组大小相等。对于快速排序算法来说,拥有两个大小相等的子数组是最优的情况。假设最坏的划分情况是一个子数组只有一个数据项,另一个子数组含有剩下的N-1个数据项。如果每趟都出现这种1和N-1的情况,那么算法的执行效率降低到O(N^2)。因此如果待排序数组真的是任意排列的,那么选择最右端的数据项,情况不会太糟。但是如果数据是有序或者逆序的,从数组的两端选择数据项作为枢纽都不是好办法。
     那么如何去解决上诉的问题呢?这里已经被设计了一种更好的选择枢纽的方法——三数据取中。即在数组里的第一个、中间一个、最后一个这三个数据中取中值数据,同时还对这三个数据项进行了排序。这样的话,划分的时候,就可以不用考虑最左端的数据和最右端的数据。并且枢纽也在中间。接下来让我们看下相关的代码。
/*
*三数据取中方法
*/
public  long medianOf3(int left, int right) 
{
     int center = (left+right)/2;

     if(theArray[left] > theArray[center])
          swap(left, center);
     
     if(theArray[left] > theArray[right])
          swap(left, right);

     if(theArray[center] > theArray[right])
          swap(center, right);

     swap(center, right-1);//把中值放到数组右侧
     return theArray[right-1];//返回中值,即枢纽pivot
}

/*
*当数组个数小于等于3个的时候,手动排序。
*/
public void manualSort(int left, int right)
{
     int size = right-left+1;
     if(size <= 1)
          return;

     if(size == 2)
     {
          if(theArray[left] > theArray[right])
               swap(left, right);
          return;
     }
     else
     {
          if(theArray[left] > theArray[right-1])
               swap(left, right-1);
          if(theArray[left] > theArray[right])
               swap(left, right);
          if(theArray[right-1] > theArray[right])
               swap(right-1, right);
     }
}

public void recQuick(int left, int right)
{
     int size = right-left+1;
     if(size <= 3)
          manualSort(left, right);
     else
     {
          long median = medianOf3(left, right);
          int partition = partitionIt(left, right, median);
          recQuickSort(left, partition-1);
          recQuickSort(partition+1, right);
     }
}
     当然,处理小划分除了手动排序外,还可以使用插入排序。虽然并没有明显的节省时间。
     快速排序的时间复杂度为O(N*logN)。

五、基数排序
     基数排序是把关键字拆分成数字位,并且按数字位的值对数据项进行排序,神奇的是,它不需要比较操作。这里以10为基数进行讨论。以下是算法的步骤:
  1. 根据数据项个位上的值,把所有的数据项分为10组。
  2. 然后对这个10组数组项重新排列,把所有关键字是以0为结尾的数据项排在最前,然后是1...最后是9。这是第一趟排序。
  3. 第二趟排序,再次把所有数据项分为10组。但是这次依据的是数据项十位上的值来分组。这次分组不能改变先前的排序顺序。然后再以关键字从0到9排序。
  4. 对于剩余的位重复这个过程,直到没有位数可以排序。
     421 240 035 532 305 430 124
     (240 430) (421) (532) (124) (035) (305)
     (305) (421 124) (430 532 035) (240)
     (035) (124) (240) (305) (421 430) (532)
     基数排序的效率乍一看,如果有10个数据项,则有20次复制。假设有5位数字排序,就需要20*5次复制。如果有100个数据项,那么就有200*5次复制。复制的次数和数据项的个数成正比,效率就是O(N)。这是我么看到的效率最高的排序。但是不幸的是,只要数据项越多,就需要越长的关键字。如果数据项增加10倍,那么关键字就要增加一位。复制的次数是数据项的个数与关键字的位数的乘积成正比。位数是关键字值的对数,因此算法的效率倒退为O(N*logN)。


排序算法第七章

  • 2014年03月18日 16:48
  • 598KB
  • 下载

JDBC第七章知识点总结——JDBC高级应用--DAO封装

JDBC第七章知识点总结——JDBC高级应用--DAO封装 知识点预览 JDBC知识回顾 DAO封装 ThreadLocal JDBC及DAO综合运用 JDBC回顾 1.      JDBC 2....
  • Wentasy
  • Wentasy
  • 2012年10月14日 14:33
  • 3371

libevent参考手册第七章:Bufferevent:高级话题 (九)

译自http://www.wangafu.net/~nickm/libevent-book/Ref6a_advanced_bufferevents.html   本章描述bufferevent的一...
  • wzsy
  • wzsy
  • 2016年10月13日 13:55
  • 344

pp看书笔记---C#高级编程第九版 第七章 【运算符和类型强制转换】

写在前面: 1.介绍一些不常见但有用的运算符 2.介绍C#对象相等判断方法及解析 3.运算符重载 4.类型强制转换运算符(对5的拓展,是4的一种) 不常见但有用的运算符 checked ...

第七章-面向对象高级编程

1 使用__slots__ 1.1 绑定属性和方法   1) 来给实例绑定属性     在没有做任何限制的时候, 可以通过 实例.属性名 = 属性值   class Student(ob...

第七章 —高级组件:拖动条seekBar、星级评分条RatingBar、进度条ProgressBar、滚动视图ScrollView

拖动条seekBar

JavaScript高级程序设计(第3版)第七章读书笔记

第七章 函数表达式 1. 函数声明有一个重要特征 ,函数声明提升。即在执行代码之前会先读取函数声明,意味着可以把函数声明放在调用它的语句后面。 2. 使用arguments.callee实现对函数...

第七章 SpringMVC高级的技术 笔记1

概述: AbstractAnnotationConfigDispatcherServletInitializer它其实做了很多事情,所以需要慢慢研究它 此类的方法之一就是customizeRegi...

书籍:PHP与MYSQL程序设计:高级OPP特性(第七章):持续更新(完)

五个高级特性:对象克隆、继承、接口、抽象类、命名空间 同样在每行代码都有解析。重要的是概念。 class Corporate_Drone{//克隆p138,对象克隆很好理解,输入一遍就懂这个概念 ...

第七章 Java高级 API

第七章 Java高级 API    概念 就是application programming interface的简称     Windows API就是windows操作系统提供的各种函数javaA...
  • kukum
  • kukum
  • 2011年05月11日 16:34
  • 273
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:第七章 高级排序
举报原因:
原因补充:

(最多只允许输入30个字)