3

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.)

Community
  • 1
  • 1
finnw
  • 47,861
  • 24
  • 143
  • 221
  • What do you define as "the pivot"? How do you decide if the pivot "exists" or not? – flight Oct 12 '11 at 21:16
  • @quasiverse, The pivot *value* is given, you just don't know where it is in the array (and it should not require a separate pass to search for it.) – finnw Oct 12 '11 at 21:18

3 Answers3

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.

Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
0

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.

meriton
  • 68,356
  • 14
  • 108
  • 175
  • It will terminate as long as you keep the distinction between `< pivot` and `<= pivot`, i.e. keep the equal-to-pivot values in their own segment and do not recurse into that segment. – finnw Oct 13 '11 at 17:59
  • No, that distinction is irrelevant if there might not be a value equal to the pivot value. If the pivot value is less than, or greater than, all elements in the segment, and the choice of pivot only depends on the segment, the recursion will not terminate. – meriton Oct 13 '11 at 18:04
  • Ah, there is actually another constraint that prohibits that situation. I will add it to the question. – finnw Oct 13 '11 at 18:10
0

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;
    }
}
finnw
  • 47,861
  • 24
  • 143
  • 221