有没有有效的方法将一元数转换为二进制数?

时间:2015-10-21 10:50:02

标签: algorithm haskell functional-programming lambda-calculus

让这些数据类型分别代表一元和二元自然数:

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?

3 个答案:

答案 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),其中Mincrement的最差情况。所以如果我们可以在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

因此,如果你负责从右到左表示你的数字,那么天真的算法是线性的(注意你可以执行列出反转并保持线性,如果你真的需要你的数字反过来)。