小撒是一只好学的小鸭子,这天,小撒在学习算法
顺序统计量(order statistic)
在一个数组中,第i
个数据统计量指的是数组中第i
小的元素。
为了取得特定的顺序统计量,办法之一就是对数组的全部元素进行排序,随后找出排序数组的第i
项。这当然是一个可行的办法,但是有没有执行效率更高的办法呢?
这里我们将同样使用分治的思想。首先回忆一下我们曾学过的快速排序的过程。
- 在快速排序的每一步,我们选择一个基点并将数组分为小于和不小于基点的两部分
- 假设分割后基点在数组中的位置是
k
- 若
i
等于k
,那么基点的数字就是我们要找的顺序统计量 - 否则,比较
i
与k
的大小我们就知道我们要寻找的目标在分割后数组的左子数组还是右子数组,并对子数组重复以上的操作
在平均情况下,这一算法是线性时间内可解的。
代码示例(js)
/**
* arr:目标数组
* index: 查找的元素的下标,例如查找最小元素,index === 0
* start: 初始调用start === 0
* end: 初始调用end === arr.length - 1
*/
const order = (arr, index, start, end) => {
if (start >= end) return arr[index]
const pivot = arr[start]
let x = start, i = start + 1, j = end
while (x < j) {
if (arr[i] > pivot) {
swap(arr, i, j)
j--
} else {
swap(arr, x, i)
x = i
i++
}
}
if (index === x) return pivot
if (index < x) return order(arr, index, start, x - 1)
return order(arr, index, x + 1, end)
}
最坏情况线性时间的选择
在快排中存在一个问题,即最差情况,如果我们每次选取的基点恰好为数组中最小或最大的元素,则每次的迭代只从数组中排除了一个元素而非一部分元素,对于快排会造成n ^ 2
的运行时间,在这里也无法实现线性时间的运行了。
于是这里我们要对算法进行优化,使得算法的每次迭代,都能保证排除一部分而非仅一个无需关心的元素。
算法的技巧在于找到一个合理的基点:
- 将
n
个元素的数组按5
个一组分为m
组,每组元素5
个,最后一组元素等于或少于5
个 - 寻找每一组的中位数
- 寻找中位数的中位数,并使用其作为基点
图示中箭头由较大的元素指向较小的元素。蓝色的点是中位数的中位数,即我们选择的基点:由此我们可以观察到,图中绿色的区域均不小于基点,而橙色的区域均不大于基点,因此使用以上方法找到的中位数能保证可以避开最差情况。
至于具体的代码小伙伴们请试着开动脑筋自己写一写哦。
小结
今天介绍的算法同样使用了分治的思想,具有线性运行时间,并且通过对算法的改动避免了最差情况的出现。