三个值策略的中位数

时间:2011-09-26 18:34:42

标签: algorithm sorting quicksort

快速排序中选择枢轴值的三种策略的中位数是什么?

我正在网上阅读,但我无法弄明白究竟是什么?以及它如何比随机快速排序更好。

8 个答案:

答案 0 :(得分:30)

您可以查看数组的第一个,中间和最后一个元素的中位数,并选择这三个元素的中位数作为支点。

要获得三个中位数的“完全效果”,排序这三个项目也很重要,而不仅仅是使用中位数作为支点 - 这不会影响选择的内容在当前迭代中的枢轴,但是可以/将影响在下一个递归调用中用作枢轴的内容,这有助于限制一些初始排序的不良行为(在许多情况下结果特别糟糕的是数组这是排序的,除了在数组的高端有一个最小的元素(或在低端有最大的元素)。例如:

与随机选择枢轴相比:

  1. 确保一个常见案例(完全排序的数据)保持最佳状态。
  2. 操纵最坏的情况更难。
  3. PRNG通常相对较慢。
  4. 第二点可能有更多解释。如果你使用了显而易见的(rand())随机数生成器,那么对于某人来说,安排这些元素是相当容易的(对很多情况而言),所以它会不断地选择不好的枢轴。这可能是一个严重的问题,例如Web服务器可能正在排序潜在攻击者输入的数据,他们可能通过让您的服务器浪费大量时间对数据进行排序来安装DoS攻击。在这种情况下,你可以使用真正的随机种子,或者你可以包含你自己的PRNG而不是使用rand() - 或者你使用三个中位数,这也有其他优点提及。

    另一方面,如果你使用一个足够随机的发生器(例如,硬件发生器或计数器模式下的加密),它可能更多难以强迫坏情况而不是中位数三个选择。同时,实现这种随机性通常会带来相当大的开销,所以除非你真的希望在这种情况下受到攻击,否则它可能是不值得的(如果你这样做,它可能值得至少考虑一个保证O(N log N)最坏情况的替代方案,例如合并排序或堆排序。

答案 1 :(得分:11)

我发现三个中位数的实现在我的快速排序中很有效。

(Python)
# Get the median of three of the array, changing the array as you do.
# arr = Data Structure (List)
# left = Left most index into list to find MOT on.
# right = Right most index into list to find MOT on

def MedianOfThree(arr, left, right):
    mid = (left + right)/2
    if arr[right] < arr[left]:
        Swap(arr, left, right)        
    if arr[mid] < arr[left]:
        Swap(arr, mid, left)
    if arr[right] < arr[mid]:
        Swap(arr, right, mid)
    return mid

# Generic Swap for manipulating list data.
def Swap(arr, left, right):
    temp = arr[left]
    arr[left] = arr[right]
    arr[right] = temp    

答案 2 :(得分:4)

此策略包括确定性地或随机地选择三个数字,然后使用它们的中位数作为枢轴。

这会更好,因为它降低了发现“坏”支点的可能性。

答案 3 :(得分:2)

Common / vanilla quicksort 选择最右边的元素作为枢轴。这导致它在许多情况下表现出病理性能O(N 2)。特别是已排序和反向排序的集合。在这两种情况下,最右边的元素是选择作为枢轴的最差元素。理想情况下,在分区过程中我认为是枢轴。分区应该将数据与枢轴分成两个部分,即低部分和高部分。低截面低于枢轴,高截面更高。

三个中位数数据透视选择:

  • 选择最左边,中间和最右边的元素
  • 将它们分配到左侧分区,枢轴和右侧分区。以与常规快速排序相同的方式使用数据透视。

这样可以减轻排序/反向排序输入的常见病态O(N²)。 创建三个中位数的病理输入仍然很容易。但它是一种构造和恶意使用。不是自然的顺序。

随机支点:

  • 选择随机数据透视。将其用作常规枢轴元素。

如果是随机的,则不会出现病态O(N²)行为。对于通用类型,随机数据通常很可能是计算密集型的,因此是不合需要的。如果它不是随机的(即srand(0);, rand(),可预测且容易受到与上述相同的O(N²)攻击。

请注意,随机数据 不会因选择多个元素而受益。主要是因为中位数的影响已经是内在的,并且随机值比两个元素的排序更加计算密集。

答案 4 :(得分:2)

想想简单...... Python示例......

def bigger(a,b): #Find the bigger of two numbers ...
    if a > b:
        return a
    else:
        return b

def biggest(a,b,c): #Find the biggest of three numbers ...
    return bigger(a,bigger(b,c))

def median(a,b,c): #Just dance!
    x = biggest(a,b,c)
    if x == a:
        return bigger(b,c)
    if x == b:
        return bigger(a,c)
    else:
        return bigger(a,b)

答案 5 :(得分:1)

我们可以通过一个例子来理解三个中值的策略,假设我们得到一个数组:

[8, 2, 4, 5, 7, 1]

所以最左边的元素是8,最右边的元素是1。中间元素是4,因为对于任何长度 2k 的数组,我们将选择 k 元素。

然后我们按升序或降序对这三个元素进行排序,这给了我们:

[1, 4, 8]

因此,中位数为4。我们使用4作为我们的支点。

在实施方面,我们可以:

// javascript
function findMedianOfThree(array) {
    var len = array.length;
    var firstElement = array[0];          
    var lastElement = array[len-1];
    var middleIndex = len%2 ? (len-1)/2 : (len/2)-1;
    var middleElement = array[middleIndex];
    var sortedArray = [firstElement, lastElement, middleElement].sort(function(a, b) {
        return a < b; //descending order in this case
    });
    return sortedArray[1];
}

实现它的另一种方式受到@kwrl的启发,我想更清楚地解释一下:

    // javascript
    function findMedian(first, second, third) {
        if ((second - first) * (third - first) < 0) { 
            return first;
        }else if ((first - second) * (third - second) < 0) {
            return second;
        }else if ((first - third)*(second - third) < 0) {
            return third;
        }
    }
    function findMedianOfThree(array) {
        var len = array.length;
        var firstElement = array[0];          
        var lastElement = array[len-1];
        var middleIndex = len%2 ? (len-1)/2 : (len/2)-1;
        var middleElement = array[middleIndex];
        var medianValue = findMedian(firstElement, lastElement, middleElement);
        return medianValue;
    }

考虑函数findMedian,第一个元素仅在second Element > first Element > third Elementthird Element > first Element > second Element时返回,在两种情况下:(second - first) * (third - first) < 0,同样的推理适用于其余两个案例。

使用第二个实现的好处是它可以有更好的运行时间。

答案 6 :(得分:0)

想更快... C示例...

int medianThree(int a, int b, int c) {
    if ((a > b) != (a > c)) 
        return a;
    else if ((b > a) != (b > c)) 
        return b;
    else
        return c;
}

这类似于XOR运算符。所以你会读:

  • a是否大于其他一个? return a
  • b是否大于其他一个? return b
  • 如果以上都不是:return c

中值方法更快,因为它会导致数组中的分区更加均匀,因为分区是基于枢轴值的。

在最坏的情况下,使用随机选择或固定选择,您会将每个数组划分为仅包含数据透视图的数组和包含其余数组的另一个数组,从而导致O(n²)复杂性。

使用中位数方法可以确保不会发生这种情况,而是引入了计算中位数的开销。

编辑:

Benchmarks结果显示XORBigger快24倍,尽管我对Bigger进行了一些优化:

Plot demonstrating benchmarks

答案 7 :(得分:-1)

我认为只需三个值就不需要重新排列数组中的值。只需通过减去比较所有这些;然后你可以决定哪一个是中值:

// javascript:
var median_of_3 = function(a, b, c) {
    return ((a-b)*(b-c) > -1 ? b : ((a-b)*(a-c) < 1 ? a : c));
}