快排遇到的坑

快排遇到的坑

https://blog.csdn.net/liuzuoping/article/details/103387250?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162071138516780264087350%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=162071138516780264087350&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-103387250.first_rank_v2_pc_rank_v29&utm_term=java+%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95&spm=1018.2226.3001.4187

↑在这位兄台的文章下学习了一下各种排序的写法,感觉总的来说做得很好,有动图搭配理解,不过在快排的地方遇到了一个以前也讨论了很久的坑,今天又遇到,就记录一下,先看下他的代码

public static void sort(int[] arr) {
    sort(arr, 0, arr.length - 1);
}

private static void sort(int[] arr, int startIndex, int endIndex) {
    if (endIndex <= startIndex) {
        return;
    }
    //切分
    int pivotIndex = partition(arr, startIndex, endIndex);
    sort(arr, startIndex, pivotIndex-1);
    sort(arr, pivotIndex+1, endIndex);
}


private static int partition(int[] arr, int startIndex, int endIndex) {
    int left = startIndex;
    int right = endIndex;
    int pivot = arr[startIndex];//取第一个元素为基准值

    while (true) {
        //从左往右扫描
        while (arr[left] <= pivot) {
            left++;
            if (left == right) {
                break;
            }
        }

        //从右往左扫描
        while (pivot < arr[right]) {
            right--;
            if (left == right) {
                break;
            }
        }

        //左右指针相遇
        if (left >= right) {
            break;
        }

        //交换左右数据
        int temp = arr[left];
        arr[left] = arr[right];
        arr[right] = temp;
    }

    //将基准值插入序列
    int temp = arr[startIndex];
    arr[startIndex] = arr[right];
    arr[right] = temp;
    return right;
}

乍一看感觉也没什么问题,自己运行一次以后,发现结果不对
第一行为原数组,第二行为执行结果
第一行为原数组,第二行为执行结果

在纸上模拟了一下,发现了问题所在:原来的代码是先从左开始扫描,再从右开始扫描,这样可能会导致一种结果:左边的扫描遇到了一个比基准值大的数字,但是右边恰好也没有可以替换的数字了,满足了left == right,最后导致了基准数与之交换
比如:3 1 2 4 这个数组需要从小到大排序,如果先从左扫描,12都比3小,左指针会直接到达4的位置,满足了left == right的条件,因此第一次排序后,结果是4 1 2 3,后续的排序也不会正确了。但是如果先从右开始扫描,一定找到的是一个小于等于基准值3的数,与其交换后,仍然是满足左边的数小于等于基准值的。

结论:在类似的代码逻辑下,从左开始扫描可能会将大于基准值的数移动到左侧,导致最后结果不对。
最后贴一个修改后的代码,文章中有任何不合理的地方,欢迎指正

public static void sort(int[] arr) {
    sort(arr, 0, arr.length - 1);
}

private static void sort(int[] arr, int startIndex, int endIndex) {
    if (endIndex <= startIndex) {
        return;
    }
    //切分
    int pivotIndex = partition(arr, startIndex, endIndex);
    sort(arr, startIndex, pivotIndex - 1);
    sort(arr, pivotIndex + 1, endIndex);
}


private static int partition(int[] arr, int startIndex, int endIndex) {
    int left = startIndex;
    int right = endIndex;
    int pivot = arr[startIndex];//取第一个元素为基准值
    
    while (true) {
        //从右往左扫描
        while (pivot <= arr[right]) {
            right--;
            if (left == right) {
                break;
            }
        }

        //从左往右扫描
        while (arr[left] <= pivot) {
            left++;
            if (left == right) {
                break;
            }
        }

        //左右指针相遇
        if (left >= right) {
            break;
        }

        //交换左右数据
        int temp = arr[left];
        arr[left] = arr[right];
        arr[right] = temp;

    }

    //将基准值插入序列
    int temp = arr[startIndex];
    arr[startIndex] = arr[right];
    arr[right] = temp;
    return right;
}

运行结果:
第一行为原数组,第二行为结果
第一行为原数组,第二行为结果

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值