对已经排序的数组进行快速排序
Quicksort to already sorted array
本题中:https://www.quora.com/What-is-randomized-quicksort
Alejo Hausner 告诉:快速排序的成本,在最坏的情况下,即
Ironically, if you apply quicksort to an array that is already sorted, you will get probably get this costly behavior
我无法得到它。谁能给我解释一下。
https://www.quora.com/What-will-be-the-complexity-of-quick-sort-if-array-is-already-sorted 可能是这个问题的答案,但这并没有让我得到完整的回应。
根据实施方式,有多种 'common' 方法来选择枢轴。
一般来说 'unsorted' 来源没有好坏之分。
所以有些实现只是把第一个元素作为枢轴。
在已经排序的源的情况下,这会导致最差的主元,因为最短的间隔将始终为空。
-> 递归步骤 = O(n) 而不是所需的 O(log n)。
这导致 O(n²) 复杂度,这对排序非常不利。
随机选择枢轴可以避免这种行为。随机选择的主元极不可能在每次递归中都具有如上所述的相同不良特征。
另外,由于您无法预测随机生成器的选择(如果它是好的),因此不可能生成不良来源
快速排序算法是这样的:
- select一个支点
- 将小于枢轴的元素移到开头,将大于枢轴的元素移到末尾
- 现在数组看起来像
[<=p, <=p, <=p, p, >p, >p, >p]
- 递归排序数组的第一和第二“一半”
Quicksort 将是高效的,运行 时间接近 n log n
,如果主元总是靠近数组的中间结束。如果枢轴是中值,这将非常有效。但是 select 计算实际中位数本身的成本就很高。如果不幸的是,主元恰好是数组中最小或最大的元素,您将得到这样的数组:[p, >p, >p, >p, >p, >p, >p]
。如果这种情况经常发生,您的“快速排序”实际上表现得像 select离子排序。在这种情况下,由于要递归排序的子数组的大小在每次迭代中仅减少 1,因此将有 n
级迭代,每级迭代花费 n
次操作,因此总体复杂度将为`n^2.
现在,既然我们不愿意使用代价高昂的操作来找到一个好的主元,我们不妨随机选择一个元素。而且由于我们也不太关心任何类型的真正随机性,我们可以从数组中选择一个任意元素,例如第一个。
如果数组是随机均匀打乱的,那么选择第一个元素就很好。您可以合理地希望它会定期给您一个“平均”元素。但是如果数组已经排序......那么根据定义第一个元素是最小的。所以我们处于复杂度为 n^2
.
的糟糕情况
避免“坏列表”的一个简单方法是选择真正的随机元素而不是任意元素。或者,如果您有理由相信快速排序通常会在几乎已排序的列表上调用,您可以选择位置 n/2
中的元素而不是位置 1.
中的元素。
还有几篇关于 select 枢轴的不同方法的研究论文,以及对复杂性影响的精确计算。例如,您可以选择三个随机元素,将它们从小到大排列并保留中间的元素。但结论通常是:如果你尝试写一个更好的pivot-selection,那么它的成本也会更高,算法的整体复杂度也不会提高多少。
本题中:https://www.quora.com/What-is-randomized-quicksort
Alejo Hausner 告诉:快速排序的成本,在最坏的情况下,即
Ironically, if you apply quicksort to an array that is already sorted, you will get probably get this costly behavior
我无法得到它。谁能给我解释一下。
https://www.quora.com/What-will-be-the-complexity-of-quick-sort-if-array-is-already-sorted 可能是这个问题的答案,但这并没有让我得到完整的回应。
根据实施方式,有多种 'common' 方法来选择枢轴。
一般来说 'unsorted' 来源没有好坏之分。 所以有些实现只是把第一个元素作为枢轴。
在已经排序的源的情况下,这会导致最差的主元,因为最短的间隔将始终为空。
-> 递归步骤 = O(n) 而不是所需的 O(log n)。
这导致 O(n²) 复杂度,这对排序非常不利。
随机选择枢轴可以避免这种行为。随机选择的主元极不可能在每次递归中都具有如上所述的相同不良特征。
另外,由于您无法预测随机生成器的选择(如果它是好的),因此不可能生成不良来源
快速排序算法是这样的:
- select一个支点
- 将小于枢轴的元素移到开头,将大于枢轴的元素移到末尾
- 现在数组看起来像
[<=p, <=p, <=p, p, >p, >p, >p]
- 递归排序数组的第一和第二“一半”
Quicksort 将是高效的,运行 时间接近 n log n
,如果主元总是靠近数组的中间结束。如果枢轴是中值,这将非常有效。但是 select 计算实际中位数本身的成本就很高。如果不幸的是,主元恰好是数组中最小或最大的元素,您将得到这样的数组:[p, >p, >p, >p, >p, >p, >p]
。如果这种情况经常发生,您的“快速排序”实际上表现得像 select离子排序。在这种情况下,由于要递归排序的子数组的大小在每次迭代中仅减少 1,因此将有 n
级迭代,每级迭代花费 n
次操作,因此总体复杂度将为`n^2.
现在,既然我们不愿意使用代价高昂的操作来找到一个好的主元,我们不妨随机选择一个元素。而且由于我们也不太关心任何类型的真正随机性,我们可以从数组中选择一个任意元素,例如第一个。
如果数组是随机均匀打乱的,那么选择第一个元素就很好。您可以合理地希望它会定期给您一个“平均”元素。但是如果数组已经排序......那么根据定义第一个元素是最小的。所以我们处于复杂度为 n^2
.
避免“坏列表”的一个简单方法是选择真正的随机元素而不是任意元素。或者,如果您有理由相信快速排序通常会在几乎已排序的列表上调用,您可以选择位置 n/2
中的元素而不是位置 1.
还有几篇关于 select 枢轴的不同方法的研究论文,以及对复杂性影响的精确计算。例如,您可以选择三个随机元素,将它们从小到大排列并保留中间的元素。但结论通常是:如果你尝试写一个更好的pivot-selection,那么它的成本也会更高,算法的整体复杂度也不会提高多少。