当阵列中有很多重复项时优化QuickSort

时间:2019-02-25 04:04:05

标签: java algorithm sorting quicksort

根据以下情况,我有去年的问题:

当要排序的项目列表包含很多重复值时,我们可以通过将所有与中心点相等的值组合在一起来改进QuickSort,然后递归将左侧的那些值和左侧的那些值进行递归快速排序正确的。对分区方法进行必要的更改以实现此目的。

这是当前使用的实现:

// Quick Sort algorithm
public class QuickSort {

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

    private static void quickSort(int[] a, int i, int j) {
        if (i < j) {
            int pivotIdx= partition(a, i, j);
            quickSort(a, i, pivotIdx-1);
            quickSort(a, pivotIdx+1, j);
        }
    }

    private static void swap(int[] a, int pos1, int pos2) {
        int temp = a[pos1];
        a[pos1] = a[pos2];
        a[pos2] = temp;
    }

    private static int partition(int[] a, int i, int j) {
        // partition data items in a[i..j]
        int p = a[i]; // p is the pivot, the i-th item
        int m = i;    // Initially S1 and S2 are empty

        for (int k=i+1; k<=j; k++) { // process unknown region
            if (a[k] < p) { // case 2: put a[k] to S1
                m++;
                swap(a,k,m);
            }
        }

        swap(a,i,m); // put the pivot at the right place
        return m;    // m is the pivot's final position
    }

    public static void printArray(int[] a) {
        for (int i = 0; i < a.length; i++)
            System.out.print(a[i] + " ");
        System.out.println();
    }

    public static void main(String[] args) {
        int[] arr = { 7, 12, 3, 5, -6, 3, 8, 2, 10, -3 };

        printArray(arr);
        quickSort(arr);
        printArray(arr);
    }
}

我对这里介绍的quicksort算法有一些基本的了解,但是我真的不明白这个问题是否实际上给了我有关如何实现该算法的提示,因为我认为quicksort必须遍历列表才能做出2个分区并动态决定位置X放置枢轴的位置,在此实现中,枢轴被选择为输入数组的最左侧元素。如果动态确定此位置X,您如何精确地将等于枢轴的元素“分组”到中间,以及如何精确地改进算法?

1 个答案:

答案 0 :(得分:0)

主要思想是使用三向分区策略解决此问题。有关详细信息,请参见Dutch National Flag Problem

如果您有很多重复元素,那么您的快速排序将尝试将每个重复元素分别放置在正确的位置。但是您不需要这样做。

让我们看一下我在上述声明中声明的示例:

假设您有一个像{4,6,4,3,4,2,5,4,1,4}这样的数组。在此数组中,元素4重复5次。而且,当应用快速排序并将4放置在正确位置时,您会将数组划分为2个部分,以便左侧部分包含所有小于或等于4的元素(但没有具体说明)顺序),右侧部分包含所有大于4的元素。但这是幼稚的方法。

让我们看看如何改善这一点(假设我们有很多重复的元素)

当快速排序找到4并将其分区以将4放置在正确位置时,您还可以跟踪所有相等的元素(数组中其他相等的元素)到4),以及左侧的较小值和右侧的较大值。

因此,当进行分区而不是具有两个索引leftright时(子数组0到left包含所有小于或等于枢轴和子数组{{ 1}}至left包含所有大于支点的元素,而rightright是尚待探索的元素),您可以拥有3个索引,如下所述:

  • len(array)-1-元素小于数据透视的子数组
  • [0,left)-元素等于数据透视的子数组
  • [left, mid)-元素大于支点的子数组
  • [mid, right]-有待探索的元素。

通过这种方式,修改后的快速排序将仅进行较少次数的数据透视(具体来说,等于数组中唯一元素的数量)。因此,递归调用的数量将减少。

因此,该解决方案利用了存在大量重复项的特定输入的情况(因此,数组中具有更多重复项的元素,这种经过改进的quicksort变体将表现得更好)

给我一些代码

[right, len(array))

以上代码的输出为:

import java.util.Arrays;
import java.util.stream.IntStream;

public class QuickSort {

    public static void main(String[] args) {
        int[] arr = new int[]{2, 3, 4, 1, 2, 4, 3, 5, 6, 2, 2, 2, 1, 1, 1};
        quickSort(arr);
        System.out.print("Sorted array: ");
        Arrays.stream(arr).forEach(i -> System.out.print(i + " "));
        System.out.println();
    }

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

    private static void quickSort(int[] arr, int start, int end) {
        if (start > end)
            return; // base condition

        System.out.print("Recursive call on: ");
        IntStream
                .rangeClosed(start, end)
                .map(i -> arr[i])
                .forEach(i -> System.out.print(i + " "));
        System.out.println();

        int n = arr.length;
        if (start < 0 || start >= n || end < 0 || end >= n)
            throw new IllegalArgumentException("the indices of the array are not valid");

        int pivot = arr[end];
        /*
            [start,left) - sub-array with elements lesser than pivot
            [left, mid) - sub-array with elements equal to pivot
            [mid, right] - sub-array with elements greater than pivot
            [right, end) - elements yet to be explored.
         */
        int left = start, mid = start, right = start;

        while (right != end) {
            if (arr[right] < pivot) {
                swap(arr, left, right);
                swap(arr, mid, right);
                left++;
                right++;
                mid++;
            } else if (arr[right] == pivot) {
                swap(arr, mid, right);
                mid++;
                right++;
            } else if (arr[right] > pivot) {
                right++;
            }
        }

        swap(arr, mid, right);
        System.out.println("Placed " + pivot + " at it's correct position");
        System.out.println();
        quickSort(arr, start, left - 1);
        quickSort(arr, mid + 1, end);
    }

    private static void swap(int[] arr, int a, int b) {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
}

上面的输出清楚地表明,将枢轴放置在正确的位置后,我们在两个不同的数组上递归,但是这两个数组中没有一个具有先前的枢轴。 (这是优化)