让这些数据类型分别代表一元和二元自然数:
data UNat = Succ UNat | Zero
data BNat = One BNat | Zero BNat | End
u0 = Zero
u1 = Succ Zero
u2 = Succ (Succ Zero)
u3 = Succ (Succ (Succ Zero))
u4 = Succ (Succ (Succ (Succ Zero)))
b0 = End // 0
b1 = One End // 1
b2 = One (Zero End) // 10
b3 = One (One End) // 11
b4 = One (Zero (Zero End)) // 100
(Alternatively, one could use `Zero End` as b1, `One End` as b2, `Zero (Zero End)` as b3...)
我的问题是:有没有办法实现这个功能:
toBNat :: UNat -> BNat
在O(N)
中有效,只通过一次UNat?
答案 0 :(得分:10)
我喜欢其他答案,但我发现它们的渐近分析很复杂。因此,我提出了另一个具有非常简单的渐近分析的答案。基本思想是为一元数实现divMod 2
。因此:
data UNat = Succ UNat | Zero
data Bit = I | O
divMod2 :: UNat -> (UNat, Bit)
divMod2 Zero = (Zero, O)
divMod2 (Succ Zero) = (Zero, I)
divMod2 (Succ (Succ n)) = case divMod2 n of
~(div, mod) -> (Succ div, mod)
现在我们可以通过迭代divMod
来转换为二进制文件。
toBinary :: UNat -> [Bit]
toBinary Zero = []
toBinary n = case divMod2 n of
~(div, mod) -> mod : toBinary div
渐近分析现在非常简单。给定一个n
的一元符号,divMod2
花费O(n)时间来生成一半大的数字 - 比如说,c*n
时间最多只需n
c*(n + n/2 + n/4 + n/8 + ...)
}}。因此,迭代此过程需要花费很多时间:
c*(2*n)
众所周知,这一系列会聚到toBinary
,因此2*c
也在O(n)中,见证常数为.sort()
。
答案 1 :(得分:5)
如果我们有一个函数来增加BNat
,我们可以通过UNat
运行,在每一步增加BNat
来轻松完成此操作:
toBNat :: UNat -> BNat
toBNat = toBNat' End
where
toBNat' :: BNat -> UNat -> BNat
toBNat' c Zero = c
toBNat' c (Succ n) = toBNat' (increment c) n
现在,这是O(NM)
,其中M
是increment
的最差情况。所以如果我们可以在O(1)中做increment
,那么答案是肯定的。
我尝试实施increment
:
increment :: BNat -> BNat
increment = (reverse End) . inc' . (reverse End)
where
inc' :: BNat -> BNat
inc' End = One End
inc' (Zero n) = One n
inc' (One n) = Zero (inc' n)
reverse :: BNat -> BNat -> BNat
reverse c End = c
reverse c (One n) = reverse (One c) n
此实施是O(N)
,因为您必须reverse
BNat
来查看最低有效位,这样总体上会O(N)
。如果我们认为BNat
类型代表反向二进制数,我们就不需要反转BNat
,而且,正如@augustss所说,我们有O(1),它给你O (N)整体而言。
答案 2 :(得分:5)
要递增二进制数字,您必须翻转数字末尾的第一个零和前面的所有数字。此操作的成本与输入结束时的1的数量成比例(为此,您应将数字表示为从右到左列表,例如,列表[1; 0; 1; 1]代码为13)
设a(n)为n末尾的数字1:
a(n) = 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, ...
然后让
s(k) = a(2^k) + a(2^k+1) + ... + a(2^(k+1)-1)
是两个2的幂之间的元素之和。你应该能够通过注意到
a(2^(k+1)) ..., a(2^(k+2) - 1)
是通过连接获得的
a(2^k) + 1, ..., a(2^(k+1) - 1) and a(2^k), ..., a(2^(k+1) - 1)
因此,作为几何级数,s(k)= 2 ^ k - 1。
现在增加N倍数的成本应与
成比例 a(0) + a(1) + ... + a(N)
= s(0) + s(1) + s(2) + ... + s(log(N))
= 2^0 - 1 + 2^1 -1 + 2^2-1 + ... + 2^log(N) - 1
= 2^0 + 2^1 + 2^2 + ... + 2^log(N) - log(N) - 1
= 2^(log(N) + 1) - 1 - log(N) - 1 = 2N - log(N) - 2
因此,如果你负责从右到左表示你的数字,那么天真的算法是线性的(注意你可以执行列出反转并保持线性,如果你真的需要你的数字反过来)。