选择问题
选最大和最小
输入:集合L(含n个不等的实数)
输出:L中的第i小的元素
-
i=1,称为最小元素
-
i=n,称为最大元素
位置处在中间爱你位置的元素,成为中位元素。
n为奇数,中位数唯一,i=(n+1)/2。
n为偶数,可指定为i=n/2+1。
选最大算法:顺序比较,在最坏情况下的时间为W(n)=n−1。
代码为:
1 2 3 4 5 6 7 8 9 10 11 12
| def max_value(numbers: List[int]) -> Tuple[int, int]: if not numbers: return (-1, -1) if len(numbers) == 1: return (0, numbers[0])
m = numbers[0], k = 0 for i in range(1, len(numbers)): if m < numbers[i]: m = numbers[i] k = i return (k, m)
|
选最大最小通常算法:
时间复杂度:W(n)=n−1+n−2=2n−3
分组算法解决最大最小值:
输入:n个数的数组L
输出:max,min
-
将n个元素两两一组分成⌊n/2⌋组。
-
每组比较,得到⌊n/2⌋个较小和⌊n/2⌋个较大的。
-
在⌈n/2⌉个较大(含轮空元素)找最大max
-
在⌈n/2⌉个比较(含轮空元素)中找最小min
时间复杂度:
-
在上述代码2中,组内比较⌊n/2⌋次。
-
在3-4行内求max和min比较,最多2×(⌈n/2⌉−1)次。
-
即W(n)=⌊n/2⌋+2×⌈n/2⌉−2=n+⌈n/2⌉−2=⌈3n/2⌉−2
分治算法:
-
将数组L从中间划分为2个子数组L1,L2
-
递归地在L1中求最大的max1和min1
-
递归地在L2中求最大的max2和min2
-
max←max{max1,max2}
-
min←min{min1,min2}
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import math from typing import List, Tuple
def max_min(arr: List[int]) -> Tuple[int, int]: i, j = 0, len(arr) - 1 if i == j: return [arr[i], arr[i]] elif i == j - 1: return [arr[i], arr[j]] if arr[i] > arr[j] else [arr[j], arr[i]] else: mid = math.floor(j/2) max1, min1 = max_min(arr[i : mid]) max2, min2 = max_min(arr[mid : j + 1]) return [max(max1, max2), min(min1, min2)]
if __name__ == "__main__": arr = [6, 10, 32, 8, 19, 20, 222, 14, 53, 1] max_value, min_value = max_min(arr) print(max_value, min_value)
|
最坏情况复杂度:
假设n=2k,W(n)=2W(n/2)+2W(2)=1
解
W(2k)=2W(2k−1)+2=2[2W(2k−2)+2]+2=22W(2k−2)+22+2=...2k−1+2k−1+...+22+2=3×2k−1−2=3n/2−2
选择算法小结:
-
选最大:顺序比较,比较次数n−1
-
选最大最小:
选第二大问题
输入:n个数的数组L
输出:第二大的数Second
通常算法:顺序比较
-
顺序比较找到最大的max
-
从剩下n−1个数中找最大,就是第二大的second
时间复杂度:W(n)=n−1+n−2=2n−3 (比较次数)
提高效率的途径
(使用空间换时间)
锦标赛算法
-
两两分组比较,大的进入下一轮,直到剩下1个元素max为止;
-
在每次比较中淘汰较小的元素,将被淘汰元素记录在淘汰他的元素的链表上。
-
检查max的链表,从中找到最大元素,即secode。
伪代码:
-
k <- n // 参与淘汰的元素数字
-
将k个元素两两1组,分成⌊k/2⌋组
-
每组的2个数比较,找到较大数
-
将被淘汰数记入较大数的链表
-
如果 k 为奇数,那么 k←⌊k/2⌋+1
-
否则 k←⌊k/2⌋
-
如果k>1,那么转到步骤2
-
max ← 最大数
-
second ← max的链表中最大
其中,1-4是一轮淘汰,7为继续分组淘汰
时间复杂度分析:
命题1 设参与比较的元素有t个元素,经过i轮淘汰后元素至多为⌈t/2i⌉。
证 对i归纳。i=1,分⌊t/2⌋,淘汰⌊t/2⌋个元素,进入下一轮的元素是t−⌊t/2⌋=⌈t/2⌉。
假设i轮分组淘汰后元素数至多为⌈t/2i⌉,那么i+1轮分组淘汰后元素为:
⌈⌈t/2i⌉/2⌉=⌈t/2i+1⌉
命题2 max在第一阶段分组比较中总计进行了⌈logn⌉次比较。
证 假设到产生max时总计进行k轮淘汰,根据 命题1 有⌈n/2k⌉=1。
若n=2d,那么有k=d=logn=⌈logn⌉;
若 2d<n<2d+1 ,那么有k=d+1=⌈logn⌉。
综上,
-
第一阶段元素数:n
比较次数:n−1
淘汰了n−1个元素
-
第二阶段:元素数⌈logn⌉
比较次数:⌈logn⌉−1
淘汰元素数为⌈logn⌉−1
-
时间复杂度W(n)=n+⌈logn⌉−2
第二大小结
求第二大算法:
-
调用2次找最大:2n−3
-
锦标赛算法:用空间换取时间。
一般选择算法的设计
问题:选第k小
输入:数组S,S的长度n,正整数k,1≤k≤n
输出:第k小的数
例子
-
S={3,4,8,2,5,9,18},k=4,解:5
-
统计数据的集合S,∣S∣=n,选中位数,k=⌈n/2⌉
选择算法的分析
伪码
算法 Select(S, k)
-
将S分成5个一组,共nM=⌈n/5⌉组
-
每组排序,中位数防盗集合M
-
m∗←Select(M,⌈M/2⌉) // S分A,B,C,D
-
A,D元素小于 m∗ 放 S1,大于 m∗ 放 S2。
-
S1←S1∪C;S2←S2∪B
-
if k=∣S1∣+1 then 输出 m∗
-
else if k≤∣S1∣
-
then Select(S1,k)
-
then Select(S2,k-∣S1∣-1)
用m*划分
n=5(2r+1),∣A∣=∣D∣=2r
子问题规模至多:2r+2r+3r+2=7r+2
子问题规模估计
不妨设n=5(2r+1),∣A∣=∣D∣=2r,
r=2n/5−1=10n−21
划分后子问题规模至多为:
7r+2=7(10n−21+2)=107n−23<107n
时间复杂度递推方程
算法工作量W(n)
行2:O(n) // 每5个数找中位数,构成M
行3:W(n/5) // M中找中位数m∗
行4:O(n) // 用m∗划分集合S
行8-9:W(7n/10) // 递归
W(n)≤W(n/5)+W(7n/10)+O(n)
递归树
W(n)≤W(n/5)+W(7n/10)+O(n)
W(n)≤cn(1+0.9+0.92+...)=O(n)
讨论
为什么使用5个一组,而不是3个一组或者7个一组?
分析:递归调用
-
求m∗的工作量与∣M∣=n/t相关,t为每组元素数,t大,∣M∣小。
-
归约后子问题大小与分组元素数t有关,t大,子问题的规模大。
3分组时的子问题规模
假设t=3,3个一组:
n=3(2r+1)r=(n/3−1)/2=n/6−1/2
子问题规模最多为4r+1=4n/6−1
算法时间复杂度
算法时间复杂度满足方程:
W(n)=W(n/3)+W(4n/6)+cn
由递归树得到W(n)=Θ(nlogn)
关键点: