三值策略的中位数
Posted
技术标签:
【中文标题】三值策略的中位数【英文标题】:median of three values strategy 【发布时间】:2011-09-26 18:34:41 【问题描述】:快速排序中选择枢轴值的三种策略的中位数是多少?
我正在网上阅读它,但我无法弄清楚它到底是什么?以及它比随机快速排序更好的地方。
【问题讨论】:
我在这里读过***.com/questions/6111714/quicksort-pivot 【参考方案1】:三的中位数让你查看数组的第一个、中间和最后一个元素,并选择这三个元素的中位数作为枢轴。
要获得三个中位数的“完整效果”,对这三个项目进行排序也很重要,而不仅仅是使用中位数作为枢轴 - 这不会影响选择的内容当前迭代中的枢轴,但可以/将影响下一次递归调用中用作枢轴的内容,这有助于限制一些初始排序的不良行为(在许多情况下结果特别糟糕的是数组已排序,除了在数组的高端(或低端的最大元素)具有最小元素。例如:
与随机选择枢轴相比:
-
它确保一种常见情况(完全排序的数据)保持最佳状态。
更难以操纵以提供最坏的情况。
PRNG 通常比较慢。
第二点可能需要更多解释。如果您使用明显的 (rand()
) 随机数生成器,那么某人很容易(在许多情况下,无论如何)安排元素,因此它会不断选择较差的枢轴。这对于像网络服务器这样的东西来说可能是一个严重的问题,它可能正在对潜在攻击者输入的数据进行排序,他们可能会通过让您的服务器浪费大量时间对数据进行排序来发动 DoS 攻击。在这种情况下,您可以使用真正的随机种子,或者您可以包含您自己的 PRNG 而不是使用 rand() - 或者您使用三的中位数,这也具有其他优点提到了。
另一方面,如果您使用足够随机的生成器(例如,硬件生成器或计数器模式下的加密),则可能更难以强制执行坏情况,而不是中位数三选。同时,实现这种级别的随机性通常会产生相当多的开销,所以除非你真的期望在这种情况下受到攻击,否则它可能不值得(如果你这样做,可能至少值得考虑保证 O(N log N) 最坏情况的替代方案,例如合并排序或堆排序。
【讨论】:
当您使用 rand() 时,别人创建病态排序是多么容易?他们如何预测 PRNG 的结果? @ordinary:rand()
通常是一个线性同余生成器。在Crypto.SE 上描述了这种生成器的一种方法。理论上,rand() 可以是一个加密安全的生成器,但这种情况很少见(至少可以这么说)。
@user2079139:你取的中位数越多,排序就越慢,所以你通常希望选择尽可能小的数字,并且仍然避免出现重大问题。
我同意 cmets 关于使用较差的 PRNG 的观点,但是在中位数 3 上强制使用最坏的情况不是容易得多吗?您只需将这三个数字设为最大,然后将下一个中位数设为次大,依此类推。
中值枢轴周围的分区不会自动对 3 个元素进行排序吗?所以没有理由对它们进行排序。【参考方案2】:
想得更快……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
请注意,通过切换b
的比较,该方法还涵盖了某些输入相等的所有情况。同样,我们重复相同的比较 a > b
与 b < a
相同,智能编译器可以重用和优化它。
中值方法更快,因为它会导致数组中的分区更均匀,因为分区是基于枢轴值的。
在使用随机选择或固定选择的最坏情况下,您会将每个数组划分为一个仅包含枢轴的数组和另一个包含其余部分的数组,从而导致 O(n²) 复杂度。
使用中值方法可以确保不会发生这种情况,但您会引入计算中值的开销。
编辑:
Benchmarks 结果显示 XOR
比 Bigger
快 32 倍,尽管我对 Bigger 做了一点优化:
您需要记住 XOR
实际上是 CPU 算术逻辑单元 (ALU) 的一个非常基本的运算符,然后虽然在 C 中它可能看起来有点 hacky,但实际上它正在编译为非常高效的 @987654337 @汇编运算符。
【讨论】:
【参考方案3】:我发现的中位数三的实现在我的快速排序中效果很好。
(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
【讨论】:
或者,交换操作可以在一行中完成,例如:arr[left], arr[right] = arr[right], arr[left]
【参考方案4】:
common/vanilla quicksort 选择最右边的元素作为枢轴。这导致它在许多情况下表现出病理表现 O(N²)。特别是排序和反向排序的集合。在这两种情况下,最右边的元素都是选择作为枢轴的最差元素。理想情况下,我认为枢轴位于分区的中间。分区应该将带有枢轴的数据分成两部分,一个低部分和一个高部分。低段低于枢轴,高段高于枢轴。
三中的中位数枢轴选择:
选择最左边、中间和最右边的元素 将它们排序到左分区、枢轴和右分区。以与常规快速排序相同的方式使用枢轴。这样可以缓解排序/反向排序输入的常见病态 O(N²)。 仍然很容易创建中间值的病态输入。但这是一种构造和恶意使用。不是自然排序。
随机支点:
选择一个随机枢轴。将其用作常规枢轴元素。如果是随机的,则不会表现出病态的 O(N²) 行为。对于通用排序,随机枢轴通常很可能是计算密集型的,因此是不可取的。如果它不是随机的(即 srand(0); ,rand(),可预测且易受与上述相同的 O(N²) 攻击。
请注意,随机枢轴不会从选择多个元素中受益。主要是因为中位数的影响已经是内在的,并且随机值比两个元素的排序在计算上更密集。
【讨论】:
【参考方案5】:想的简单……Python 示例……
def large(a,b): #求两个数中较大的一个... 如果 a > b: 返回一个 别的: 返回 b def maximum(a,b,c): #找出三个数中最大的一个... 返回更大(a,更大(b,c)) def median(a,b,c): #跳舞吧! x = 最大(a,b,c) 如果 x == a: 返回更大(b,c) 如果 x == b: 返回更大(a,c) 别的: 返回更大(a,b)【讨论】:
你不需要最大的。相反,只需将 x 设置为更大(a,更大(b,c))【参考方案6】:此策略包括确定性或随机选择三个数字,然后使用它们的中位数作为枢轴。
这样会更好,因为它降低了找到“坏”支点的可能性。
【讨论】:
如果我们使用随机数选择方法选择数字,那么在这种情况下我们必须生成三个随机数而不是一个随机快速排序,这比随机快速排序好多少? 嗯,我理解你的意思,但是生成随机数的开销如何:你不认为在我们选择单个随机数作为枢轴的情况下比生成三个随机数更好吗? 一般来说,当你选择枢轴时,选择整体中位数会给你最平衡的分割,因此最好的运行时间。但是,选择真正的中位数非常耗时。当您对 3 个数字进行采样时,您会得到比仅对 1 个数字进行采样时更好的近似中位数。通常,如果近似中位数发现启发式算法更复杂,它将对更大的数组进行更多的采样(其中回报更大),并且更少的采样对于较小的数组。 好的,选择三个数字意味着选择数组的三个随机索引。这是真的吗?否则,如果您选择三个随机值,则很有可能超出界限异常。 是的,我的意思是在数组中选择三个随机索引。【参考方案7】:我们可以通过一个例子来理解中位数三的策略,假设给定一个数组:
[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 Element
和third Element > first Element > second Element
时才会返回,并且在这两种情况下:(second - first) * (third - first) < 0
,同样的推理适用于其余两种情况。
使用第二种实现的好处是它可以有更好的运行时间。
【讨论】:
【参考方案8】:我认为仅对三个值不需要重新排列数组中的值。只需通过减法来比较它们;然后你可以决定哪一个是中值:
// javascript:
var median_of_3 = function(a, b, c)
return ((a-b)*(b-c) > -1 ? b : ((a-b)*(a-c) < 1 ? a : c));
【讨论】:
对降级发表评论会很好! 我没有投反对票,但我怀疑这是投反对票,因为问题是关于三中位数策略的问题,因为它与快速排序/快速选择有关,而不仅仅是查找三个元素的中位数。以上是关于三值策略的中位数的主要内容,如果未能解决你的问题,请参考以下文章
算法初级面试题07——前缀树应用介绍和证明贪心策略拼接字符串得到最低字典序切金条问题项目收益最大化问题随时取中位数宣讲会安排