zoukankan      html  css  js  c++  java
  • 八大排序方法及对Arrays类的排序实现探讨

    1.插入排序—直接插入排序(Straight Insertion Sort)

    基本思想:

    将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。

    要点:设立哨兵,作为临时存储和判断数组边界之用。

    如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。

    效率:

    时间复杂度:O(n^2).

    其他的插入排序有二分插入排序,2-路插入排序。

    代码如下

     2. 插入排序—希尔排序(Shell`s Sort)

            基本思想:

    • 算法先将要排序的一组数按某个增量d(n/2,n为要排序数的个数)分成若干组,每组中记录的下标相差d.对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。当增量减到1时,进行直接插入排序后,排序完成。

    • 希尔排序法(缩小增量法) 属于插入类排序,是将整个无序列分割成若干小的子序列分别进行插入排序的方法。

    • 希尔排序方法是一个不稳定的排序方法。
    • 代码如下

    • 3. 选择排序—简单选择排序(Simple Selection Sort)

    • 基本思想:

      在要排序的一组数中,选出最小(或者最大)的个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后个数)比较为止。

    • 代码如下

    • 简单选择排序,每趟循环只能确定一个元素排序后的定位。我们可以考虑改进为每趟循环确定两个元素(当前趟最大和最小记录)的位置,从而减少排序所需的循环次数。改进后对n个数据进行排序,最多只需进行[n/2]趟循环即可。
    • 4.选择排序—堆排序(Heap Sort)

    • 堆排序是一种树形选择排序,是对直接选择排序的有效改进。

      基本思想:

      堆的定义如下:具有n个元素的序列(k1,k2,...,kn),当且仅当满足


      时称之为堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最小项(小顶堆)。
      若以一维数组存储一个堆,则堆对应一棵完全二叉树,且所有非叶结点的值均不大于(或不小于)其子女的值,根结点(堆顶元素)的值是最小(或最大)的。如:

      (a)大顶堆序列:(96, 83,27,38,11,09)

        (b)  小顶堆序列:(12,36,24,85,47,30,53,91)

       

      初始时把要排序的n个数的序列看作是一棵顺序存储的二叉树(一维数组存储二叉树),调整它们的存储序,使之成为一个堆,将堆顶元素输出,得到n 个元素中最小(或最大)的元素,这时堆的根节点的数最小(或者最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n 个元素中次小(或次大)的元素。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。称这个过程为堆排序

      因此,实现堆排序需解决两个问题:
      1. 如何将n 个待排序的数建成堆;
      2. 输出堆顶元素后,怎样调整剩余n-1 个元素,使其成为一个新堆。


      首先讨论第二个问题:输出堆顶元素后,对剩余n-1元素重新建成堆的调整过程。
      调整小顶堆的方法:

      1)设有m 个元素的堆,输出堆顶元素后,剩下m-1 个元素。将堆底元素送入堆顶((最后一个元素与堆顶进行交换),堆被破坏,其原因仅是根结点不满足堆的性质。

      2)将根结点与左、右子树中较小元素的进行交换。

      3)若与左子树交换:如果左子树堆被破坏,即左子树的根结点不满足堆的性质,则重复方法 (2).

      4)若与右子树交换,如果右子树堆被破坏,即右子树的根结点不满足堆的性质。则重复方法 (2).

      5)继续对不满足堆性质的子树进行上述交换操作,直到叶子结点,堆被建成。

      称这个自根结点到叶子结点的调整过程为筛选。如图:


      再讨论对n 个元素初始建堆的过程。
      建堆方法:对初始序列建堆的过程,就是一个反复进行筛选的过程。

      1)n 个结点的完全二叉树,则最后一个结点是第个结点的子树。

      2)筛选从第个结点为根的子树开始,该子树成为堆。

      3)之后向前依次对各结点为根的子树进行筛选,使之成为堆,直到根结点。

      如图建堆初始过程:无序序列:(49,38,65,97,76,13,27,49)
                                    


                                    

    • 代码如下

    •  

      package sort;
      /**
       * 作者  super_xueyi
       * 日期:2017年8月31日下午6:42:27
       * 描述:HeapSort
       */
      public class HeapSort {
          public static int[] heapSort(int[] A, int n) {
              //堆排序算法
              int i;
              //先把A[]数组构建成一个大顶堆。
              //从完全二叉树的最下层最右边的非终端结点开始构建。
              for(i=n/2-1;i>=0;i--){
                  HeapAdjust(A,i,n);
              }
              //开始遍历
              for(i=n-1;i>0;i--){
                  swap(A,0,i);
                  //每交换一次得到一个最大值然后丢弃
                  HeapAdjust(A,0,i);
              }
              return A;
          }
          //A[i]代表的是下标为i的根结点
          private static void HeapAdjust(int[] A,int i,int n){
              //【注意】这里下标从0开始
              int temp;
              //存储根结点
              temp = A[i];
              //沿根结点的左右孩子中较大的往下遍历,由于完全二叉树特性 i的左子节点2i+1  i的右子节点2i+2
              for(int j=2*i+1;j<n;j=j*2+1){
                  if(j<n-1&&A[j]<A[j+1]){
                      ++j;
                  }
                  if(temp>=A[j]){
                      break;
                  }
                  //将子节点赋值给根结点
                  A[i] = A[j];
                  //将子节点下标赋给i
                  i = j;
              }
              //将存储的根结点的值赋给子节点
              A[i] = temp;
          }
          private static void swap(int[] A,int i,int j){
              int temp = A[i];
              A[i]=A[j];
              A[j] = temp;
          }
      }

      5. 归并排序(Merge Sort)

    • 基本思想
    • 归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
      归并操作的工作原理如下:
      第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
      第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置
      第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
      重复步骤3直到某一指针超出序列尾
      将另一序列剩下的所有元素直接复制到合并序列尾
    • 代码如下

    • package sort;
      
      /**
       * 作者  super_xueyi
       * 日期:2017年8月31日下午6:48:49
       * 描述:MergeSort
       */
      public class MergeSort {
          public static int[] mergeSort(int[] A, int n) {
             //归并排序,递归做法,分而治之
      
              mSort(A,0,n-1);
              return A;
          }
      
          private static void mSort(int[] A,int left,int right){
              //分而治之,递归常用的思想,跳出递归的条件
              if(left>=right){
                  return;
              }
              //中点
              int mid = (left+right)/2;
              //有点类似后序遍历!
              mSort(A,left,mid);
              mSort(A,mid+1,right);
      
              merge(A,left,mid,right);
      
      
      
          }
      
          //将左右俩组的按序子序列排列成按序序列
          private static void merge(int[] A,int left,int mid,int rightEnd){
              //充当tem数组的下标
              int record = left;
              //最后复制数组时使用
              int record2 = left;
              //右子序列的开始下标
              int m =mid+1;
              int[] tem = new int[A.length];
      
              //只要left>mid或是m>rightEnd,就跳出循环
              while(left<=mid&&m<=rightEnd){
      
                  if(A[left]<=A[m]){
      
                      tem[record++]=A[left++];
                  }else{
                      tem[record++]=A[m++];
                  }
      
              }
              while(left<=mid){
                  tem[record++]=A[left++];
              }
              while(m<=rightEnd){
                  tem[record++]=A[m++];
              }
              //复制数组
              for( ;record2<=rightEnd;record2++){
                  A[record2] = tem[record2];
              }
      
          }
      
      }
      View Code

      6. 交换排序—冒泡排序(Bubble Sort)

    • 基本思想
    • 冒泡排序,顾名思义,从下往上遍历,每次遍历往上固定一个最小值
    • 加一个标志位,当某一趟冒泡排序没有元素交换时,则冒泡结束,元素已经有序,可以有效的减少冒泡次数。
    • 代码如下

      7. 交换排序—快速排序(Quick Sort)

      基本思想

      快速排序(Quicksort)是对冒泡排序的一种改进,使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。

    • 从数列中挑出一个元素,称为”枢轴”(pivot)。
    • 重新排序数列,所有元素比枢轴值小的摆放在基准前面,所有元素比枢轴值大的摆在枢轴的后面(相同的数可以到任一边)。在这个分区结束之后,该枢轴就处于数列的中间位置。这个称为分区(partition)操作。
    • 递归地(recursive)把小于枢轴值元素的子数列和大于枢轴值元素的子数列排序。
    • 代码如下

    • package sort;
      
      /**
       * 作者  super_xueyi
       * 日期:2017年8月31日下午6:56:03
       * 描述:QuickSort
       */
      public class QuickSort {
          public static int[] quickSort(int[] A, int n) {
              //快速排序
      
              qSort(A,0,n-1);
      
              return A;
      
          }
          public static void qSort(int[] A,int left,int right){
      
               //枢轴
              int pivot;
              if(left<right){
      
                  pivot = partition(A,left,right);
      
                  qSort(A,left,pivot-1);
                  qSort(A,pivot+1,right);
      
              }      
      
          }
      
          //优化选取一个枢轴,想尽办法把它放到一个位置,使它左边的值都比它小,右边的值都比它大
          public static int partition(int[] A,int left,int right){
      
              //优化选取枢轴,采用三数取中的方法
              int pivotKey = median3(A,left,right);
              //从表的俩边交替向中间扫描
              //枢轴用pivotKey给备份了
              while(left<right){
                 while(left<right&&A[right]>=pivotKey){
                     right--;
                 }
                  //用替换方式,因为枢轴给备份了,多出一个存储空间
                  A[left]=A[right];
                 while(left<right&&A[left]<=pivotKey){
                     left++;
                 }
                 A[right]=A[left];
      
              }
      
              //把枢轴放到它真正的地方
              A[left]=pivotKey;
              return left;
          }
          //三数取中
          public static int median3(int[] A,int left,int right){
      
              int mid=(right-left)/2;
              if(A[left]>A[right]){
                  swap(A,left,right);
              }
              if(A[mid]>A[left]){
                  swap(A,mid,left);
              }
              if(A[mid]>A[right]){
                  swap(A,mid,right);
              }
      
              return A[left];
          }
      
          public  static void swap(int[] A,int i,int j){
              int temp =A[i];
              A[i]=A[j];
              A[j]=temp;
          }
      }

      8. 桶排序/基数排序(Radix Sort)

    • 基本思想

    • 设置一个定量的数组当作空桶子。
    • 寻访序列,并且把项目一个一个放到对应的桶子去。
    • 对每个不是空的桶子进行排序。
    • 从不是空的桶子里把项目再放回原来的序列中
    • 代码如下

     通过Arrays类的源代码可以发现其调用的DualPivotQuicksort类的sort方法

    查看DualPivotQuicksort类的源代码发现在带排序数组长度小于一个值时使用插入排序,查看常量INSERTION_SORT_THRESHOLD,

    发现其为47。

     同样的方法查看快速排序的最佳数组长度为286

    并且发现当数组基本有序时不使用归并排序

     在数组长度太大是使用

    计数排序Counting sort,长度为3200以上时

  • 相关阅读:
    公共服务领域英文译写规范
    [.NET Core]
    [WebAPI]
    [C#]
    [ES]
    [.Net Core]
    [SourceTree]
    如何使用一个库中不存在的函数
    在使用masm32 rc编译资源文件.rc出现的问题
    MSDN 2005 安装问题
  • 原文地址:https://www.cnblogs.com/java-ssl-xy/p/7455128.html
Copyright © 2011-2022 走看看