快排实现(递归与非递归)

314 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

思路

这个其实和荷兰国旗一样,都是先让一个数放在它该放的地方,然后就有两个部分出来了,然后利用递归解决就可以了。如果有不了解的可以点击这个链接去了解一下,帮助理解

荷兰国旗问题 - 掘金 (juejin.cn)

举例

数组【5,4,9,0,2,3,7】

第一次先让5在应该在的地方

数组就变成了

【4,0,2,3,5,9,7】(这个样子不一定是对的,只是举例)

然后数组递归之后就变成了两个数组分别是

【4,0,2,3】

【9,7】

然后让他们再从最开始解决5的那样。

代码

我们先来看看我们是怎么利用荷兰国旗的问题解决5能找到自己的位置的

public static int[] netherlandsFlag(int[] arr, int L, int R) {
    if (L > R) {
        return new int[] { -1, -1 };
    }
    if (L == R) {
        return new int[] { L, R };
    }
    int less = L - 1;
    int more = R;
    int index = L;
    while (index < more) {
        if (arr[index] == arr[R]) {
            index++;
        } else if (arr[index] < arr[R]) {
            swap(arr, index++, ++less);
        } else {
            swap(arr, index, --more);
        }
    }
    swap(arr, more, R);
    return new int[] { less + 1, more };
}

这边返回的数组是相同区间的l和r。

那我们拿到相同区间的左右后,我们可以干什么呢?

我们可以把这些拍好的区域划开,然后在没有排好的区域里面执行操作

public static void process(int[] arr, int L, int R) {
    if (L >= R) {
        return;
    }
    swap(arr, L + (int) (Math.random() * (R - L + 1)), R);
    int[] equalArea = netherlandsFlag(arr, L, R);
    process(arr, L, equalArea[0] - 1);
    process(arr, equalArea[1] + 1, R);
}

用这个代码就可以不管排好的区域了。有人会问着变的随机数是什么意思

这个是为了避免偶然性,因为你固定一个位置之后,有可能数据环境不是很好,那就要递归好多次,就很危险,这个时候随机一个数,让所有交给上帝。

之后再用方法调用一下

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

我们在之前的归并排序里面讲过,递归在生产环境里面是不受欢迎的,是因为它调用栈十分频繁,导致了效率的降低。那我们现在就实现一个非递归版本

在我们实现非递归版本之前,我们先要明白,为什么我们会用到递归,我们了解这个之后,我们看看我们是不是有东西可以替代这个递归。

我们这边创建递归的原因是不是为了排除排好的数字,然后在排好数字的左右各自调用了一个栈,那我们只要用一个变量记住我们现在应该调用哪一个部分,然后在要用的时候拿出来就好了。

那我们知道函数栈频繁的创建和销毁是很浪费的,那我们自己创建一个栈来使用是不是更好一点呢?

非递归版本

我们先创建一个类

public static class WaiteUse {
    public int l;
    public int r;

    public WaiteUse(int left, int right) {
        l = left;
        r = right;
    }
}

这个方便我们记录等待排列的左右是多少

public static void quickSort2(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        int N = arr.length;
        swap(arr, (int) (Math.random() * N), N - 1);
        //第一次处理,获得左右值。
        int[] help = netherlandsFlag(arr, 0, N - 1);
        int curLeft = help[0];
        int curRight = help[1];
        Stack<WaiteUse> stack = new Stack<>();
//        压入两个要等待排序的数组区间
        stack.push(new WaiteUse(0, curLeft - 1));
        stack.push(new WaiteUse(curRight + 1, N - 1));
//        跳出循环的条件是栈为空
        while (!stack.isEmpty()) {
            WaiteUse waiteUse = stack.pop();
//            如果碰到两个值相等的就跳过
            if (waiteUse.l < waiteUse.r) {
                swap(arr, waiteUse.l + (int) (Math.random() * (waiteUse.r - waiteUse.l + 1)), waiteUse.r);
                help = netherlandsFlag(arr, waiteUse.l, waiteUse.r);
                curLeft = help[0];
                curRight = help[1];
//                压入新获得的两个没有排序的数组区间
                stack.push(new WaiteUse(waiteUse.l, curLeft - 1));
                stack.push(new WaiteUse(curRight + 1, waiteUse.r));
            }
        }
    }

这边就是快速排序的所有内容了。