算法中的快速排序 quicksort

Posted 大宇007

tags:

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

快速排序之所以比较快,是因为相比冒泡排序,每次交换是跳跃式的。每次排序的时候设置一个基准点,将小于等于基准点的数全部放到基准点的左边,将大于等于基准点的数全

部放到基准点的右边。这样在每次交换的时候就不会像冒泡排序一样只能在相邻的数之间进行交换,交换的距离就大得多了。因此总的比较和交换次数就少了,速度自然就提高了。

当然在最坏的情况下,仍可能是相邻的两个数进行了交换。因此快速排序的最差时间复杂度和冒泡排序是一样的,都是O(N2),它的平均时间复杂度为O (NlogN)。其实快速排序是

基于一种叫做“二分”的思想。

对比了一下java中的Arrays.sort()方法,在 一本书里面看到的简易版更容易理解,先上传一段代码

public class ListTest2
{
    private static int a[] = new int[100];
    
    private static int n = 25;
    
    public static void main(String[] args)
    {
        Random random = new Random(100);
        for (int i = 0; i < n; i++)
        {
            int nextInt = random.nextInt(100);
            System.out.print(nextInt + "-");
            a[i] = nextInt;
        }
        System.out.println();
        for (int k = 0; k < n; k++)
        {
            if (a[k] != 0)
            {
                System.out.print(a[k] + "*");
            }
        }
        System.out.println();
        quicksort(0, n);
        for (int k = 0; k < a.length; k++)
        {
            if (a[k] != 0)
            {
                System.out.print(a[k] + " ");
            }
        }
        System.out.println();
    }
    
    public static void quicksort(int left, int right)
    {
        System.out.println("print sorted int[]-----");
        int i, j, t, temp;
        if (left > right)
            return;
        temp = a[left];// temp中存为基数
        i = left;
        j = right;
        while (i != j)
        {
            // 顺序很重要,要先从右往左找
            while (a[j] >= temp && i < j)
                j--;
            // 再从左往右找
            while (a[i] <= temp && i < j)
                i++;
            // 交换两个数在数组中的位置
            if (i < j)// 当哨兵i和哨兵j没有相遇时
            {
                t = a[i];
                a[i] = a[j];
                a[j] = t;
            }
        }
        // 最终将基准书归位
        a[left] = a[i];
        a[i] = temp;
        
        quicksort(left, i - 1);// 继续处理左边的,递归规程
        quicksort(i + 1, right);// 继续处理右边的,递归过程
    }
}

 

快速排序由 C. A. R. Hoare(东尼·霍尔,Charles Antony Richard Hoare)在1960 年提出,之后又有许多人做了进一步的优化,

java 版本里面的快速排序相对复杂很多,里面还涉及了位运算,目前还没有具体学习,不过源码先贴出来,方便以后学习,如果有大神愿意解析一下的话,那是最好啦,好了,下面是java源码

package test1;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;

/**
 * @author dayu
 * @version 创建时间:2017年8月24日 上午9:25:40
 *          类说明
 */
public class ListTest
{
    /**
     * The maximum number of runs in merge sort.
     */
    private static final int MAX_RUN_COUNT = 67;
    
    /**
     * The maximum length of run in merge sort.
     */
    private static final int MAX_RUN_LENGTH = 33;
    
    /**
     * If the length of an array to be sorted is less than this
     * constant, Quicksort is used in preference to merge sort.
     */
    private static final int QUICKSORT_THRESHOLD = 286;
    
    /**
     * If the length of an array to be sorted is less than this
     * constant, insertion sort is used in preference to Quicksort.
     */
    private static final int INSERTION_SORT_THRESHOLD = 47;
    
    /**
     * If the length of a byte array to be sorted is greater than this
     * constant, counting sort is used in preference to insertion sort.
     */
    private static final int COUNTING_SORT_THRESHOLD_FOR_BYTE = 29;
    
    /**
     * If the length of a short or char array to be sorted is greater
     * than this constant, counting sort is used in preference to Quicksort.
     */
    private static final int COUNTING_SORT_THRESHOLD_FOR_SHORT_OR_CHAR = 3200;
    
    public static void main(String[] args)
    {
        int a[] = {2, 4, 1, 6, 4, 8, 5, 3, 9, 11, 42, 6, 7};
        sort(a, 0, a.length - 1, null, 0, 0);
        for (int i = 0; i < a.length; i++)
        {
            System.out.print(a[i]+((i==a.length-1)?"":"*"));
        }
    }
    
    public static void sort(int[] a, int left, int right, int[] work, int workBase, int workLen)
    {
        
        // Use Quicksort on small arrays
        if (right - left < QUICKSORT_THRESHOLD)
        {
            sort(a, left, right, true);
            return;
        }
        
        /*
         * Index run[i] is the start of i-th run
         * (ascending or descending sequence).
         */
        int[] run = new int[MAX_RUN_COUNT + 1];
        int count = 0;
        run[0] = left;
        
        // 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;
            }
        }
        
        // Check special cases
        // Implementation note: variable "right" is increased by 1.
        if (run[count] == right++)
        { // The last run contains one element
            run[++count] = right;
        }
        else if (count == 1)
        { // The array is already sorted
            return;
        }
        
        // Determine alternation base for merge
        byte odd = 0;
        for (int n = 1; (n <<= 1) < count; odd ^= 1)
            ;
        
        // Use or create temporary array b for merging
        int[] b; // temp array; alternates with a
        int ao, bo; // array offsets from ‘left‘
        int blen = right - left; // space needed for b
        if (work == null || workLen < blen || workBase + blen > work.length)
        {
            work = new int[blen];
            workBase = 0;
        }
        if (odd == 0)
        {
            System.arraycopy(a, left, work, workBase, blen);
            b = a;
            bo = 0;
            a = work;
            ao = workBase - left;
        }
        else
        {
            b = work;
            ao = 0;
            bo = workBase - left;
        }
        
        // Merging
        for (int last; count > 1; count = last)
        {
            for (int k = (last = 0) + 2; k <= count; k += 2)
            {
                int hi = run[k], mi = run[k - 1];
                for (int i = run[k - 2], p = i, q = mi; i < hi; ++i)
                {
                    if (q >= hi || p < mi && a[p + ao] <= a[q + ao])
                    {
                        b[i + bo] = a[p++ + ao];
                    }
                    else
                    {
                        b[i + bo] = a[q++ + ao];
                    }
                }
                run[++last] = hi;
            }
            if ((count & 1) != 0)
            {
                for (int i = right, lo = run[count - 1]; --i >= lo; b[i + bo] = a[i + ao])
                    ;
                run[++last] = right;
            }
            int[] t = a;
            a = b;
            b = t;
            int o = ao;
            ao = bo;
            bo = o;
        }
        
    }
    
    private static void sort(int[] a, int left, int right, boolean leftmost)
    {
        int length = right - left + 1;
        
        // Use insertion sort on tiny arrays
        if (length < INSERTION_SORT_THRESHOLD)
        {
            if (leftmost)
            {
                /*
                 * 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;
                }
            }
            else
            {
                /*
                 * Skip the longest ascending sequence.
                 */
                do
                {
                    if (left >= right)
                    {
                        return;
                    }
                } while (a[++left] >= a[left - 1]);
                
                /*
                 * Every element from adjoining part plays the role
                 * of sentinel, therefore this allows us to avoid the
                 * left range check on each iteration. Moreover, we use
                 * the more optimized algorithm, so called pair insertion
                 * sort, which is faster (in the context of Quicksort)
                 * than traditional implementation of insertion sort.
                 */
                for (int k = left; ++left <= right; k = ++left)
                {
                    int a1 = a[k], a2 = a[left];
                    
                    if (a1 < a2)
                    {
                        a2 = a1;
                        a1 = a[left];
                    }
                    while (a1 < a[--k])
                    {
                        a[k + 2] = a[k];
                    }
                    a[++k + 1] = a1;
                    
                    while (a2 < a[--k])
                    {
                        a[k + 1] = a[k];
                    }
                    a[k + 1] = a2;
                }
                int last = a[right];
                
                while (last < a[--right])
                {
                    a[right + 1] = a[right];
                }
                a[right + 1] = last;
            }
            return;
        }
        
        // Inexpensive approximation of length / 7
        int seventh = (length >> 3) + (length >> 6) + 1;
        
        /*
         * Sort five evenly spaced elements around (and including) the
         * center element in the range. These elements will be used for
         * pivot selection as described below. The choice for spacing
         * these elements was empirically determined to work well on
         * a wide variety of inputs.
         */
        int e3 = (left + right) >>> 1; // The midpoint
        int e2 = e3 - seventh;
        int e1 = e2 - seventh;
        int e4 = e3 + seventh;
        int e5 = e4 + seventh;
        
        // Sort these elements using insertion sort
        if (a[e2] < a[e1])
        {
            int t = a[e2];
            a[e2] = a[e1];
            a[e1] = t;
        }
        
        if (a[e3] < a[e2])
        {
            int t = a[e3];
            a[e3] = a[e2];
            a[e2] = t;
            if (t < a[e1])
            {
                a[e2] = a[e1];
                a[e1] = t;
            }
        }
        if (a[e4] < a[e3])
        {
            int t = a[e4];
            a[e4] = a[e3];
            a[e3] = t;
            if (t < a[e2])
            {
                a[e3] = a[e2];
                a[e2] = t;
                if (t < a[e1])
                {
                    a[e2] = a[e1];
                    a[e1] = t;
                }
            }
        }
        if (a[e5] < a[e4])
        {
            int t = a[e5];
            a[e5] = a[e4];
            a[e4] = t;
            if (t < a[e3])
            {
                a[e4] = a[e3];
                a[e3] = t;
                if (t < a[e2])
                {
                    a[e3] = a[e2];
                    a[e2] = t;
                    if (t < a[e1])
                    {
                        a[e2] = a[e1];
                        a[e1] = t;
                    }
                }
            }
        }
        
        // Pointers
        int less = left; // The index of the first element of center part
        int great = right; // The index before the first element of right part
        
        if (a[e1] != a[e2] && a[e2] != a[e3] && a[e3] != a[e4] && a[e4] != a[e5])
        {
            /*
             * Use the second and fourth of the five sorted elements as pivots.
             * These values are inexpensive approximations of the first and
             * second terciles of the array. Note that pivot1 <= pivot2.
             */
            int pivot1 = a[e2];
            int pivot2 = a[e4];
            
            /*
             * The first and the last elements to be sorted are moved to the
             * locations formerly occupied by the pivots. When partitioning
             * is complete, the pivots are swapped back into their final
             * positions, and excluded from subsequent sorting.
             */
            a[e2] = a[left];
            a[e4] = a[right];
            
            /*
             * Skip elements, which are less or greater than pivot values.
             */
            while (a[++less] < pivot1)
                ;
            while (a[--great] > pivot2)
                ;
            
            /*
             * Partitioning:
             * 
             * left part center part right part
             * +--------------------------------------------------------------+
             * | < pivot1 | pivot1 <= && <= pivot2 | ? | > pivot2 |
             * +--------------------------------------------------------------+
             * ^ ^ ^
             * | | |
             * less k great
             * 
             * Invariants:
             * 
             * all in (left, less) < pivot1
             * pivot1 <= all in [less, k) <= pivot2
             * all in (great, right) > pivot2
             * 
             * Pointer k is the first index of ?-part.
             */
            outer: for (int k = less - 1; ++k <= great;)
            {
                int ak = a[k];
                if (ak < pivot1)
                { // Move a[k] to left part
                    a[k] = a[less];
                    /*
                     * Here and below we use "a[i] = b; i++;" instead
                     * of "a[i++] = b;" due to performance issue.
                     */
                    a[less] = ak;
                    ++less;
                }
                else if (ak > pivot2)
                { // Move a[k] to right part
                    while (a[great] > pivot2)
                    {
                        if (great-- == k)
                        {
                            break outer;
                        }
                    }
                    if (a[great] < pivot1)
                    { // a[great] <= pivot2
                        a[k] = a[less];
                        a[less] = a[great];
                        ++less;
                    }
                    else
                    { // pivot1 <= a[great] <= pivot2
                        a[k] = a[great];
                    }
                    /*
                     * Here and below we use "a[i] = b; i--;" instead
                     * of "a[i--] = b;" due to performance issue.
                     */
                    a[great] = ak;
                    --great;
                }
            }
            
            // Swap pivots into their final positions
            a[left] = a[less - 1];
            a[less - 1] = pivot1;
            a[right] = a[great + 1];
            a[great + 1] = pivot2;
            
            // Sort left and right parts recursively, excluding known pivots
            sort(a, left, less - 2, leftmost);
            sort(a, great + 2, right, false);
            
            /*
             * If center part is too large (comprises > 4/7 of the array),
             * swap internal pivot values to ends.
             */
            if (less < e1 && e5 < great)
            {
                /*
                 * Skip elements, which are equal to pivot values.
                 */
                while (a[less] == pivot1)
                {
                    ++less;
                }
                
                while (a[great] == pivot2)
                {
                    --great;
                }
                
                /*
                 * Partitioning:
                 * 
                 * left part center part right part
                 * +----------------------------------------------------------+
                 * | == pivot1 | pivot1 < && < pivot2 | ? | == pivot2 |
                 * +----------------------------------------------------------+
                 * ^ ^ ^
                 * | | |
                 * less k great
                 * 
                 * Invariants:
                 * 
                 * all in (*, less) == pivot1
                 * pivot1 < all in [less, k) < pivot2
                 * all in (great, *) == pivot2
                 * 
                 * Pointer k is the first index of ?-part.
                 */
                outer: for (int k = less - 1; ++k <= great;)
                {
                    int ak = a[k];
                    if (ak == pivot1)
                    { // Move a[k] to left part
                        a[k] = a[less];
                        a[less] = ak;
                        ++less;
                    }
                    else if (ak == pivot2)
                    { // Move a[k] to right part
                        while (a[great] == pivot2)
                        {
                            if (great-- == k)
                            {
                                break outer;
                            }
                        }
                        if (a[great] == pivot1)
                        { // a[great] < pivot2
                            a[k] = a[less];
                            /*
                             * Even though a[great] equals to pivot1, the
                             * assignment a[less] = pivot1 may be incorrect,
                             * if a[great] and pivot1 are floating-point zeros
                             * of different signs. Therefore in float and
                             * double sorting methods we have to use more
                             * accurate assignment a[less] = a[great].
                             */
                            a[less] = pivot1;
                            ++less;
                        }
                        else
                        { // pivot1 < a[great] < pivot2
                            a[k] = a[great];
                        }
                        a[great] = ak;
                        --great;
                    }
                }
            }
            
            // Sort center part recursively
            sort(a, less, great, false);
            
        }
        else
        { // Partitioning with one pivot
            /*
             * Use the third of the five sorted elements as pivot.
             * This value is inexpensive approximation of the median.
             */
            int pivot = a[e3];
            
            /*
             * Partitioning degenerates to the traditional 3-way
             * (or "Dutch National Flag") schema:
             * 
             * left part center part right part
             * +-------------------------------------------------+
             * | < pivot | == pivot | ? | > pivot |
             * +-------------------------------------------------+
             * ^ ^ ^
             * | | |
             * less k great
             * 
             * Invariants:
             * 
             * all in (left, less) < pivot
             * all in [less, k) == pivot
             * all in (great, right) > pivot
             * 
             * Pointer k is the first index of ?-part.
             */
            for (int k = less; k <= great; ++k)
            {
                if (a[k] == pivot)
                {
                    continue;
                }
                int ak = a[k];
                if (ak < pivot)
                { // Move a[k] to left part
                    a[k] = a[less];
                    a[less] = ak;
                    ++less;
                }
                else
                { // a[k] > pivot - Move a[k] to right part
                    while (a[great] > pivot)
                    {
                        --great;
                    }
                    if (a[great] < pivot)
                    { // a[great] <= pivot
                        a[k] = a[less];
                        a[less] = a[great];
                        ++less;
                    }
                    else
                    { // a[great] == pivot
                        /*
                         * Even though a[great] equals to pivot, the
                         * assignment a[k] = pivot may be incorrect,
                         * if a[great] and pivot are floating-point
                         * zeros of different signs. Therefore in float
                         * and double sorting methods we have to use
                         * more accurate assignment a[k] = a[great].
                         */
                        a[k] = pivot;
                    }
                    a[great] = ak;
                    --great;
                }
            }
            
            /*
             * Sort left and right parts recursively.
             * All elements from center part are equal
             * and, therefore, already sorted.
             */
            sort(a, left, less - 1, leftmost);
            sort(a, great + 1, right, false);
        }
        
    }
    
    private static void ArrayListTest(List list)
    {
        System.out.println(list);
        Collections.reverse(list);
        System.out.println(list);
    }
    
    public static void listSort()
    {
        
        int a[] = {1, 4, 2, 4, 5};
        Arrays.sort(a);
        for (int i = 0; i < a.length; i++)
        {
            System.out.print(a[a.length - i - 1] + "\t");
        }
        
        int left = 0;
        int right = a.length - 1;
        
        for (int i = left, j = i; i < right; j = ++i)
        {
            // System.out.println("i:" + i + ",j:" + j);
            int ai = a[i + 1];
            while (ai < a[j])
            {
                a[j + 1] = a[j];
                if (j-- == left)
                {
                    break;
                }
            }
            a[j + 1] = ai;
            System.out.println(ai);
        }
        System.out.println("============");
    }
    
}

 可以看出大致的思维是一致的

以上是关于算法中的快速排序 quicksort的主要内容,如果未能解决你的问题,请参考以下文章

Algorithms - Quicksort - 快速排序算法

QuickSort在算法征服阶段实现排序?

快速排序 QuickSort

js算法-快速排序(Quicksort)

快速排序(QuickSort)

算法第四版学习笔记之快速排序 QuickSort