其他分享
首页 > 其他分享> > 分区函数和快排(快排分为递归和非递归两个版本)

分区函数和快排(快排分为递归和非递归两个版本)

作者:互联网

package class08;

import java.util.Arrays;
import java.util.Stack;

/**
 * 分区函数和快排。
 * 快排分为递归和非递归两个版本。
 */
public class Code03_PartitionAndQuickSort {
    /**
     * 分区。结果是分为两个区域,小于等于区(不保证有序),和大于区(不保证有序)。
     * 以数组arr的最后一个数为基准数,将小于等于它的数,归为小于等于区。大于它的数,归于大于区。
     *
     * @param arr 需要被分区的数组
     */
    public static void splitNum1(int[] arr) {
        //分区函数的默认的前提条件是,数组的最后一个数为基准数。(即arr[N - 1]为基准数)
        int lessEqualsR = -1;//小于等于基准数,区域的最右位置的索引。
        int index = 0;//当前数的索引。即当前要判断的数,判断它是小于等于基准数,还是大于基准数。
        int N = arr.length;
//        while (index < N - 1) {//这个是不对的。
        //index是索引,N是数组的个数,所以,当index=6时,N等于7时,就是数组的最后一个数,和自己比较大小。
        //那么(arr[index] <= arr[N - 1])的结果是true。所以会有一个交换的操作。把基准数换到它该在的位置。
        while (index < N) {//只要当前数的索引(指针),小于数组长度。
            if (arr[index] <= arr[N - 1]) {//1.如果当前数小于等于基准数。
                swap(arr, ++lessEqualsR, index++);//将小于等于区的再右边一个数,和当前数,交换。指针index往后跳一步,即index++。
                //上边这一句顶下边这三句。
                //swap(arr, lessEqualsR + 1, index);//将小于等于区的再右边一个数,和当前数,交换。
                //lessEqualsR++;//小于等于区,右扩一个位置。
                //index++;//指针index跳一步。
            } else {//2.如果当前数大于基准数。
                index++;//指针index直接++。不做交换操作,小于等于区,也不右扩。
            }
            printArr(arr);
        }
    }

    /**
     * 分区。结果是分为三个区域,小于区(不保证有序),等于区(都是等于基准数的,所有可以归为有序),和大于区(不保证有序)。
     *
     * @param arr 需要被分区的数组
     */
    public static void splitNum2(int[] arr) {
        int lessR = -1;//小于区的最右位置的索引。
        int N = arr.length;
        int moreL = N - 1;//大于区的最左位置的索引。
        int index = 0;//当前数的索引。将要对当前数做判断,判断它是小于基准数,还是等于,还是大于。
        while (index < moreL) {//只要当前数的索引,小于大于区的最右位置的索引。
            if (arr[index] < arr[N - 1]) {//1.如果当前数小于基准数。
                swap(arr, ++lessR, index++);//小于区的再右边一个数,和当前数,交换。(小于区右扩一个位置)。当前数跳一步(index++)。
            } else if (arr[index] > arr[N - 1]) {//2.如果当前数大于基准数。
                //大于区的再左边一个数,和当前数,交换。(大于区左扩一个位置)。当前数保持位置不变。
                //因为交换后的当前数,是一个新数,还没判断呢。所有index不能往后跳一步。
                swap(arr, --moreL, index);
            } else {//3.当前数大于基准数。
                index++;//index直接跳一步。不做交换操作。小于区不右扩,大于区也不左扩。
            }
        }
        //跳出循环后,记得:交换数组的最后一个数和大于区的最左位置的数。
        //因为最后一个数就是基准数本身,并不大于基准数。它只是一直"潜伏"在大于区内部。
        //所以在调整完小于区,等于区,和大于区后,最后记得将最后一个数,放在它该在的位置,即等于区的最后位。也就是moreL索引位置。
        swap(arr, N - 1, moreL);
    }

    //part2
    //快排(1.递归版本)
    public static void quickSort1(int[] arr) {
        if (arr == null || arr.length <= 1) {
            return;
        }
        process(arr, 0, arr.length - 1);
    }

    //使arr在L到R上,有序。
    public static void process(int[] arr, int L, int R) {
        if (L >= R) {
            return;
        }
        int[] equalsArea = partition(arr, L, R);
        process(arr, L, equalsArea[0] - 1);//equalsArea[0]:等于区域的第一个数的索引。
        process(arr, equalsArea[1] + 1, R);//equalsArea[1]:等于区域的最后一个数的索引。
    }

    /**
     * 在arr[L...R]范围上,将arr[R]作为基准数。划分出小于区,等于区,和大于区。
     *
     * @param arr 要被分区的数组
     * @param L   arr的最左索引
     * @param R   arr的最右索引
     * @return 一个只有两个元素的数组。
     * 第一个数代表:等于区域的第一个数的索引。
     * 第二个数代表:等于区域的最后一个数的索引。
     */
    public static int[] partition(int[] arr, int L, int R) {
        int lessR = L - 1;//当L等于0时,lessR等于-1,所以抽象后后,lessR等于L-1。
        int moreL = R;//当R等于N-1时,moreL等于N-1,所以抽象化后,moreL等于R。
        int index = L;//当L等于0时,index等于0,所以抽象化后,index等于L。
        while (index < moreL) {//当R等于N-1时,index<N-1,所以抽象化后,index<moreL。
            if (arr[index] < arr[R]) {//原来是:arr[index] < arr[N-1]。基准数的索引,永远是[L...R]范围上的R。
                swap(arr, ++lessR, index++);
            } else if (arr[index] > arr[R]) {//原来是:arr[index] > arr[N-1]。基准数的索引,永远是[L...R]范围上的R。
                swap(arr, --moreL, index);
            } else {
                index++;
            }
        }
        swap(arr, moreL, R);//记得将[L...R]范围上,最后索引位置的数,放在它该在的位置上。也就是归位。
        //等于区域的最左数的索引是,小于区域的最右数的索引,再+1。
        //等于区域的最右数的索引是,大于区域的最左数的索引。没有-1。因为arr[N-1]和arr[moreL]交换了,所以moreL就是等于区域的最右数的索引。
        return new int[]{lessR + 1, moreL};
    }

    //part3
    //2.快排(非递归版本)
    public static void quickSort2(int[] arr) {
        if (arr == null || arr.length <= 1) {
            return;
        }
        Stack<Job> stack = new Stack<>();//定义一个栈,用来存放任务。
        stack.push(new Job(0, arr.length - 1));//先往任务栈里添加一个任务:在[0,N-1]上排序。
        while (!stack.empty()) {//只要任务栈不是空的,就继续。
            Job cur = stack.pop();//弹出一个任务,记为当前任务。
            int[] equalsArea = partition(arr, cur.L, cur.R);//将当前任务的左边界,和右边界,作为参数,调用分区函数partition()。返回一个等于区。
            if (equalsArea[0] > cur.L) {//当等于区的第一个元素,大于当前任务的左边界,才有小于区域。
                stack.push(new Job(cur.L, equalsArea[0] - 1));//向任务栈中添加一个任务,范围是,当前任务的左边界,和等于区的第一个数的索引的,前一个位置。
            }
            if (equalsArea[1] < cur.R) {//当等于区的第二个元素,小于当前任务的有边界,才有大于区域。
                stack.push(new Job(equalsArea[1] + 1, cur.R));//向任务栈中添加一个任务,范围是,等于区的最后一个数的索引的,后一个位置,和当前任务的右边界。
            }
        }
    }

    /**
     * 自定义任务类,在[L,R]山排序。
     * 比如:最大的任务就是将数组arr,在0到arr.length-1范围上,排序。
     * new Job(0, arr.length - 1);
     * 属性L:当前任务的左边界
     * 属性R:当前任务的右边界
     */
    public static class Job {
        public int L;
        public int R;

        public Job(int left, int right) {
            L = left;
            R = right;
        }
    }

    //交换数组arr中i和j索引位置的两个数
    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    //打印数组
    public static void printArr(int[] arr) {
        for (int num : arr) {
            System.out.print(num + " ");
        }
        System.out.println();
    }

    //生成随机数组
    public static int[] generateRandomArr(int maxValue, int maxSize) {
        int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
        }
        return arr;
    }

    //复制数组
    public static int[] copyArr(int[] arr) {
        int[] arr2 = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            arr2[i] = arr[i];
        }
        return arr2;
    }

    //判断两个数组是否相等
    public static boolean isEquals(int[] arr1, int[] arr2) {
        if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
            return false;
        }
        if (arr1 == null && arr2 == null) {
            return true;
        }
        if (arr1.length != arr2.length) {
            return false;
        }
        for (int i = 0; i < arr1.length; i++) {
            if (arr1[i] != arr2[i]) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
//        int[] arr = {5, 1, 3, 2, 4, 6, 5, 8, 7, 9, 3, 5};
//        printArr(arr);
//        splitNum1(arr);
////        splitNum2(arr);
//        printArr(arr);

        int testTimes = 100000;
        int maxValue = 10000;
        int maxSize = 100;
        boolean flag = true;
        System.out.println("test start!");
        for (int i = 0; i < testTimes; i++) {
            int[] arr0 = generateRandomArr(maxValue, maxSize);
            int[] arr1 = copyArr(arr0);
            int[] arr2 = copyArr(arr0);
            quickSort1(arr1);
            quickSort2(arr2);//快排,非递归。
            Arrays.sort(arr0);
            if (!isEquals(arr0, arr1) || !isEquals(arr0, arr2)) {
                System.out.println("Oops1!");
                flag = false;
                break;
            }
        }
        System.out.println("test end!");
        System.out.println(flag ? "Nice!" : "Oops2!");
    }
}

 

标签:index,arr,递归,索引,int,分区,快排,等于,public
来源: https://www.cnblogs.com/TheFloorIsNotTooHot/p/16622674.html