10种排序算法- 归纳总结

Posted tootooquan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了10种排序算法- 归纳总结相关的知识,希望对你有一定的参考价值。

交换

冒泡排序

基本思想:简单地遍历,如果发现相邻两个元素的顺序错了,就交换过来。
对0到N-1个数据遍历一遍后,能将最大的一个数据“沉”到数组的第N-1位。

  • 时间复杂度
    冒泡排序平均时间复杂度为O(n2),最好时间复杂度为O(n),最坏时间复杂度为O(n2)。
  • 最好情况:如果待排序元素本来是正序的,那么一趟冒泡排序就可以完成排序工作,比较和移动元素的次数分别是 (n - 1) 和 0,因此最好情况的时间复杂度为O(n)。
  • 最坏情况:如果待排序元素本来是逆序的,需要进行 (n - 1) 趟排序,所需比较和移动次数分别为 n * (n - 1) / 2和 3 * n * (n-1) / 2。因此最坏情况下的时间复杂度为O(n2)。
public class Sort_algorithms 
    /*
    冒泡排序 - 最简单的写法,无优化
     */
    public static void bubbleSort(int[] nums)
        int len = nums.length;
        //两层循环,外层循环表示当前获取第几个最大的数
        for(int i = 0; i < len; i++)//i表示是第几轮排序
            for(int j = 0; j < len - 1 - i; j++)//len-i之后的元素已经排好序了
                if(nums[j] > nums[j + 1])
                    int tmp = nums[j];
                    nums[j] = nums[j + 1];
                    nums[j + 1] = tmp;
                
            
        
    

    public static void bubbleSortOptimization1(int[] nums)
        //优化1 设置一个标志flag,如果某一趟发生交换则为true,否则为false
        //如果明显没有发生交换,说明排序已完成,就不进行后续的轮数了
        int len = nums.length;
        for (int i = 0; i < len; i++)
            boolean swaped = false;
            for (int j = 0; j < len - i - 1; j++)
                if(nums[j] > nums[j+1])
                    int tmp = nums[j];
                    nums[j] = nums[j+1];
                    nums[j+1] = tmp;
                    swaped = true;
                
            
            if(!swaped) break;//说明没发生过交换
        
    

    public static void main(String[] args)
        int[] nums = 1,1,2,0,9,3,12,7,8;
        Sort_algorithms sort = new Sort_algorithms();
        sort.bubbleSortOptimization1(nums);
        for(int i:nums)
            System.out.print(i + " ");
        

    

快速排序

基本思想:使用分治法,通过一次排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均小于等于所选基准数,另一部分均大于等于所选基准数。然后分别对这两部分记录继续进行排序,至整个序列有序。

快速排序之所比较快,因为相比冒泡排序,每次交换是跳跃式的。每次排序的时候设置一个基准点,将小于等于基准点的数全部放到基准点的左边,将大于等于基准点的数全部放到基准点的右边。这样在每次交换的时候就不会像冒泡排序一样每次只能在相邻的数之间进行交换,交换的距离就大的多了。因此总的比较和交换次数就少了,速度自然就提高了。当然在最坏的情况下,仍可能是相邻的两个数进行了交换。因此快速排序的最差时间复杂度和冒泡排序是一样的都是 O(N2),它的平均时间复杂度为 O(NlogN)。其实快速排序是基于一种叫做“二分”的思想。

下面的代码是双指针法实现partition的过程。
需要注意选基准的时候,如果选数组的最左边,要先从右边开始执行;如果选数组的最右边,要先从左边开始执行。

  • 主要的区别在于最后执行基点与指针交换的操作。基点定于左侧,若先从左边开始查找,可能会导致找到一个大于的数,而指针相遇了,此时与基点位置坐交换会把一个大于基点的数交换至左侧。
    public static void swap(int[] nums, int i, int j)
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    

    //分区部分是快排最重要的部分,采用左右指针法
    public static int quickSortPartition1(int[] nums, int left, int right)
        //基准选谁?如果选左边,就需要右边的先执行;如果选右边,就需要左边的先执行
        int key = left;
        int l = left;
        int r = right;
        while(l < r)
            //l找比基准大的
            //先从右边开始查找,是防止先从左边开始查找,可能会导致找到一个大于的数,而指针相遇了,此时与基点位置坐交换会把一个大于基点的数交换至左侧。
            while(r > l && nums[r] >= nums[key])
                r--;
            
            while(l < r && nums[l] <= nums[key])
                l++;
            
            swap(nums, l, r);
        
        swap(nums, l, key);
        return l; //l是基准现在的位置
    
    

    public static void quickSort(int[] nums, int left, int right)
        if(left < right)
            int key = quickSortPartition1(nums, left, right);
            quickSort(nums, left, key - 1);
            quickSort(nums, key + 1, right);
        
    
    
    public static void main(String[] args)
        int[] nums = 1,1,2,0,9,3,12,7,8;
        Sort_algorithms.quickSort(nums,0, nums.length-1);
        for(int i:nums)
            System.out.print(i + " ");
        

    

常见快排优化

之前选择基准的策略都是固定基准,即固定地选择序列的右边界值(或左边界值)作为基准,但如果在待排序列几乎有序的情况下,选择的固定基准将是序列的最大(小)值,快排的性能不好(因为每趟排序后,左右两个子序列规模相差悬殊,大的那部分最后时间复杂度很可能会达到O(n2))。

  • 1.随机基准。每次随机选取基准值,而不是固定选取左或右边界值。使用random
int random = (int) (left + Math.random() * (right - left + 1));
//Math.random() 函数返回一个浮点数, 伪随机数在范围从0到小于1
//随机选择 left ~ right 之间的一个位置作为基准
swap(array, random, right);
//把基准值交换到右边界
  • 2.三数取中法。队头、队尾、队中三个数,取中间值。
    基本思想:
    取第一个数,最后一个数,第(N/2)个数即中间数,三个数中数值中间的那个数作为基准值。
    举个例子,对于int[] array = 2,5,4,9,3,6,8,7,1,0,2、3、0分别是第一个数,第(N/2)个是数以及最后一个数,三个数中3最大,0最小,2在中间,所以取2为基准值。

  • 3.当待排序序列的长度分割到一定大小后,使用插入排序。
    在子序列比较小的时候,直接插入排序性能较好,因为对于有序的序列,插排可以达到O(n)的复杂度,如果序列比较小,使用插排效率要比快排高。可以设置一个阈值n,之后使用插排。
    假设为5,则大于5个元素,子序列继续递归,否则选用插排。
    此时QuickSort()函数如下:

public static void Quicksort(int array[], int left, int right) 
        if(right - left > 5)
            int pos = partition(array, left, right);
            Quicksort(array, left, pos - 1);
            Quicksort(array, pos + 1, right);
        else
            insertionSort(array);
        
    
  • 4.三路划分
    如果待排序列中重复元素过多,也会大大影响排序的性能,这是因为大量相同元素参与快排时,左右序列规模相差极大,快排将退化为冒泡排序,时间复杂度接近O(n2)。这时候,如果采用三路划分,则会很好的避免这个问题。
    三路划分的思想是利用 partition 函数将待排序列划分为三部分:第一部分小于基准v,第二部分等于基准v,第三部分大于基准v。这样在递归排序区间的时候,我们就不必再对第二部分元素均相等的区间进行快排了,这在待排序列存在大量相同元素的情况下能大大提高快排效率。

//三路快排:将区域分为小于基准,等于基准,和大于基准三个部分
    public static int[] quickSortPartition3(int[] nums, int left, int right)
        int v = nums[left]; //选择左边界为基准
        int less = left - 1; // < v 部分的最后一个数,因此=v的第一个数是less + 1
        int more = right + 1; // > v 部分的第一个数,因此=v的第一个数是more - 1
        int cur = left;
        while(cur < more)
            if(nums[cur] < v)
                swap(nums,++less,cur);
                cur++;//这里可以向前移动,是因为less+1处是=v的?
            else if(nums[cur] > v)
                swap(nums,--more,cur);//此时cur指针不能前移,这是因为cur是从头至尾遍历,交换到cur位置的元素来自未知区域,还需要进一步判断array[cur]。
            else
                cur++;
            
        
        // i走向more处,空间已经分区完毕
        return new int[]less + 1,more - 1;  //返回的是 = v 区域的左右下标
    

    public static void quickSort3(int[] nums, int left, int right)
        if(left < right)
            int[] p = quickSortPartition3(nums, left, right);
            quickSort(nums, left, p[0] - 1); //避开重复元素区间
            quickSort(nums, p[1] + 1, right);
        
    

插入

直接插入排序

1.原理:每次插入新元素到整齐的区间。通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
2.算法描述
一般来说,直接插入排序都采用in-place(原地算法)在数组上实现。具体算法描述如下:
1)从第一个元素开始,该元素可以认为已经被排序;
2)取出下一个元素,在已经排序的元素序列中从后向前扫描;
3)如果该元素(已排序)大于新元素,将该元素移到下一位置;
4)重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
5)将新元素插入到该位置后;
6)重复步骤2~5。
3.直接插入排序平均时间复杂度为O(n2),最好时间复杂度为O(n),最坏时间复杂度为O(n2)。可以加入二分法进行优化。

//直接插入排序
    public static void insertionSort(int[] nums)
        int len = nums.length;
        //下标为0的元素算已经排好序了
        for(int i = 1; i < len; i++)
            for(int j = i; j > 0; j--)
                if(nums[j]<nums[j-1])
                    int tmp = nums[j];
                    nums[j] = nums[j-1];
                    nums[j-1] = tmp;
                
            
        
    

希尔排序

第一个突破O(n2)的排序算法,是直接插入排序的改进版。它与直接插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序

在此我们选择增量gap=length/2,缩小增量继续以gap = gap/2的方式,这种增量选择我们可以用一个序列来表示,n/2,(n/2)/2...1,称为增量序列。希尔排序的增量序列的选择与证明是个数学难题,我们选择的这个增量序列是比较常用的,也是希尔建议的增量,称为希尔增量,但其实这个增量序列不是最优的。

 public static int[] ShellSort(int[] array) 
        int len = array.length;
        if(len == 0)
            return array;
        int current, gap = len / 2;
        while (gap > 0) 
            for (int i = gap; i < len; i++) 
                current = array[i];
                int preIndex = i - gap;//同组前一个
                while (preIndex >= 0 && array[preIndex] > current) 
                    array[preIndex + gap] = array[preIndex];//如果前面比同组的后面大,就把前面的值给后面
                    preIndex -= gap;
                
                array[preIndex + gap] = current;//找到最终的位置了,回退赋值
            
            gap /= 2;
        
        return array;
    

选择

简单选择排序

工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

//简单选择排序
    public static void selectionSort(int[] nums)
        int len = nums.length;
        for(int i = 0; i < len-1; i++)//一共进行len-1次选择
            int minIndex = i;
            for(int j = i+1; j < len ;j++)//从i开始之后是无序的
                if(nums[j]<nums[minIndex])
                    minIndex = j;
                
            
            //找到了这一轮中最小的,进行位置的放置,将最小数和无序区的第一个数交换
            //第一轮的无序区下标为0
            int tmp = nums[i];
            nums[i] = nums[minIndex];
            nums[minIndex] = tmp;
        
    

堆排序

堆就是用数组实现的完全二叉树。普通树占用的内存空间比它们存储的数据要多。你必须为节点对象以及左/右子节点指针分配内存。堆仅仅使用一个数据来存储数组,且不使用指针。

堆的常用方法:

  • 构建优先队列
  • 支持堆排序
  • 快速找出一个集合中的最小值(或者最大值)

堆分为两种:最大堆(大根堆)和最小堆(小根堆),两者的差别在于节点的排序方式。

在最大堆中,父节点的值比每一个子节点的值都要大。在最小堆中,父节点的值比每一个子节点的值都要小。这就是所谓的“堆属性”,并且这个属性对堆中的每一个节点都成立。

结点 i 如果存在左孩子(下标不超过 len - 1 就存在),左孩子的下标为(2 * i + 1);如果存在右孩子,右孩子的下标为(2 * i + 2)。结点 i 的父结点下标为 (i - 1) / 2 (下标为 0 的结点除外,它没有父结点)。最后一个非叶子结点即为堆尾元素的父结点,下标为 (len - 1 - 1) / 2 = (len - 2) / 2。

堆排序的思想:
堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。

步骤一:构造初始堆。

代码的核心:将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。

    //堆排序
    //1.构建最大堆
    //2.将树顶元素和树尾的进行交换,重构前面的n-1个元素构建最大堆(共n-1次)
    //3.重复2直至所有元素有序

    public static void heapSort(int[] nums)
        int len = nums.length;
        //构建最大堆
        buildMaxHeap(nums);

        //循环将堆顶与堆尾交换,删除堆尾,重新调整大根堆
        while (len > 0) 
            swap(nums, 0, len - 1);
            len--; //原先的堆尾进入有序区,删除堆尾元素
            adjustHeap(nums, 0, len); //重新调整大根堆
        

    
    public static void adjustHeap(int[] nums, int i, int len)
        int maxIndex = i;
        //如果有左子树,且左子树大于父节点i,就把最大指针指向左子树
        if(2*i + 1 < len && nums[2*i + 1]>nums[maxIndex])
            maxIndex = 2*i + 1;
        
        //如果有右子树,且右子树大于父节点i,就把最大指针指向右子树
        if(2*i + 2 < len && nums[2*i + 2]>nums[maxIndex])
            maxIndex = 2*i + 2;
        
        //如果父节点不是最大值,则将父节点与最大值交换,并且递归调整与父节点交换的位置。
        if (maxIndex != i) 
            swap(nums, maxIndex, i);
            adjustHeap(nums, maxIndex,len);
        
    
    public static void buildMaxHeap(int[] nums)
        //从最后一个非叶子节点开始自底向上构建大根堆
        for(int i = (nums.length-2)/2; i>=0; i--)
            //自顶向下调整以i为根的大根堆
            adjustHeap(nums, i, nums.length);
        
    


    public static void main(String[] args)
        int[] nums = 1,1,2,0,2,8;
        Sort_algorithms.heapSort(nums);
        for(int i:nums)
            System.out.print(i + " ");
        

    

归并排序

1.原理
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
2.算法描述
1)把长度为 n 的输入序列分成两个长度为 n / 2 的子序列;
2)对这两个子序列分别采用归并排序;
3)将两个排序好的子序列合并成一个最终的排序序列。
3.时间复杂度
归并排序的形式就是一棵二叉树,它需要遍历的次数就是二叉树的深度,而根据完全二叉树的可以得出它在任何情况下时间复杂度均是O(nlogn)。
4.空间复杂度为O(n)

-使用递归的方法实现归并排序:

//把两个有序数组合并
    public static int[] merge(int[] left, int[] right)
        int[] result = new int[left.length +right.length];
        int index = 0, i = 0, j = 0;
        while (index < result.length && i < left.length && j < right.length)
            result[index++] = left[i] < right[j] ? left[i++]:right[j++];
        
        while (i < left.length)
            result[index++] = left[i++];
        
        while (j < right.length)
            result[index++] = right[j++];
        
        return result;
    
    //归并排序
    public static int[] mergeSort(int[] nums)
        int len = nums.length;
        int mid = len / 2;
        int[] left = Arrays.copyOfRange(nums, 0, mid);
        int[] right = Arrays.copyOfRange(nums, mid, len);
        return merge(mergeSort(left),mergeSort(right));

    

使用非递归实现归并排序:

非递归版的归并排序,省略了中间的栈空间,直接申请一段O(n)的地址空间即可,因此空间复杂度为O(n),时间复杂度为O(nlogn);
与递归版排序相反,它是先以间隔为1两两进行归并,再以间隔为2进行归并...直到2k超过数组长度为止

桶思想

计数排序

计数排序不是一个比较排序算法,该算法于1954年由 Harold H. Seward提出,通过计数将时间复杂度降到了O(N)。

基础版算法步骤
第一步:找出原数组中元素值最大的,记为max。

第二步:创建一个新数组count,其长度是max加1,其元素默认值都为0。

第三步:遍历原数组中的元素,以原数组中的元素作为count数组的索引,以原数组中的元素出现次数作为count数组的元素值。

第四步:创建结果数组result,起始索引index。

第五步:遍历count数组,找出其中元素值大于0的元素,将其对应的索引作为元素值填充到result数组中去,每处理一次,count中的该元素值减1,直到该元素值不大于0,依次处理count中剩下的元素。

第六步:返回结果数组result。


public int[] countSort(int[] A) 
    // 找出数组A中的最大值
    int max = Integer.MIN_VALUE;
    for (int num : A) 
        max = Math.max(max, num);
    
    // 初始化计数数组count
    int[] count = new int[max+1];
    // 对计数数组各元素赋值
    for (int num : A) 
        count[num]++;
    
    // 创建结果数组
    int[] result = new int[A.length];
    // 创建结果数组的起始索引
    int index = 0;
    // 遍历计数数组,将计数数组的索引填充到结果数组中
    for (int i=0; i<count.length; i++) 
        while (count[i]>0) 
            result[index++] = i;
            count[i]--;
        
    
    // 返回结果数组
    return result;


优化版
基础版能够解决一般的情况,但是它有一个缺陷,那就是存在空间浪费的问题。

比如一组数据101,109,108,102,110,107,103,其中最大值为110,按照基础版的思路,我们需要创建一个长度为111的计数数组,但是我们可以发现,它前面的[0,100]的空间完全浪费了,那怎样优化呢?

数组长度定为max-min+1,即不仅要找出最大值,还要找出最小值,根据两者的差来确定计数数组的长度。

public int[] countSort2(int[] A) 
    // 找出数组A中的最大值、最小值
    int max = Integer.MIN_VALUE;
    int min = Integer.MAX_VALUE;
    for (int num : A) 
        max = Math.max(max, num);
        min = Math.min(min, num);
    
    // 初始化计数数组count
    // 长度为最大值减最小值加1
    int[] count = new int[max-min+1];
    // 对计数数组各元素赋值
    for (int num : A) 
        // A中的元素要减去最小值,再作为新索引
        count[num-min]++;
    
    // 创建结果数组
    int[] result = new int[A.length];
    // 创建结果数组的起始索引
    int index = 0;
    // 遍历计数数组,将计数数组的索引填充到结果数组中
    for (int i=0; i<count.length; i++) 
        while (count[i]>0) 
            // 再将减去的最小值补上
            result[index++] = i+min;
            count[i]--;
        
    
    // 返回结果数组
    return result;


桶排序

用通俗易懂的话来理解:

  • 将待排序的序列分到若干个桶中(按照映射函数将数据分配到不同的桶里),每个桶内的元素再进行个别排序。
  • 时间复杂度最好可能是线性O(n),桶排序不是基于比较的排序
  • 桶排序是一种用空间换取时间的排序。桶排序平均时间复杂度为O(n + k),最好时间复杂度为O(n + k),最坏时间复杂度为O(n2)。桶排序空间复杂度为O(n + k)。

在设计桶排序,需要知道输入数据的上界和下界,看看数据的分布情况,再考虑是否用桶排序,当然如果能用好桶排序,效率还是很高的!射函数人为设计,但要保证桶 i 中的数均小于桶 j (i < j)中的数,即必须桶间必须有序,桶内可以无序,可以考虑按照数的区间范围划分桶。

    /**
     * 桶排序
     * 1. 获取最大最小树,桶的空间
     * 2. num[i] -min 定位桶索引,添加当前数到桶中。
     * 3. 使用索引扫描每个桶位,更新到排序数组中
     * @param nums
     */
    public static void sort(int[] nums) 
        int len = nums.length;
        int min = Integer.MAX_VALUE;
        int max = Integer.MIN_VALUE;
        for (int num : nums) 
            min = Math.min(num, min);
            max = Math.max(num, max);
        
        ArrayList<Integer>[] buckets = new ArrayList[max-min +1];
        for (int i = 0; i < buckets.length; i++) 
            buckets[i] = new ArrayList<>();
        
        for (int i = 0; i < len; i++) 
            buckets[nums[i]-min].add(nums[i]);
        
        for (int i = 0,j = 0; i < buckets.length; i++) 
            List<Integer> curList = buckets[i];
            int index = 0;
            while (!curList.isEmpty() && index< curList.size())
                nums[j++] = curList.get(index++);
            
        
    

基数排序

一种非比较型整数排序算法。
① 将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。
② 从最低位开始,依次进行一次排序。
③ 这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
时间复杂度:O(k*N);空间复杂度:O(k + N);稳定性:稳定

    /**
     * 基数排序
     * 循环:
     * 1. 获取每个数的第i个位置的基数,放到桶中
     * 2. 遍历桶元素替换到排序数组中
     * 3. 递归循环直至最高位的基数
     * @param nums
     */
    public static void sort(int[] nums) 
        List<Integer>[] buckets = new ArrayList[10];
        for (int i = 0; i < buckets.length; i++) 
            buckets[i] = new ArrayList<>();
        
        int max = Integer.MIN_VALUE;
        for (int num : nums) 
            max = Math.max(max, num);
        
        int maxLen = String.valueOf(max).length();
        int mod = 10, div = 1;
        for (int i = 1; i <= maxLen; i++, mod*=10, div*=10) 
            for (int num : nums) 
                // 求当前数在位置i上的基数
                int index = (num%mod)/div;
                buckets[index].add(num);
            
            int index = 0;
            for (List<Integer> curList : buckets) 
                int curIndex = 0;
                while (curIndex < curList.size()) 
                    nums[index++] = curList.get(curIndex++);
                
                curList.clear();
            
        
    

JDK8中的Arrays.sort()算法

Arrays.sort并不是单一的排序,而是插入排序,快速排序,归并排序三种排序的组合

  • 数量非常小的情况下(就像上面说到的,少于47的),插入排序等可能会比快速排序更快。 所以数组少于47的会进入插入排序。
  • 快排数据越无序越快(加入随机化后基本不会退化),平均常数最小,不需要额外空间,不稳定排序。
  • 归排速度稳定,常数比快排略大,需要额外空间,稳定排序。

点进sort方法:

      // Use Quicksort on small arrays
      if (right - left < QUICKSORT_THRESHOLD) //QUICKSORT_THRESHOLD = 286
        sort(a, left, right, true);
        return;
      

数组一进来,会碰到第一个阀值QUICKSORT_THRESHOLD(286),注解上说,小过这个阀值的进入Quicksort (快速排序),其实并不全是,点进去sort(a, left, right, true);方法:

// Use insertion sort on tiny arrays
    if (length < INSERTION_SORT_THRESHOLD) 
        if (leftmost) 
        ......

点进去后我们看到第二个阀值INSERTION_SORT_THRESHOLD(47),如果元素少于47这个阀值,就用插入排序,往下看确实如此:

            /*
             * Traditional (without sentinel) insertion sort,
             * optimized for server VM, is used in case of
             * the leftmost part.
             */
            for (int i = left, j = i; i < right; j = ++i) 
                int ai = a[i + 1];
                while (ai < a[j]) 
                    a[j + 1] = a[j];
                    if (j-- == left) 
                        break;
                    
                
                a[j + 1] = ai;

至于大过INSERTION_SORT_THRESHOLD(47)的,用一种快速排序的方法:
1.从数列中挑出五个元素,称为 “基准”(pivot);
2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
3.递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

上述是少于阀值QUICKSORT_THRESHOLD(286)的两种情况,至于大于286的,它会进入归并排序(Merge Sort),但在此之前,它有个小动作:

    // Check if the array is nearly sorted
    for (int k = left; k < right; run[count] = k) 
        if (a[k] < a[k + 1])  // ascending
            while (++k <= right && a[k - 1] <= a[k]);
         else if (a[k] > a[k + 1])  // descending
            while (++k <= right && a[k - 1] >= a[k]);
            for (int lo = run[count] - 1, hi = k; ++lo < --hi; ) 
                int t = a[lo]; a[lo] = a[hi]; a[hi] = t;
            
         else  // equal
            for (int m = MAX_RUN_LENGTH; ++k <= right && a[k - 1] == a[k]; ) 
                if (--m == 0) 
                    sort(a, left, right, true);
                    return;
                
            
        

        /*
         * The array is not highly structured,
         * use Quicksort instead of merge sort.
         */
        if (++count == MAX_RUN_COUNT) 
            sort(a, left, right, true);
            return;
        
    

这里主要作用是看他数组具不具备结构:实际逻辑是分组排序,每降序为一个组,像1,9,8,7,6,8。9到6是降序,为一个组,然后把降序的一组排成升序:1,6,7,8,9,8。然后最后的8后面继续往后面找。。。

每遇到这样一个降序组,++count,当count大于MAX_RUN_COUNT(67),被判断为这个数组不具备结构(也就是这数据时而升时而降),然后送给之前的sort(里面的快速排序)的方法(The array is not highly structured,use Quicksort instead of merge sort.)。

如果count少于MAX_RUN_COUNT(67)的,说明这个数组还有点结构,就继续往下走下面的归并排序。

以上是关于10种排序算法- 归纳总结的主要内容,如果未能解决你的问题,请参考以下文章

最全排序算法原理解析java代码实现以及总结归纳

排序算法总结

常见排序算法总结

算法之经典排序算法小归纳

10种排序算法总结

10 大排序算法总结