算法:使用$ O(n \ log n)$中的中位数和1比较进行排序

时间:2013-09-29 15:56:52

标签: arrays algorithm sorting

我正在尝试编写一个排序算法,它采用输入数组并生成排序数组。以下是算法的约束。

  1. 时间复杂度= $ O(n \ log n)$
  2. 整个算法只进行一次比较操作。
  3. 我们有一个函数可以为一组三个元素提供中位数。
  4. 我尝试过寻找解决方案,以下是我的结果。

    1. 我们使用中值函数来获得最小和最大的对。 示例:给出一个数组A [1..n],我们得到前三个元素的中位数,让我们称它为一组,我们收到一个$ Median $。在下一次迭代中,我们从集合中删除接收到的中值,并将下一个元素添加到集合中,然后再次调用中值函数。在长度上重复此步骤会为整个阵列生成一对最大和最小元素。

    2. 我们使用这一对,使用比较操作并将它们放在位置A [0]& A [N-1]。

    3. 我们在数组A [1..n-2]上重复相同的操作,得到另一对最大和最小的。

    4. 我们采用A [0]和新收到的对的中位数。

    5. 中位数值位于A [1]。
    6. 我们采用A [n-1]和新收到的对的中位数。
    7. 中位数值位于A [n-2]。
    8. 重复步骤3~7以获得排序数组。

      该算法满足条件2& 3而不是时间的复杂性。我希望有人能提供一些指导如何进一步。

2 个答案:

答案 0 :(得分:1)

Quicksort(以相反顺序呈现)的工作方式如下。假设你有一个数组:

 [1, 4, 5, 2, 3]

摘要中的Quicksort基本上是从左侧和右侧向阵列中间滑动。当我们向内滑动时,我们想要交换物品,使大件物品向右移动,小物件向左移动。最终我们应该有一个数组,其中所有小东西都在左边,所有大的东西都在右边。

此过程的结果保证将一个元素放置在正确的位置(因为它左边的所有内容都会更小,右边的所有内容都会更大,所以它必须处于正确的位置)。该值称为pivot。快速排序的第一步是确保枢轴位于正确的位置。

我们这样做的方法是选择一个随机元素作为我们的pivot - 我们要放入其中的项目是正确的位置。对于这个简单的例子,我们只使用最后一个数字(3)pivot是我们的比较值。

一旦我们选择了pivot /比较值,我们就会监控最左边的元素(1)和最右边的元素(3)。我们称之为left-pointerright-pointerleft-pointers作业将向数组中间滑动,当它找到大于pivot的内容时停止。 right指针执行相同的操作,但它向内滑动,查找 less 的值,而不是pivot。在代码中:

while (true) {
    while (array[++left] < pivot);
    while (array[--right] > pivot) if (left == right) break;

    if (left >= right) break;           // If the pointers meet then exit the loop
    swap(array[left], array[right]);    // Swap the values otherwise.
}

因此,在上面的示例中,当left-pointer点击(4)时,它会识别出高于我们的枢轴元素并停止移动。右侧枢轴从右侧执行相同的操作,但在到达(2)时停止,因为它比<{1}} 更低。当双方都停止时,我们会进行交换,所以我们最终得到:

pivot

请注意,我们正在接近排序。我们继续向内移动两个指针,直到它们都指向相同的元素,或者它们交叉 - 以先到者为准。当发生这种情况时,我们做出最后一步,即用[1, 2, 5, 4, 3] 所指向的任何点替换枢轴元素(3),在这种情况下将left/right-pointers,因为它们都是(5)在中间停下来。然后我们交换,以便得到:

[1, 2, 3, 4, 5] 
(Notice that we swap the original pivot (3) with the value pointed to by both sides (5))

整个过程称为分区。在代码中它看起来像这样:

int partition(int *array, int lBound, int rBound) {
    int pivot = array[rBound];          // Use the last element as a pivot
    int left = lBound - 1;              // Point to one before the first element
    int right = rBound;             // Point to the last element;

    // We'll break out of the loop explicity
    while (true) {

        while (array[++left] < pivot);
        while (array[--right] > pivot) if (left == right) break;

        if (left >= right) break;    // If the pointers meet then exit the loop
        swap(array[left], array[right]);    // Swap the pointers otherwise.
    }

    swap(array[left], array[rBound]);   // Move the pivot item into its correct place
    return left;    // The left element is now in the right place
}

重要的是要注意尽管在此示例中分区步骤完全对我们的数组进行了排序,但通常不是分区步骤的。 paritition步骤的目的是将一个元素放入正确的位置,并确保该元素的剩余部分 less ,右边的所有内容都是。或者换句话说,将pivot值移动到正确的位置,然后保证枢轴的所有内容都小于它,并且右边的所有内容都更大。因此,尽管在此示例中数组已完全排序,但通常我们只能保证一个项和一个项仅位于正确的位置(以及左侧和右边分别是更大/更小)。这就是上面的partition方法返回left的原因,因为它告诉调用函数这个一个元素位于正确的位置(并且数组已被正确分区)。

如果我们从像:

这样的数组开始
[1, 7, 5, 4, 2, 9, 3]

然后分区步骤将返回如下内容:

[1, 3, 2, [4], 7, 5, 9]

其中[4]是保证在正确位置的唯一值,但左边的所有内容都小于[4],右边的所有内容都是更大(尽管不是必须排序!)。

第二步是递归执行此步骤。也就是说,如果我们可以将一个元素放入正确的位置,那么我们应该能够最终将所有项目放入正确的位置。这是quicksort函数。在代码中:

int *quicksort(int *array, int lBound, int rBound) {
    if (lBound >= rBound) return array;   // If the array is size 1 or less - return.

    int pivot = partition(array, lBound, rBound);   // Split the array in two. 
    quicksort(array, lBound, pivot - 1);    // Sort the left size. (Recursive)
    quicksort(array, pivot + 1, rBound);    // Sort the right side. (Recursive) 

    return array;
}

请注意,第一步是确保我们的数组面至少为2.处理小于此值的任何内容都没有意义,因此如果不满足该条件则返回。下一步是调用我们的分区函数,它将根据上面概述的过程拆分数组。一旦我们知道阵列有一个位置正确的元素,我们只需再次调用quicksort,但这次是在枢轴的左侧,然后再次在枢轴的右侧。 请注意,我们不包含数据透视,因为分区可以保证将其放入正确的位置!

如果我们继续以递归方式调用quicksort,最终我们会将数组减半并对其进行分区,直到得到size-one数组(根据定义已经排序)。所以我们分区,然后减半,分区,减半等,直到整个数组被排序(到位)。这给了我们O(n lg(n))时间的排序。酷!

以下是一个使用它的简单示例:

int main() {
    int array [] {1, 0, 2, 9, 3, 8, 4, 7, 5, 6};

    quicksort(array, 0, 9); // Sort from zero to 9.

    // Display the results
    for (int index = 0; index != 10; ++index) {
        cout << array[index] << endl;
    }

    return 0;
}

可在此处找到良好的视觉演示:http://www.youtube.com/watch?v=Z5nSXTnD1I4

答案 1 :(得分:0)

步骤1和2确实是正确解决方案的第一步。但是,一旦你知道最小和最大的元素,中位数oracle 就是比较oracle;如果您想将a[i]a[j]进行比较,那么a[i] < a[j]恰好是a[i] = median(a[i], a[j], a[0]),其中a[0]是最小元素。所以你可以直接进行快速排序或合并或者什么样的运动。