当数组可能包含或不包含pivot元素时,就地分区

时间:2011-10-12 21:12:52

标签: java algorithm sorting quicksort data-partitioning

是否有in-place partitioning算法(Quicksort实现中使用的那种算法)不依赖于数组中存在的pivot元素?

换句话说,数组元素必须按以下顺序排列:

  • 小于枢轴的元素(如果有的话)
  • 等于枢轴的元素(如果有的话)
  • 大于枢轴的元素(如果有的话)

如果它恰好存在于数组中,它仍然必须返回pivot元素的索引(排序后),否则返回特殊值;这可以是索引的one's complement,其中可以插入元素以维护顺序(如Java标准binary search函数的返回值。)

我看到的实现需要将pivot元素的索引作为参数给出(或者总是在数组的开头。)不幸的是我事先并不知道数组中是否存在pivot(或者它在数组中的位置。)


修改(回复meriton的评论):我们还可以假设满足以下条件之一:

  • 阵列长度< 2,
  • 至少有一个元素是&lt; = pivot 至少有一个元素是&gt; = pivot。

数组中可能存在重复值(包括数据透视值的重复项。)

3 个答案:

答案 0 :(得分:1)

这是一个有趣的问题。您可以通过数组的单个顺序传递来完成。 C#中的代码示例,如下所示。它假定一个名为apivot值的整数数组。

// Skip initial items that are < pivot
int iInsert = 0;
while (iInsert < a.Length && a[iInsert] < pivot)
{
    ++iInsert;
}
// Skip items that are = pivot
int numPivot = 0;
while (iInsert < a.Length && a[iInsert] == pivot)
{
    ++iInsert;
    ++numPivot;
}

int iCurrent = iInsert;
// Items will be added AFTER iInsert.
// Note that iInsert can be -1.
--iInsert;
while (iCurrent < a.Length)
{
    if (a[iCurrent] < pivot)
    {
        if (numPivot == 0)
        {
            ++iInsert;
            int temp = a[iInsert];
            a[iInsert] = a[iCurrent];
            a[iCurrent] = temp;
        }
        else
        {
            ++iInsert;
            int temp = a[iInsert];
            a[iInsert - numPivot] = a[iCurrent];
            a[iCurrent] = temp;
            a[iInsert] = pivot;
        }
    }
    else if (a[iCurrent] == pivot)
    {
        ++iInsert;
        int temp = a[iInsert];
        a[iInsert] = pivot;
        a[iCurrent] = temp;
        ++numPivot;
    }
    ++iCurrent;
}

int firstPivot = iInsert - numPivot + 1;

可能有一些优化机会。

这种方法的有趣之处在于,您可以轻松地将其调整为从传入数据流构建。你不必知道有多少物品要来。只需使用可以动态调整大小的列表。当最后一个项目进入时,您的列表的顺序正确。

答案 1 :(得分:0)

你很幸运:上个月编码kata是为了实现快速排序。这就是我想出的:

/**
 * Sorts the elements with indices i such that l <= i < r
 */
private static void qsort(int[] a, int left, int right) {
    int l = left;
    int r = right - 1;

    if (l >= r) {
        return;
    }

    int pivot = a[l];
    l++;
    for (;;) {
        while (l <= r && a[l] <= pivot) l++;
        while (a[r] > pivot  && l < r) r--;

        if (l < r) {
            int t = a[l];
            a[l] = a[r];
            a[r] = t;
        } else {
            break;
        }
    }
    l--;
    a[left] = a[l];
    a[l] = pivot;

    qsort(a, left, l);
    qsort(a, r, right);
}

如您所见,该算法仅使用数据透视图的原始位置来查找数据透视表的值,并将数据透视交换到分区之间的索引。

如果我们不知道枢轴存在,我们只需将值等于枢轴值等于值&lt;如果不是将三个组中的元素分区小于,等于和大于枢轴,我们将分成两个小于或等于pivot的组,并且大于pivot,并递归到每个分区上。这个解决方案是正确的。

然而,终止将不再得到保证:已知QuickSort会终止,因为每个递归步骤都使用比调用者更短的数组切片,并且已知数组切片更短,因为它们不包含pivot元素。对于您修改的算法,情况不再适用。实际上,很容易看出终止将取决于您的支点价值选择策略。

答案 2 :(得分:0)

另一种可能性是将方法拆分为两个,一个分区为[&lt; = pivot,&gt; pivot]和另一个将该结果的第一部分划分为[&lt; pivot,&gt; = pivot]。

public static int partitionLE(double[] a, int left, int right, double pivot) {
    double x, y;
    if (left >= right) return left;
    for (;;) {
        while ((x = a[left]) <= pivot) {
            if (++ left >= right) return left;
        }
        while ((y = a[right-1]) > pivot) {
            if (left >= -- right) return left;
        }
        if (left < right) {
            a[left] = y;
            a[right-1] = x;
        } else {
            return left;
        }
    }
}
public static int partitionLT(double[] a, int left, int right, double pivot) {
    double x, y;
    if (left >= right) return left;
    for (;;) {
        while ((x = a[left]) < pivot) {
            if (++ left >= right) return left;
        }
        while ((y = a[right-1]) >= pivot) {
            if (left >= -- right) return left;
        }
        if (left < right) {
            a[left] = y;
            a[right-1] = x;
        } else {
            return left;
        }
    }
}
public static int partition(double[] a, int left, int right, double pivot) {
    int lastP = partitionLE(a, left, right, pivot);
    int firstP = partitionLT(a, left, lastP, pivot);
    if (firstP < lastP) {
        return firstP;
    } else {
        return ~firstP;
    }
}