• 算法


    经典快排

    经典快排的思路是选取数组的最后一个数 x,按照问题一的思路把整个数组划分成 小于等于 x | 大于 x 两个部分,将 x 和 大于 x 部分数组的第一个元素交换位置。此时整个数组划分成 小于等于 x | x | 大于 x 三个部分,也就是这一次排序将 x 值排好位置。

    再分别对 小于等于 x大于 x 中的数组递归划分,直到划分成一个数,此时所有元素也完成排序。

    按照问题二的思路可以对经典快排做改进,使得每次划分数组成为小于 x | 等于 x | 大于 x 三个部分,通过这种排序方式可以一次划分多个 x 值的位置,排序效率得到提高。

    但是,经典快排出现问题与数据状况有关。每次选择 x 值都是数组的最后一个数,如果遇到 [1,2,3,4,5] 或者 [5,4,3,2,1] 这种数组,算法时间复杂度将变成 O(n^2)。

    随机快排

    随机快排是经典快排的一种改进,通过生成随机下标 i,选择 a[i] 和最后一个数 x 进行交换,再使用经典快排。此时的事件就是一个概率事件,需要使用期望来估计算法的时间复杂度。

    仍以 [1,2,3,4,5] 为例,经过随机快排初始变换,可以形成下列五种情况,数据状况的影响有效降低。在长期期望下,随机快排算法的时间复杂度为 O(N*logN)。由于每次划分数据都需要记录 =x 数组的下标范围,因此额外的空间复杂度为 O(logN)。

    5,2,3,4,1;
    1,5,3,4,2;
    1,2,5,4,3;
    1,2,3,5,4;
    1,2,3,4,5.
    

    思想

    随机快排和经典快排的差别就在于添加了一行代码,使比较的数 x 具有随机性。

    通过这种随机的方法处理特殊的数据,使得算法具有更好的鲁棒性。

    单边循环法和双边循环法

    上述快排的实现都是从一个方向上遍历元素,然后分成两个数组,成为单边循环法。还有另一种实现的方法是双边循环,在《大话数据结构》书中可以看到实现。

    虽然实现方法多种多样,但是其核心本质仍然是选取数组中的值与数组其他元素比较大小,经过一轮循环之后将数组分成两个部分。单边循环和双边循环本质上是一样的,只是实现方式上不同。

    快排代码

    思路:使用 quickSort() 函数处理数组,先进行随机处理,使用核心方法 partition() 将数据分成 小于 x | 等于 x | 大于 x 三个部分,返回等于区域的左右下标值 [a, b],递归调用 小于 x 区域和 大于 x 区域。

    quickSort(arr, left, right) {
    	if (left < right) 
     		swap(); //随机快排的改动处
    	    int[] p = partition(arr, left, right); //通过方法返回的是分组后确定的 = num 区域范围
    		quickSort(arr, left, p[0] - 1); //递归 < num 区域
    		quickSort(arr, p[1] + 1, right); //递归 > num 区域
    }
    
    partition(); //就是荷兰国旗问题的过程
    
    public static void quickSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        quickSort(arr, 0, arr.length - 1);
    }
    
    
    public static void quickSort(int[] arr, int left, int right) {
        //多余判断
        //if (left == right) {
        //    return;
        //}
        if (left < right) {
            //随机快排核心
            //Math.random() 取值范围 [0, 1)
            //(Math.random() * (right - left + 1)) 此处数值 <= (right - left),因此可以保证参数 first 在区间内
            swap(arr, left + (int) (Math.random() * (right - left + 1)), right);
            int[] p = partition(arr, left, right);
            quickSort(arr, left, p[0] - 1);
            quickSort(arr, p[1] + 1, right);
        }
    }
    
    
    public static int[] partition(int[] arr, int left, int right) {
        int less = left - 1;
        int more = right + 1;
        //int more = right;
        //left 成为数组遍历的 cur 指针,当触碰到 more 边界时终止循环
        while (left < more) {
            if (arr[left] < arr[right]) {
                swap(arr, less + 1, left);
                less++;
                left++;
                //左神代码
                //swap(arr, ++less, left++);
            } else if (arr[left] == arr[right]) {
                left++;
            } else {
                swap(arr, left, more - 1);
                more--;
                //左神代码
                //swap(arr, left, --more);
            }
        }
        return new int[]{less + 1, more - 1};
        //return new int[]{less + 1, more};
    }
    

    手写代码过程中发现有些判断多余,因此做了注释。在整体代码中,有些语句能够精简成更简洁的代码,但是为了思路的连贯性,仅将简洁的代码做注释保留。从这些精简的代码中也可以看到一些优雅的代码真令人惊叹。

  • 相关阅读:
    POJ1006(中国剩余定理)
    Ubuntu16.04安装jupiter
    OpenGL.tutorial06键盘和鼠标
    OpenGL.教程
    OpenGL.Qt532.cube
    Qt551.主窗体Margin
    Qt551.窗口滚动条
    Qt551.OpenGL.ZC简单例子
    Qt5OpenGL.顶点像素大小设置
    OpenGL.Tutorial03_Matrices_测试
  • 原文地址:https://www.cnblogs.com/chenxianbin/p/11888513.html
Copyright © 2020-2023  润新知