问题
Is there an in-place partitioning algorithm (of the kind used in a Quicksort implementation) that does not rely on the pivot element being present in the array?
In other words, the array elements must be arranged in this order:
- Elements less than the pivot (if any)
- Elements equal to the pivot (if any)
- Elements greater than the pivot (if any)
It must still return the index (after sorting) of the pivot element if it happens to be present in the array, or a special value if not; This could be the one's complement of an index where the element could be inserted to maintain the order (like the return value of Java's standard binary search function.)
The implementations I have seen require the index of the pivot element to be given as a parameter (or always to be at the start of the array.) Unfortunately I do not know in advance whether the pivot is present in the array (or where it is in the array.)
Edit (in reply to meriton's comments): We can also assume that one of the following conditions is true:
- The array length is < 2, or
- At least one element is <= pivot and at least one element is >= pivot.
There may be duplicate values in the array (including duplicates of the pivot value.)
回答1:
This was an interesting problem. You can do it with a single sequential pass through the array. Code example in C#, below. It assumes an array of integers called a
, and a pivot
value.
// 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;
There are probably some optimization opportunities.
The interesting thing about this approach is that you could easily adapt it to build from a stream of incoming data. You wouldn't have to know how many items are coming. Just use a list that can be resized dynamically. When the last item comes in, your list is in the proper order.
回答2:
You're in luck: Last months coding kata was to implement quicksort. Here's what I came up with:
/**
* 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);
}
As you can see, the algorithm uses the pivot's original location only to find the value of the pivot, and to swap the pivot to the index between the partitions.
If we don't know that the pivot exists, we would simply treat values equal to pivot like values < pivot, i.e. rather than partitioning elements in the three groups less than, equal to, and greater than the pivot, we'd partition into the two groups less or equal to pivot, and greater than pivot, and recurse on each of those partitions. This solution would be correct.
However, termination would no longer be assured: QuickSort is known to terminate because each recursion step uses works on a shorter array slice than its caller, and the array slices are known to be shorter because they don't contain the pivot element. That is no longer true for your modified algorithm. Indeed, it is easy to see that termination will depend on your pivot value selection strategy.
回答3:
Another possibility is to split the method into two, one that partitions into [<= pivot, > pivot] and another that partitions the first part of that result into [< pivot, >= 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;
}
}
来源:https://stackoverflow.com/questions/7746662/in-place-partition-when-the-array-may-or-may-not-contain-the-pivot-element