快速排序"lomuto"分区算法:交替实现分析

Quick sort "lomuto" partition algorithm: alternate implementation analysis

请考虑经典算法教科书中的快速排序“lomuto 分区”方案算法导论作者Cormen、Leiserson、Rivest & Stein.

PARTITION(A, p, r)
x = A[r]
i = p - 1
for j = p to r - 1
    if A[j] <= x
        i = i + 1
        exchange A[i] with A[j]
exchange A[i + 1] with A[r]
return i + 1

实现这个算法后,我发现它并不能正常工作,最多也就是差1个元素。 然后我开始计算解决方案并最终得出以下有效的 C 代码。我已经在代码中评论了算法的工作原理。

int quickSortPartition(int *array, int beginIndex, int endIndex) // end index = array length - 1.
{
    int pivotIndex = beginIndex; // first element as pivot.
    int pivotValue = array[pivotIndex]; // initial pivot value.
    int i = beginIndex + 1; // start loop with i being 2nd index.
    while(i < endIndex) // loop running until end of array.
    {
        if(array[i] > array[i + 1]) // comparing the 2 elements ahead of pivot.
        {
            swap(array, i, i + 1); // swapping the 2 elements if prev element > next element.
        }
        if(array[i] < pivotValue) // comparing element at pivot index with the next index element.
        {
            swap(array, pivotIndex, i); // swapping if next element is less than element at pivot index.
            ++pivotIndex; // incrementing pivot index by 1 ONLY when swap occurs.
        }
        ++i; // drive loop.
    }
    if(array[pivotIndex] > array[pivotIndex + 1]) // at the very end, compare whether the element to the right of pivot is > element at pivot index.
    {
        swap(array, pivotIndex, pivotIndex + 1); // swapping if next element is less than element at pivot index.
        ++pivotIndex; // incrementing pivot index by 1 ONLY when swap occurs.
    }
    return pivotIndex; // returning new pivot index.
}

我的实现效率是否低于书中所谓的“lomuto 分区”?当然,它们都是 O(n),但是就一次迭代中的赋值和比较等原子操作而言呢?它对大规模案例的效率有显着影响吗? 为什么书上的算法不行?它缺少什么部分? 我也很感激有关如何进一步简化我的代码的建议。

其他后续信息:

def quicksort(A, p, r):
    if p < r:
        q = partition(A, p, r)
        quicksort(A, p, q - 1)
        quicksort(A, q + 1, r)

def partition(A, p, r):
    x = A[r]
    i = p - 1
    for j in range(p, r - 1):
        if A[j] <= x:
            i = i + 1
            A[i], A[j] = A[j], A[i]
    A[i + 1], A[r] = A[r], A[i + 1]
    return i + 1

array = [2, 55, 43, 12, 65, 72, 41, 18, 6]
print(array)
quicksort(array, 0, len(array) - 1)
print(array)

结果:

unsorted: [2, 55, 43, 12, 65, 72, 41, 18, 6]
sorted: [2, 6, 41, 43, 12, 55, 65, 72, 18]

如您所见,排序失败。 另外,如果我将 len(array) 用作第三个参数,则会出现溢出错误。

#ifndef A1_H
#define A1_H

// am1n

#include<stdio.h>
#include<stdlib.h>

void printArray(int *array, int arrayLength)
{
    int i = 0;
    --arrayLength;
    for(i = 0; i < arrayLength; ++i)
    {
        printf("%d, ", array[i]);
    }
    printf("%d.\n", array[i]);
}

void swap(int *array, int a, int b)
{
    if(array[a] != array[b])
    {
        int tempValue = array[a];
        array[a] = array[b];
        array[b] = tempValue;
    }
}

int fastSortPartition(int *array, int beginIndex, int endIndex) // end index = array length - 1.
{
    int pivotIndex = beginIndex; // first element as pivot.
    int pivotValue = array[pivotIndex]; // initial pivot value.
    int i = beginIndex + 1; // start loop with i being 2nd index.
    while(i < endIndex) // loop running until end of array.
    {
        if(array[i] > array[i + 1]) // comparing the 2 elements ahead of pivot.
        {
            swap(array, i, i + 1); // swapping the 2 elements if prev element > next element.
        }
        if(array[i] < pivotValue) // comparing element at pivot index with the next index element.
        {
            swap(array, pivotIndex, i); // swapping if next element is less than element at pivot index.
            ++pivotIndex; // incrementing pivot index by 1 ONLY when swap occurs.
        }
        ++i; // drive loop.
    }
    if(array[pivotIndex] > array[pivotIndex + 1]) // at the very end, compare whether the element to the right of pivot is > element at pivot index.
    {
        swap(array, pivotIndex, pivotIndex + 1); // swapping if next element is less than element at pivot index.
        ++pivotIndex; // incrementing pivot index by 1 ONLY when swap occurs.
    }
    return pivotIndex; // returning new pivot index.
}

void fastSort(int *array, int beginIndex, int endIndex)
{
    if(beginIndex < endIndex)
    {
        int pivotIndex = fastSortPartition(array, beginIndex, endIndex);
        fastSort(array, beginIndex, pivotIndex - 1);
        fastSort(array, pivotIndex + 1, endIndex);
    }
}

#endif

将循环上限修改为合适的值即可解决

for j in range(p, r):

注意Python范围不包括右边界,pivot保持在末尾直到分区结束

您的示例的结果

[2, 6, 12, 18, 41, 43, 55, 65, 72]

令 N 为正在处理的子数组中的元素数。
令 L 为最终落入数组左侧部分的项目数 - 小于或等于原始算法中的主元,小于代码中的主元加上主元本身。

这两个例程都必须对数组进行 N-1 步和 N-1 次比较,以检查每个元素并识别那些小于或小于或等于主元的元素。原始例程进行的比较次数恰好相同。但是你的代码做了两倍的事情:在循环的每次迭代中,它比较 array[i]pivotValue AND array[i]array[i + 1].

这两个例程还必须几乎恰好进行 L 次交换以将这些较小的项目组合在一起,再加上最多两次交换以暂时将枢轴移开并放回原位。但是您的代码可能会进行 N–1 次额外交换,以将 'big' 元素向右推,但没有显着增益。

所以答案是:不,你提出的代码肯定没有比原始算法更有效,无论是在多次比较还是多次交换中。

您的代码所做的工作(基本操作)大约是 Lomuto 方法的两倍。但这只是一个比例因子;渐进地考虑它们都是线性的,O(N)。


事实证明我是对的:您的实现不是 Lomuto 算法 – 由于 for j in range() 的不正确使用,您没有按照原始例程的要求测试数组的最后一项。