数据结构学习笔记3.2—快速排序

快速排序是目前最流行的算法,在大多数情况下,快速排序都是最快的,执行时间为O(N*logN)。

快速排序本质上是通过把一个数组划分为两个子数组,然后在递归调用自身对每一个字数组进行快速排序。

即数组划分为字数组A,B,在对A进行划分为A1,A2,对A1划分为A11,A12。。B也是如此执行,这就涉及到递归调用自身的操作。

1.取右值作为枢纽的情形

    /**
     * 快速排序
     * 
     * @param left
     * @param right
     */
    public static void quickSort(int left, int right, int[] arr)
    {
        // 检查数据是否只包含一个数据项,如果是,则为有序数组,无需排序
        if (right - left <= 0)
        {
            return;
        }
        else
        {
            // 设置参照值为最右边的数据值
            int pivot = arr[right];

            int partition = partitionIt(left, right, pivot, arr);

            // 调用自身对左边排序
            quickSort(left, partition - 1, arr);

            // 调用自身对右边排序
            quickSort(partition + 1, right, arr);
        }
    }

(1)设置枢纽:int pivot = arr[right]; 为了方便,这里选择做右边的作为枢纽。当一轮排序下来后,枢纽的位置在最终排序的位置就确定了,

这是因为,最终排序后,左边的数值总比枢纽的小,右边的数值总比枢纽的大。

(2)这里看到主要有3个方法

      1)partitionIt(left, right, pivot, arr); 数据结构学习笔记3.1节所描述的划分方法,主要是取出枢纽值。

      2)quickSort(left, partition - 1, arr); 递归调用自身排序左边的数组。

      3)quickSort(partition + 1, right, arr); 递归调用自身排序右边的数组。

递归方法可以这样理解:先把左边排好,再排列右边。

完整代码:

/**
     * 快速排序
     * 
     * @param left
     * @param right
     */
    public static void quickSort(int left, int right, int[] arr)
    {
        // 检查数据是否只包含一个数据项,如果是,则为有序数组,无需排序
        if (right - left <= 0)
        {
            return;
        }
        else
        {
            // 设置参照值为最右边的数据值
            int pivot = arr[right];

            int partition = partitionIt(left, right, pivot, arr);

            // 调用自身对左边排序
            quickSort(left, partition - 1, arr);

            // 调用自身对右边排序
            quickSort(partition + 1, right, arr);
        }
    }

    /**
     * 划分数据
     * 
     * @param left 左边数据
     * @param right 右边数据
     * @param pivot 参照值
     * @return
     */
    public static int partitionIt(int left, int right, int pivot, int[] arr)
    {
        // 左边指针
        // 左边数据-1,为了后面执行++leftPrt操作时,数据能正确+1
        int leftPrt = left - 1;

        // 右边指针
        // 右边数据-1,为了后面执行--rightPrt操作时,数据能正确-1
        int rightPrt = right + 1;

        while (true)
        {
            // 左边数据比参照值的小,不做后续操作,循环
            while (leftPrt < arr.length && arr[++leftPrt] < pivot)
            {
                int a = 0;
            }

            // 右边数据比参照值的大,不做后续操作,循环
            while (rightPrt > 0 && arr[--rightPrt] >= pivot)
            {
                int b = 0;
            }

            if (leftPrt >= rightPrt)
            {
                break;
            }
            else
            {
                // 交左右两边的数据
                swap(leftPrt, rightPrt, arr);
            }
        }

        // 重置参照值
        swap(leftPrt, right, arr);

        return leftPrt;
    }

    /**
     * 数据交换
     * 
     * @param index1 入参1
     * @param index2 入参2
     * @param arr 比较数组
     */
    public static void swap(int index1, int index2, int[] arr)
    {
        int temp = arr[index1];
        arr[index1] = arr[index2];
        arr[index2] = temp;
    }

2.取中值作为枢纽的情形

对于快速排序算法来说,最优的情况是有两个大小相等的子数组。

以右值作为枢纽,有一种情形:当数组以逆序排序列时,这时候产生N-1的子数组和一个只包含枢纽的子数组。此时执行的效率为O(N2)。

解决方法:

三数据项取中:思路是取数组N的第1个,第N/2个,第N个数据,即arr[0],arr[N/2],,arr[N-1]项数据,此时操作是:

(1)比较三项数据,取中间值作为枢纽。

(2)对这三个数据排序。

完整代码:

    /**
     * 快速排序
     * @param left 数组第一个位置,即0
     * @param right 数组最后一个数据,即arr.length - 1
     * @param arr 数组
     */
    public static void recQuickSort(int left, int right, int[] arr)
    {
        // 数组长度
        int size = right - left + 1;
        
        // 如果数据项只有3项
        if (size <= 3)
        {
            // 处理数组个数小于3个的数组
            manualSort(left, right, arr);
        }
        else
        {
            int median = midanOf3(left, right, arr);
            int partition = partitionIt(left, right, median, arr);
            recQuickSort(left, partition - 1, arr);
            recQuickSort(partition, right, arr);
        }
    }
    
    /**
     * 处理整个数组小于或等于3个数据项的数组
     * @param left 数组左边位置
     * @param right 数组右边位置
     * @param arr 数组
     */
    public static void manualSort(int left, int right, int[] arr)
    {
        int size = right - left + 1;
        if (size <= 1)
        {
            return;
        }
        if (size == 2)
        {
            // 如果左边数据大于右边数据,则交换位置
            if (arr[left] > arr[right])
            {
                swap(left, right, arr);
            }
            return;
        }
        // 数组长度为3
        else
        {
            // 交换位置,交换方法:先用第一个数据项一次于后面两个数据项比较,
            // 取出最小的数据放在最左边,然后在比较后面两个数据
            if (arr[left] > arr[right - 1])
            {
                swap(left, right, arr);
            }
            if (arr[left] > arr[right])
            {
                swap(left, right, arr);
            }
            if(arr[right- 1] > arr[right])    
            {
                swap(right - 1, right, arr);
            }
        }
    }
    
    /**
     * 取出3个数据项,排序,并将中枢取出放在数组right - 1位置
     * @param left 数组左边项
     * @param right数组右边项
     * @param arr 数组
     */
    public static int midanOf3(int left, int right, int[] arr)
    {
        // 取出中间项
        int center = (right + left) / 2;
        
        // 通过交换排序,思路与manualSort中使用的方法一致
        if (arr[left] > arr[center])
        {
            swap(left, center, arr);
        }
        if (arr[left] > arr[right])
        {
            swap(left, right, arr);
        }
        if (arr[center] > arr[right])
        {
            swap(center, right, arr);
        }
        
        // 交换中枢与right - 1的位置
        // 由于arr[right]的位置已经排好,即数值比中枢的要打,已经划分
        // 所以取倒数第二个数据right - 1的数据,将center中枢的位置与之交换,目的减少右边排序次数
        swap(center, right - 1, arr);
        
        // 此时位置值为中枢值
        return arr[right - 1]; 
    }
    
    /**
     * 划分数组
     * @param left 数组左边位置
     * @param right 数组右边位置
     * @param median 中枢值
     * @param arr 数组
     */
    public static int partitionIt(int left, int right, int median ,int[] arr)
    {
        int leftPrt = left;
        int rightPrt = right - 1; 
        
        while(true)
        {
            while (arr[++leftPrt] < median);
            while (arr[--rightPrt] > median);
            
            if (leftPrt >= rightPrt)
            {
                break;
            }
            else
            {
                // 左边大于中枢值,右边小于中枢值,交换
                swap(leftPrt, rightPrt, arr);
            }
        }
        
        // 重置中枢值,此时左边leftPrt指针的位置指向右边数组的第一个数据项,比中枢值大
        // 将左边标记的位置与right - 1交换,此时,中枢值所在的位置已经固定,不会在变
        swap(leftPrt, right - 1, arr);
        return leftPrt;
    }
    
    /**
     * 数据交换
     * 
     * @param index1 入参1
     * @param index2 入参2
     * @param arr 比较数组
     */
    public static void swap(int index1, int index2, int[] arr)
    {
        int temp = arr[index1];
        arr[index1] = arr[index2];
        arr[index2] = temp;
    }

1. manualSort(left, right, arr);这个方法是处理当数组划分后,数组个数小于等于3个时,此时直接交换排序排序即可。

2.midanOf3(left, right, arr);这个方法是去数组(包含子数组)的中间值,也是通过比较交换的方法排序。

取中值排序的特点:对于随机排序,执行效果与取右值相差不大,但是在逆序中,排序消耗时间有明显的提高。

原文地址:https://www.cnblogs.com/winlrou/p/3529089.html