快速排序基本思想

快速排序也是一种交换排序,采用分治法,每一轮先找一个基准元素,然后把比基准元素大的移动到它的一边,比基准元素小的移动到另一边,从而把数列拆分成两个部分,再对这两个部分的子数列采用相同的操作,直到整个数列不能继续拆解,这样整个数列就是有序的了。快速排序的每一轮下来都会把基准元素放到有序的位置上。

性能分析

  • 稳定性:不稳定
  • 时间复杂度:快排每一次的比较和交换都需要把整个数组遍历一遍,时间复杂度时O(n),这样的遍历一共有logn轮,所以总体的时间复杂度是O(nlogn).

基准元素的选择

  • 一般选取数列的第一个元素为基准元素
  • 随机选取

元素的交换

双边循环法

过程:
原始数列:

首先选取基准元素pivot,并且设置两个指针left,right,指向数列的最左和最右两个元素,如下:

接下来进行第一轮循环,从right开始,让指针所指向的元素和基准元素pivot作比较,若大于或等于pivot,则指针向左移动;小于pivot则right指针停止移动,切换到left指针。
在当前数列中,1<4,所以right直接停止移动,换到left指针,进行下一步行动。left指针移动,让其所指向的元素和pivot作比较,小于或等于pivot,则指针向右移动;大于则停止移动。
由于left指针开始时指向的时基准元素,所以相等,所以left向右移动1位。如下:

由于7>4,left指针停下来,这是交换left和right指针所指向的元素。

接下来,进行第2次循环,重新切换到right指针,向左移动。right移动到8,8>4,继续左移。由于2<4,停在2的位置。
按照这个思路,后续的步骤如下:


单边循环法

原始序列如下:

确定基准元素pivot,同时设置一个mark指针指向数列的起始位置,这个mark指针代表小于基准元素的区域边界

接下来,从基准元素的下一个位置开始遍历数组。
如果遍历到的元素大于pivot,就继续往右遍历。
如果遍历到的元素小于基准元素,则需要做两件事,第一:把mark指针右移1位,因为小于pivot的 区域边界增大了1;第二:让最新遍历到的元素和mark指针所在位置的元素交换位置,因为最新遍历的元素归属于小于pivot的区域。
首先遍历到元素7,7>4,继续遍历。

接下来遍历到的是元素3,3<4,所以mark右移一位。

随后,让元素3和mark指针所在位置的元素交换位置,因为3归属于小于pivot的区域。

按照这个思路,后续步骤如下:


代码实现

双边循环法的快排:

public static void quickSort(int[] arr, int startIndex, int endIndex) {
        // 递归结束条件:startIndex大等于endIndex的时候
        if (startIndex >= endIndex) {
            return;
        }
        // 得到基准元素位置
        int pivotIndex = partition(arr, startIndex, endIndex);
        // 根据基准元素,分成两部分递归排序
        quickSort(arr, startIndex, pivotIndex - 1);
        quickSort(arr, pivotIndex + 1, endIndex);
    }

    /**
     * 分治(双边循环法)
     * @param arr     待交换的数组
     * @param startIndex    起始下标
     * @param endIndex    结束下标
     */
    private static int partition(int[] arr, int startIndex, int endIndex) {
        // 取第一个位置的元素作为基准元素(也可以选择随机位置)
        int pivot = arr[startIndex];
        int left = startIndex;
        int right = endIndex;

        while( left != right) {
            //控制right指针比较并左移
            while(left<right && arr[right] > pivot){
                right--;
            }
            //控制left指针比较并右移
            while( left<right && arr[left] <= pivot) {
                left++;
            }
            //交换left和right指向的元素
            if(left<right) {
                int p = arr[left];
                arr[left] = arr[right];
                arr[right] = p;
            }
        }

        //pivot和指针重合点交换
        arr[startIndex] = arr[left];
        arr[left] = pivot;

        return left;
    }

单边循环法的快排:

public static void quickSort(int[] arr, int startIndex, int endIndex) {
        // 递归结束条件:startIndex大等于endIndex的时候
        if (startIndex >= endIndex) {
            return;
        }
        // 得到基准元素位置
        int pivotIndex = partitionV2(arr, startIndex, endIndex);
        // 根据基准元素,分成两部分递归排序
        quickSort(arr, startIndex, pivotIndex - 1);
        quickSort(arr, pivotIndex + 1, endIndex);
    }

  /**
     * 分治(单边循环法)
     * @param arr     待交换的数组
     * @param startIndex    起始下标
     * @param endIndex    结束下标
     */
    private static int partitionV2(int[] arr, int startIndex, int endIndex) {
        // 取第一个位置的元素作为基准元素(也可以选择随机位置)
        int pivot = arr[startIndex];
        int mark = startIndex;

        for(int i=startIndex+1; i<=endIndex; i++){
            if(arr[i]<pivot){
                mark ++;
                int p = arr[mark];
                arr[mark] = arr[i];
                arr[i] = p;
            }
        }

        arr[startIndex] = arr[mark];
        arr[mark] = pivot;
        return mark;
    }


夜雨声烦
7 声望1 粉丝

一个菜鸟,主要记录一下自己复习的东西


« 上一篇
直接插入排序
下一篇 »
子网划分