zoukankan      html  css  js  c++  java
  • 八、高级排序

    冒泡排序,选择排序,插入排序容易实现,但速度比较慢。归并排序速度比简单排序要快,但需要的空间是原始数组空间的两倍。

    1、希尔排序

    希尔排序是基于插入排序的。插入排序带来的问题,假设一个很小的数据项在很靠近右端的位置上,这里本来应该是值比较大的数据项躲在的位置。把这个小数据项移动到在左边的正确位置上,所有的中间数据项都必须向右移动一位。这个步骤对每一个数据项都执行了将近N次的复制。虽不是所有的数据项都必须移动N个位置,但是数据项平均移动了N/2个位置,这就执行了N次N/2个移位,总共是N*N/2次复制。因此,插入排序的执行效率是O(N*N)。

    希尔排序通过加大插入排序中元素之间的间隔,并在这些有间隔的元素中进行插入排序,从而使数据项能大跨度地移动。当这些数据项排过一趟序后,希尔排序算法减小数据项的间隔再进行排序,依次进行下去。进行这些排序时数据项之间的间隔被称为增量,用字母h来表示:h=3*h+1,h为1,4,7,...,n,n不超过数组的大小。如数组大小为1000,则h为1,4,13,40,121,364。h的选取尽量保证间隔序列中的数字互质。

    运行时间:O(N3/2)~O(N7/6)

    // to run this program: C>java ShellSortApp
    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;     
          nElems++;                    
       }
    
       public void display()    
       {
          System.out.print("A=");
          for(int j=0; j<nElems; j++)   
             System.out.print(theArray[j] + " ");  
          System.out.println("");
       }
    
       public void shellSort()
       {
          int h = 1;                    
          while(h <= nElems/3)
             h = h*3 + 1;               
          while(h>=1)
          {
          //以下两个for循环为插入排序
    int in,out;    for(out=h;out<nElems;out+=h) {   long temp = theArray[out];   for(in=out-h;in>=0;in-=h) { if(theArray[in]>temp)       theArray[in+h] = theArray[in];    else break; } theArray[in+h] = temp; } h=(h-1)/3; } } } class ShellSortApp { public static void main(String[] args) { int maxSize = 10; ArraySh arr; arr = new ArraySh(maxSize); for(int j=0; j<maxSize; j++) { long n = (int)(java.lang.Math.random()*99
    ); arr.insert(n); } arr.display(); arr.shellSort(); arr.display(); }
    }

    2、划分

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

    运行时间:O(N)

    // to run this program: C>java PartitionApp
    class ArrayPar
    {
       private long[] theArray;          
       private int nElems;               
    
       public ArrayPar(int max)        
       {
          theArray = new long[max];    
          nElems = 0;                    
       }
    
       public void insert(long value)    
       {
          theArray[nElems] = value; 
          nElems++;                     
       }
    
       public int size()             
       { return nElems; }
    
       public void display()     
       {
          System.out.print("A=");
          for(int j=0; j<nElems; j++)   
             System.out.print(theArray[j] + " "); 
          System.out.println("");
       }
    
       public int partitionIt(int left, int right, long pivot)
       {

        int leftPtr = left;
        int rightPtr = right;

        while(true)
        {

             //防止出现数组中数据全部小于pivot,要加上leftPtr<=right
          while(leftPtr <= right && theArray[leftPtr] <= pivot)
            leftPtr++;
          while(rightPtr >= left && theArray[rightPtr] >= pivot)
            rightPtr--;
          if(leftPtr>=rightPtr)
            break;
          else
            swap(leftPtr, rightPtr);
        }
        return leftPtr;

       } 
    
       public void swap(int dex1, int dex2)  
       {
          long temp;
          temp = theArray[dex1];            
          theArray[dex1] = theArray[dex2];  
          theArray[dex2] = temp;          
       } 
    }  
    
    class PartitionApp
    {
       public static void main(String[] args)
       {
          int maxSize = 16;         
          ArrayPar arr;                 
          arr = new ArrayPar(maxSize);  
    
          for(int j=0; j<maxSize; j++)  
          {                        
             long n = (int)(java.lang.Math.random()*199);
             arr.insert(n);
          }
          arr.display();                
    
          long pivot = 99;           
          System.out.print("Pivot is " + pivot);
          int size = arr.size();
                                       
          int partDex = arr.partitionIt(0, size-1, pivot);
    
          System.out.println(", Partition is at index " + partDex);
          arr.display();                
       } 
    }

    3、快速排序

     在大多数情况下,快速排序都是最快的,执行时间为O(N*log2Ν)级 。(这只是对内部排序后者或者说随机存储器内的排序而言,对于在磁盘文件中的数据进行的排序,其他的排序算法可能更好)

    快速排序算法本质上通过把一个数组划分为两个子数组,然后递归地调用自身为每一个子数组进行快速排序来实现的,算法必须要选择枢纽以及根据这个枢纽划分为两个区域,左边小于这个枢纽,右边大于这个枢纽,再分别对左边和右边区域再划分子区域。

    // to run this program: C>java QuickSort1App
    class ArrayIns
    {
       private long[] theArray;         
       private int nElems;              
    
       public ArrayIns(int max)         
       {
          theArray = new long[max];      
          nElems = 0;                    
       }
    
       public void insert(long value)
       {
          theArray[nElems] = value;      
          nElems++;                      
       }
    
       public void display()            
       {
          System.out.print("A=");
          for(int j=0; j<nElems; j++)    
             System.out.print(theArray[j] + " ");
          System.out.println("");
       }
    
       public void quickSort()
       {
          recQuickSort(0, nElems-1);
       }
    
       public void recQuickSort(int left, int right)
       {
          if(left>=right)             
              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; int rightPtr = right-1; while(true) { while(leftPtr <= right-1 && theArray[leftPtr] <= pivot ) leftPtr++; while(rightPtr >= left && theArray[rightPtr] >= pivot) rightPtr--; if(leftPtr >= rightPtr) break; else swap(leftPtr, rightPtr); }
        //将作为标准的那个值放到leftPtr,使得左边都比它小,右边都比它大 swap(leftPtr, right);
    return leftPtr; } public void swap(int dex1, int dex2) { long temp = theArray[dex1]; theArray[dex1] = theArray[dex2]; theArray[dex2] = temp; } } class QuickSort1App { public static void main(String[] args) { int maxSize = 16; ArrayIns arr; arr = new ArrayIns(maxSize); for(int j=0; j<maxSize; j++) { long n = (int)(java.lang.Math.random()*99); arr.insert(n); }
    arr.display(); arr.quickSort(); arr.display(); } }

    如果数组中的数据是逆序排序的,则性能降到了O(N2)。问题出在枢纽的选择上,理想状态下,应该选择被排序的数据项的中值数据项作为枢纽。也就是说,应该有一半的数据项大于枢纽,一半的数据项小于枢纽。这会使数组被划分成两个大小相等的子数组。对快速排序算法来说拥有两个大小相等的子数组是最优的情况。如果快速排序算法必须要对划分的一大一小两个子数组排序,那么将会降低算法的效率,这是因为较大的子数组必须要被划分更多次。N个数据项数组的最坏的划分情况是一个子数组只有一个数据项,另一个子数组含有N-1个数据项。如果在每一趟划分中都出现这种1个数据项和N-1个数据项的分割,那么每一个数据项都需要一次单独的划分步骤。在逆序排列的数据项中实际上发生的就是这种情况:所有的子数组中,枢纽都是最小的数据项,因此每一次划分都产生一个N-1个数据项的子数组以及另外一个只包含枢纽的子数组。快速排序以O(N2)运行的时候,除了慢还有另外一个潜在的问题。当划分的次数增加的时候,递归方法的调用次数也增加了。每一个方法调用都要增加所需递归工作栈的大小。如果调用次数太多,递归工作栈可能会发生溢出,从而使系统瘫痪。选择最右端的数据项作为枢纽,如果数据项真的是任意排列的,那么这个选择不会太坏,因为通常情况下枢纽不会太靠近数组的两端。但是,当数据是有序的或者逆序时,从数据的一端或者另外一端选择数据项作为枢纽都不是好办法。

    选择任意一个数据项作为枢纽的确是很简单的,但是正如我们已经看到的那样,这并总是一个好的选择。可以检测所有的数据项,并且实际计算哪一个数据项是中值数据。这应该是理想的枢纽的选择,可是由于这个过程需要比排序本身更长的时间,因此它不可行。折衷的方法是找到数组里第一个、最后一个以及中间位置数据项的居中数据项值,并且设此数据项为枢纽。选择第一个,最后一个以及中间位置数据项的中值被称为"三数据项取中"方法。查找三个数据项的中值数据项自然比查找所有数据项的中值数据项快得多,同时也是有效地避免了在数据已经有序或者逆序的情况下,选择最大的或者最小的数据项作为枢纽的机会。很可能存在一些很特殊的数据排列使得三数据项取中的方法很低效,但是在通常情况下,对于选择枢纽它都是一个又快又有效的方法。

    // to run this program: C>java QuickSort2App
    class ArrayIns
    {
       private long[] theArray;        
       private int nElems;             
    
       public ArrayIns(int max)      
       {
          theArray = new long[max];   
          nElems = 0;               
       }
    
       public void insert(long value)   
       {
          theArray[nElems] = value;      
          nElems++;                   
       }
    
       public void display()        
       {
          System.out.print("A=");
          for(int j=0; j<nElems; j++)    
             System.out.print(theArray[j] + " ");  
          System.out.println("");
       }
    
       public void quickSort()
       {
          recQuickSort(0, nElems-1);
       }
    
       public void recQuickSort(int left, int right)
       {
          int size = right-left+1;
          if(size <= 3)                
             manualSort(left, right);
          else                      
          {
             long median = medianOfThree(left, right);
             int partition = partitionIt(left, right, median);
             recQuickSort(left, partition-1);
             recQuickSort(partition+1, right);
          }
       } 
    
       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);             
          }
       }
    
       //对最左边left,最右边right和中间的值排序,并将中间值
       //与数组的right-1互换。
       public long medianOfThree(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];  
       } 
    
       public void swap(int dex1, int dex2) 
       {
          long temp = theArray[dex1];    
          theArray[dex1] = theArray[dex2];  
          theArray[dex2] = temp;           
       }
    
        public int partitionIt(int left, int right, long pivot)
        {
           int leftPtr = left+1;         
           int rightPtr = right-2;      
    
           while(true)
           {
              while( leftPtr <= right-2 && theArray[leftPtr] <= pivot )  
                 leftPtr++;                                  
              while( rightPtr >= left+1 && theArray[rightPtr] >= pivot ) 
                 rightPtr--;                                 
              if(leftPtr >= rightPtr)      
                 break;                    
              else                         
                 swap(leftPtr, rightPtr); 
           } 
           swap(leftPtr, right-1);   
           return leftPtr;             
        }  
    }  
    
    class QuickSort2App
    {
       public static void main(String[] args)
       {
          int maxSize = 16;           
          ArrayIns arr;                 
          arr = new ArrayIns(maxSize);  
    
          for(int j=0; j<maxSize; j++)  
          {                        
             long n = (int)(java.lang.Math.random()*99);
             arr.insert(n);
          }
          /*arr.insert(10);      
          arr.insert(10);      
          arr.insert(10);
          arr.insert(10);
          arr.insert(10);*/
          arr.display();               
          arr.quickSort();              
          arr.display();             
       }
    } 

      

    如果使用三数据项取中划分方法,则必须要遵循快速排序算法不能执行三个或者少于三个数据项的划分的规则。在这种情况下,数字3被称为切割点。

    处理小划分的另一个选择是使用插入排序。当使用插入排序的时候,不用限制以3为切割点。可以把界限定为10,20或者其他任何数。当要排序的长度小于10时,用插入排序,当大于等于10时,使用三数据项取中划分方法。 

    // to run this program: C>java QuickSort3App
    class ArrayIns
    {
       private long[] theArray;          
       private int nElems;            
    
       public ArrayIns(int max)   
       {
          theArray = new long[max];     
          nElems = 0;                    
       }
    
       public void insert(long value)  
       {
          theArray[nElems] = value;     
          nElems++;                    
       }
    
       public void display()           
       {
          System.out.print("A=");
          for(int j=0; j<nElems; j++)  
             System.out.print(theArray[j] + " ");
          System.out.println("");
       }
    
       public void quickSort()
       {
          recQuickSort(0, nElems-1);
       }
    
       public void recQuickSort(int left, int right)
       {
          int size = right-left+1;
          if(size < 10)                  
             insertionSort(left, right);
          else                          
          {
             long median = medianOfThree(left, right);
             int partition = partitionIt(left, right, median);
             recQuickSort(left, partition-1);
             recQuickSort(partition+1, right);
          }
       }  
    
       public long medianOfThree(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];    
       }  
    
       public void swap(int dex1, int dex2)  
       {
          long temp = theArray[dex1];      
          theArray[dex1] = theArray[dex2];  
          theArray[dex2] = temp;           
       }
    
        public int partitionIt(int left, int right, long pivot)
        {
           int leftPtr = left+1;         
           int rightPtr = right-2;  
           while(true)
           {
              while( leftPtr <= right-2 && theArray[leftPtr] <= pivot )
                 leftPtr++;                               
              while( rightPtr >= left+1 && theArray[rightPtr] >= pivot ) 
                 rightPtr--;                                
              if(leftPtr >= rightPtr)     
                 break;                   
              else                        
                 swap(leftPtr, rightPtr); 
           }  
           swap(leftPtr, right-1);      
           return leftPtr;              
        }  
                                
       public void insertionSort(int left, int right)
       {
          int in, out;
          long temp;                         
          for(out=left+1;out<=right;out++)
          {
              temp = theArray[out];
              for(in=out-1;in>=0;in--)
              {
                  if(theArray[in]>temp)
                      theArray[in+1] = theArray[in];
                  else
                      break;
              }
              theArray[in+1] = temp;
          }
       } 
    } 
    
    class QuickSort3App
    {
       public static void main(String[] args)
       {
          int maxSize = 16;             
          ArrayIns arr;                
          arr = new ArrayIns(maxSize);  
    
          for(int j=0; j<maxSize; j++)  
          {                         
             long n = (int)(java.lang.Math.random()*99);
             arr.insert(n);
          }
          /*arr.insert(10);      
          arr.insert(10);      
          arr.insert(10);
          arr.insert(10);
          arr.insert(10);
          arr.insert(10); */     
          arr.display();             
          arr.quickSort();       
          arr.display();            
       }  
    } 

    4、基数排序

    假设基数排序都是普通的以10为基数的运算。

    1、根据数据项个位上的值,把所有的数据项分为10组。

    2、然后对这10组数据项重新排列:把所有关键字是以0结尾的数据项排在最前面,然后是关键字结尾是1的数据项,照此顺序直到以9结尾的数据项,这个步骤被称为第一趟子排序。

    3、在第二趟子排序中,再次把所有的数据项分为10组,但是这一次是根据数据项十位上的值来分组的。这次分组不能改变先前的排序顺序。也就是说,第二趟排序之后,从每一组数据项的内部来看,数据项的顺序保持不变。

    4、然后再把10组数据项重新合并,排在最前面的是十位上为0的数据项,然后是十位为1的数据项,如此排序直到十位上为9的数据项。

    5、对剩余位重复这个过程。如果某些数据项的位数少于其他数据项,那么认为它们的高位为0。

    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)

    035  124  240  305  421  430  532

     在实际应用中,个位,十位,百位...每一位数值的个数不可能完全相同,因此很难确定数组的大小。解决这个问题的一种方法是使用十个链表,而不用十个数组。链表可以根据需要扩展或收缩。

    外层循环一次查看关键字的每一位。有两个内部循环,第一个循环从数组中取得数据项放到链表中;第二个循环则把数据项从链表复制回数组。为了保证子排序的稳定,必须保证数据出链表的顺序与进链表的顺序相同。

    总结:

    •希尔排序将增量应用到插入排序,然后逐渐缩小增量。

    •n-增量排序表示每隔n个元素进行排序。

    •被称为间隔序列或者间距序列的数列决定了希尔排序的排序间隔。

    •常用的间隔序列是由递归表达式h=3*h+1生成的,h的初始值为1。

    •一个容纳了1000个数据项的数组,对它进行希尔排序可以是间隔序列为361,121,40,13,4,最后是1的增量排序。

    •希尔排序很难分析,运行的时间复杂度大约为O(N*(logN)2)。这比时间复杂度为O(N2)的排序算法要快,例如比插入排序快,但是比时间复杂度为O(N*logN)的算法慢,例如比快速排序慢。

    •划分数组就是把数组分为两个子数组,在一组中所有的数据项关键字的值都小于指定的值,而在另一组中所有数据项关键字的值则大于或等于给定值。

    •枢纽是在划分的过程中确定数据项应该放在哪一组的值。小于枢纽的数据项都放在左边一组,而大于枢纽的数据项都放在右边一组。

    •划分操作有线性时间复杂度O(N),做N+1或N+2次的比较以及少于N/2此的交换。

    •快速排序算法划分时的枢纽是一个特定数据项关键字的值,这个数据项称为pivot枢纽。

    •在快速排序的简单版本中,总是由子数组的最右端的数据项作为枢纽。

    •划分的过程中枢纽总是放在被划分子数组的右界,它不包含在划分过程中。

    •划分之后枢纽也换位,被放在两个划分子数组之间。这就是枢纽的最终排序位置。

    •快速排序的简单版本,对已经有序或逆序的数据项排序的执行效率只有O(N2)。

    •更高级的快速排序版本中,枢纽是子数组中第一个,最后一个以及中间一个数据项的中值。这称为"三数据项取中"划分。

    •三数据项取中划分有效地解决了对已有序数据项排序时执行效率仅是O(N2)的问题。

    •在三数据项取中划分中,在对左端,中间以及右端的数据项取中值的同时对它们进行排序。

    •快速排序算法的时间复杂度为O(N*logN)(除了用简单的快速排序版本对已有序数据项排序的情况)。

    •子数组小于一定的容量时用另一种方法来排序,而不用快速排序。

    •通常用插入排序对小于切割界限的子数组排序。

    •在快速排序已经对大于切割界限的子数组排完序之后,插入排序也可用于整个的数组。

    •基数排序的时间复杂度和快速排序相同,只是它需要两倍的存储空间。

  • 相关阅读:
    Laravel 学习笔记之文件上传
    Composer学习
    Laravel 学习笔记之数据库操作——Eloquent ORM
    PHP至Document类操作 xml 文件
    使用promise构建一个向服务器异步数据请求
    遍历DOM树
    关于tp验证码模块
    layui 封装自定义模块
    js进阶之路,关于UI资源的优化(转载)
    关于js 重载
  • 原文地址:https://www.cnblogs.com/xxlong/p/4992805.html
Copyright © 2011-2022 走看看