快速排序

介绍

冒泡排序是两两比较交换,属于交换排序,每轮把一个元素冒泡到数列的一端

快速排序则是在每一轮挑选一个基准元素,并让其他比他大的元素移动到数列的一边,比他小的元素移动到数列的另一边,从而把数据拆成两部分

=》分治法  分而治之

 

 

 每一轮的比较交换需要把全部元素遍历一遍,时间复杂度O(n)。

假如元素是n个,平均情况需要经历logn轮(能分成几个两部分),因此快速排序的整体平均时间复杂度是O(nlogn)

如果直接选取第一个元素为基准元素,极端情况倒序,退化成O(n^2)

故,随机选择一个元素作为基准元素,并且让基准元素和数列首元素交换位置

当然,随机选中的元素也有可能是数列的最值,同样会影响到分治的效果,所以,快速排序平均时间复杂度O(nlogn),最坏情况O(n^2)

 

基于双边循环交换

右指针选取<基准元素的值,碰到则停止移动,开始移动左指针

左指针选取>基准元素的值,碰到则停止移动,然后如果left < right就进行元素交换p

重叠则表示两边元素已经交换完,将重叠位置元素与基准元素交换,基准元素就将元素序列分成了两部分,再分别对两边元素序列进行重复操作

 

 

    /**
     *基于双边循环的快速排序
     */
    public static void bilateralCycleSort(int[] arr, int startIndex, int endIndex) {
        //递归条件
        if (startIndex >= endIndex) {
            return;
        }
        // 基准元素
        int pivotIndex = partition(arr, startIndex, endIndex);
        //根据基准元素,分成两部分进行递归排序
        bilateralCycleSort(arr, startIndex, pivotIndex - 1);
        bilateralCycleSort(arr, pivotIndex + 1, endIndex);
    }

    /**
     * 分治(双边循环法)
     * 实现元素交换并把基准元素放到中间位置,返回基准元素index 就是一轮交换
     *
     * @param arr 待交换的数组
     * @param startIndex 起始下标
     * @param endIndex 结束下标
     * @return
     */
    private static int partition(int[] arr, int startIndex, int endIndex) {
        // 可以随机选取,这里取第一个位置
        int pivot = arr[startIndex];
        int left = startIndex;
        int right = endIndex;
        while(left != right) {
            // right指针比较并左移 arr[rihgt]比基准元素大就左移,否则停止
            while(left < right && arr[right] > pivot) {
                right--;
            }
            // left指针比较并右移 arr[left]<=基准元素,就右移,否则停止
            while(left < right && arr[left] <= pivot) {
                left++;
            }
            // 交换left和right指针指向的元素 左右指针都停止时,arr[left]>pivot,arr[right]<=pivot 交换
            if(left < right) {
                int p = arr[left];
                arr[left] = arr[right];
                arr[right] = p;
            }
        }
        //左右指针重合 一边比基准元素大,一边比基准元素小,把基准元素换过来就是中间值
        arr[startIndex] = arr[left];
        arr[left] = pivot;
        return left;
    }

 

基于单边循环

双边循环虽然更加直观,但是代码实现相对繁琐。而单边循环更简单,只从数组一边对元素进行遍历和交换。

也是双指针,不过是单向的,一个mark指针记录小于pivot的边界,一个指针负责遍历并和mark指针指向的元素进行交换(将小于pivot的元素放到mast指针前面得区域,mast指针为小于pivot的元素边界)

过程:

  • 选中基准元素,设置一个mark指针指向数列起始位置,代表小于基准元素的区域边界
  • 从基准元素的下一个位置开始遍历
  • 指向元素>基准元素,继续往后遍历
  • 指向元素<基准元素,做两件事,第一:把指针右移1位,因为小于pivot的区域边界增大了1,第二:让最新遍历到的(小于pivot的)元素和mark指针指向的边界元素进行交换以更新边界
  • 最后把pivot元素交换到mark指针所在的位置,这一轮比较交换完成

 

 

 

   /**
     * 基于单边循环的快速排序
     * @param arr
     */
    public static void singleTrackCycleSort(int[] arr, int startIndex, int endIndex) {
        //递归结束条件
        if(startIndex >= endIndex) {
            return;
        }
        //单边循环法 一轮
        int pivotIndex = partitionForSingle(arr, startIndex, endIndex);
        //分治
        singleTrackCycleSort(arr, startIndex,pivotIndex-1);
        singleTrackCycleSort(arr, pivotIndex+1, endIndex);
    }

    /**
     * 单边循环法 for 快速排序
     * 两个指针单向遍历,快指针负责判断元素,慢指针负责与快指针交换位置
     *
     * @param arr
     * @param startIndex 起始下标
     * @param endIndex 结束下标
     * @return 分界下边 基准元素index
     */
    private static int partitionForSingle(int[] arr, int startIndex, int endIndex) {
        //取第一个元素为基准元素,也可以随机选取
        int pivot = arr[startIndex];
        // mark 指针 代表小于pivot的区间
        int mark = startIndex;
        for (int i = startIndex + 1; i <= endIndex; i++) {
            // 如果快指针指向元素小于pivot mark右移1位与快指针元素进行交换 扩展小于区域边界
            if (arr[i] < pivot) {
                mark++;
                int temp = arr[mark];
                arr[mark] = arr[i];
                arr[i] = temp;
            }
        }
        // 遍历完 把基准元素放到中间位置(与区域边界mark指向元素交换位置) 基准元素位置就此确定
        arr[startIndex] = arr[mark];
        arr[mark] = pivot;
        // 返回基准元素索引
        return mark;
    }

 

posted on 2023-03-14 00:43  or追梦者  阅读(9)  评论(0编辑  收藏  举报