从"荷兰国旗问题"到"快排"

从”荷兰国旗问题”到”快排”

给定一个数组arr和一个数num,请把小于num的放在数组的左边,等于num的放在中间,大于的放在右边。要求额外空间复杂度为O(1),时间复杂度为O(N).

从这道题我们可以很轻易联想到快排中的partition,但是经典快排的partition函数是没有处理”等于放在中间“这件事情的,所以我们需要用另外一个策略,实现partition。
partition

@Test
public void test(){
        int[] arr = {3,2,7,5,6,1,10};
        int num = 5;
        partition(arr,0,arr.length-1,num);
        System.out.println(Arrays.toString(arr));
}

public static void partition(int[] arr,int left,int right,int num){
        // 小于区域0-less
        int less = left - 1 ;
        // 当前指针
        int cur = left;
        // 大于区域more-arr.length
        int more = right + 1 ;
        while(cur < more){
            if(arr[cur] < num){
                // arr[cur]属于小于区
                swap(arr,++less,cur++);
            }else if(arr[cur] > num){
                // arr[cur] 属于大于区,注意这里不需要cur++
                swap(arr,--more,cur);
            }else{
                cur++;
            }
        }
    }
    public static void swap(int[] arr,int i,int j){
        int t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

根据图和代码,我们可以很轻松的理清思路。

首先,我们把数组分为三个部分,一个小于区,一个等于区,一个大于区。

遍历数组arr,如果当前遍历的数小于num,则把当前数跟小于区的后一位交换,cur++,小于区扩大一位;如果当前遍历的数等于num,则cur++;如果当前遍历的数大于num,则把当前数跟大于区的前一位交换,大于区扩大一位。

为什么交换大于区的数时,cur不需要++呢?

因为如果交换过来的是一个小于num的数,此时如果cur++,则会导致这个数无法被处理移动到小于区。

改良后的快排

我们根据这个思路,改良了快排。

import org.junit.Test;
import java.util.Arrays;

/**
 * 改良版快排:partition返回的是等于区的左边界和右边界
 */
public class QuickSort {
    @Test
    public void test(){
        int[] arr = {3,2,7,5,6,1,5};
        quicksort(arr);
        System.out.println(Arrays.toString(arr));
    }

    /**
     * 以 arr[right] 为轴
     * @param arr
     * @param left
     * @param right
     * @return 返回等于区域的左边界和右边界
     */
    public int[] partition(int[] arr,int left,int right){
        int less = left-1;
        int more = right;
        int cur = left;
        while(cur < more){
            if(arr[cur] < arr[right]){
                // 小于区
                swap(arr,++less,cur++);
            }else if(arr[cur] > arr[right]){
                // 大于区
                swap(arr,--more,cur);
            }else{
                cur++;
            }
        }
        // 把轴与more区的第一个交换
        swap(arr,more,right);
        return new int[]{less+1,more};
    }

    public static void swap(int[] arr,int i,int j){
        int t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    public void quicksort(int[] arr,int left,int right) {
        if(left < right){
            // 从数组范围内随机选择一个数,作为轴,与right交换
            swap(arr,left + (int)(Math.random()*(right - left+1)),right);
            int[] partition = partition(arr, left, right);
            quicksort(arr,left,partition[0]-1);
            quicksort(arr,partition[1]+1,right);
        }
    }

    public void quicksort(int[] arr){
        if(arr == null || arr.length < 2){
            return;
        }
        quicksort(arr,0,arr.length-1);
    }

为什么这个快排是”改良的“?

经典快排由于没有”等于区“,所以我们每次处理的只是一个位置的值。但是当我们有了”等于区“,那么等于区里面的所有的数都不需要参与到下一次的partition中了,这样可以节省了大量的时间。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值