【问题标题】:Quick sort "lomuto" partition algorithm: alternate implementation analysis快速排序“lomuto”分区算法:替代实现分析
【发布时间】:2021-06-12 21:55:15
【问题描述】:

请考虑 Cormen、Leiserson、Rivest 和 Stein 的经典算法教科书 Introduction to Algorithms 中的快速排序“lomuto partition”方案。

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),但是就数字原子操作而言,比如一次迭代中的赋值和比较呢?它对大规模案例的效率有显着影响吗? 为什么这本书的算法不起作用?它缺少什么? 我也很感激关于如何进一步简化我的代码的建议。

其他后续信息:

  • 我已经用python实现了本书的代码。
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) 用于第三个参数,则会出现溢出错误。

  • 至于我的 C 代码使用我的算法并且可以工作,这是我的头文件,你可以测试一下。
#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

【问题讨论】:

  • 这样:'在实现这个算法后,我发现它不能正常工作'你的意思是你注意到一些特定的数组没有被正确处理——对吗?如果是这样,你能显示致命的数组吗?
  • 如果你能展示你的实现也很好。
  • 这条评论有误:// end index = array length - 1.
  • 不,这是正确的,我已经实现了数组长度为-1。由于是前向比较,否则最后一次迭代会溢出。
  • 我相信您的代码不起作用。但我不相信这是 Lomuto 算法。成千上万的程序员从书中学到,你是第一个遇到错误的机会微乎其微。想说服我吗?展示你的实现和数据。

标签: c algorithm sorting quicksort


【解决方案1】:

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

这两个例程都必须进行 N-1 步遍历数组和 N-1 次比较,以检查每个元素并识别小于或小于或等于枢轴的元素。原始例程进行的比较次数正是如此。但是您的代码做了两倍的工作:在循环的每次迭代中,它都会比较 array[i] 和 pivotValue 以及 array[i] 和 array[i + 1]。

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

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

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


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

【讨论】:

  • 是的,我终于注意到了。由于我缺乏经验,我将伪代码运行循环 to 读为&lt;,它应该是==。
  • 请您谈谈您如何评价该算法的难度(基于初学者、中等或困难)?分区方案是大多数有经验的数学家都能想出的模式吗?我问这个是因为我最初尝试自己进行快速排序,但发现它非常困难。但在我的研究过程中,我发现了一篇 Wikipedia 文章,其中提到 Tony Hoare 最初并没有发布他的算法,因为他认为它太简单了。有点基于意见的问题,但我只是想知道。
  • @mindoverflow 恕我直言,这非常简单——一旦你学会了使用invariants。不变量是一个表达式,其真实值由代码保留。这有助于我们推理代码。在这种情况下,它是“直到索引pivotIndex 的数组的初始段包含小于在开头和i 之间找到的枢轴的所有项目”。随着“i”的增加,我们可以通过归纳表明每个符合条件的项目都被添加到该段中。一旦我们还证明 i 扫描了 整个 数组(您的原始代码未能做到这一点),我们就知道该例程执行了我们的预期。
  • 感谢您的宝贵见解。这是我第一次听说不变量。奇怪的是,在算法设计或离散数学中从未遇到过这种情况!我会进一步调查。再次感谢您。
  • @mindoverflow 请参阅我添加的注释,关于您的代码与 Lomuto 的大 O 估计值。
【解决方案2】:

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

for j in range(p, r):

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

你的例子的结果

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-10-18
    • 1970-01-01
    • 2020-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-28
    相关资源
    最近更新 更多