假设给定了一个自然数的集合Set X,现在要求找出最小的一个不在集合Set X中的自然数。
现在我们假设如果集合Set X中的数据是按非降序给定的,那此时解法也非常的直观,我们只要从头开始比较X和集合[0..],第一个不同的数即为所求,但是,当Set X是以一个无序给定时,例如:
[08,23,09,00,12,11,01,10,13,07,41,04,14,21,05,17,03,19,02,06]
此时我们要在怎么找出最小数呢?
我们可以根据有序时的思想,我们只要从头开始判断[0..]是否在Set X中,Haskell代码如下:
vs \\ xs = filter (`notElem` xs) vs minFree xs = head $ [0..] \\ xs
中序操作符\\和集合运算符\功能差不多,vs \\ xs 是在vs中去除在xs中的元素,此时复杂度为Θ(n2)。
我们再次分析下,我们是从集合[0..]的0开始找的,然后每个元素去比对是否在Set X中,假设Set X的长度为n,则此时如果元素不重复,并且充满[0..n-1],则此时满足条件的自然数为n,而且此时n是满足条件的自然数中最大的数了,因为假如Set X的n个元素中有比n大的数,则此时必然满足条件的数是[0..n-1]中的数了,于是根据上面的想法,我们可以根据Set X产生一个长度为n+1序列b,其中每个元素的含义为bi:自然数i在Set X中是否存在,存在为True,此时我们就可以在序列b中从头开始找有多少个连续的True了,此时True的长度即为所求的自然数了。Haskell代码如下:
search :: Array Int Bool -> Int search = length . takeWhile id . elems checklist :: [Int] -> Array Int Bool checklist xs = accumArray (||) False (0,n) (zip (filter (<=n) xs) (repeat True)) minFree = search . checklist
上面主要有两个函数,一个是search,另一个是checklist,其中search主要是传入一个Bool的数组,然后从头找出最长的True序列的长度,而checklist则是根据数组的Int list来得到一个Bool的数组,即上面的序列b.
解法三是运用分治的方法,首先我们给出一些式子:
(as++bs)\\cs =(as\\cs)++(bs\\cs)
as\\(bs++cs)=(as\\bs)\\cs
(as\\bs)\\cs =(as\\cs)\\bs
现在我们假设as和vs不相关,则此时我们可以得到as\\vs=as,bs和us也不相关,则有bs\\us=bs,此时我们可以得到:
(as++bs)\\(us++vs)=(as\\us)++(bs\\vs)
此时,我们让as=[0..b−1] ,bs =[b..],并且让us=filter(<b)xs,vs=filter(≥b),则此时我们可以得到
[0..] \\ xs = [0..b-1] \\ us ++ [b..] \\ vs where (us,vs) = partition (<b) xs head (xs++ys) = if null xs then head ys else head xs -- when is the recursive is out??
然后我们分析指导 null xs的含义即 length [0..b-1] == b,于是我们可以定义下函数如下:
minfree xs = minfrom 0 xs minfrom a xs | null xs = a -- 递归结束的条件 | length us == b - a = minfrom b vs | otherwise = minfrom a us where (us,vs) = partition (<b) xs
接下去我们要确定b的值,我们希望确定的b值,能够将xs进行等分,这样则对问题的规模可以有效的减小,一个直观想法是
b = a + n / 2 where n = length xs
但是上面是有问题的,为什么呢?
因为上面我们有个假设是这样的 us = [ x | x < b] = [a,b)的,而 vs = [ x | x >= b ] = [b..],故我们应该要的是
b = a + 1 + n / 2 where n = length xs
这样我们就可以保证上面的式子成立了,并且递归后的问题规模为:
length us ≤n div 2 < n 或者 length vs =n−ndiv 2−1≤n div 2
于是问题的复杂度为
T(n) = T(ndiv 2) + Θ(n)
minfrom a (n,xs) | n == 0 = a | m == b - a = minfrom b (n-m,vs) | otherwise = minfrom a (m,us) where (us,vs) = partition (<b) xs b = a + 1 + (n / 2) m = length us