八大排序方法及对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以上时

 

 

posted @ 2017-08-30 20:20  super_xueyi  阅读(403)  评论(0编辑  收藏  举报