二幂拆分问题

来源:互联网 发布:d3.js官网demo 编辑:程序博客网 时间:2024/06/15 00:57

转载请注明出处,http://blog.csdn.net/Bule_Zst/article/details/77111983


问题描述:

任意一个整数x都可以写成一系列2的幂的组合,这里的组合是指用加减法把它们接连起来。令f(x)为x需要的最少的2的幂的项数,求f(x)。

如:

7=20+21+22
7=2320

f(7)=2

声明:

因为f(x)=f(x), 所以后文一致假设x非负。另外令f(0)=0


下面介绍三种算法,由易到难,且算法之间存在联系。

算法1:

令floor2(x)表示不大于x的最大2幂, ceil2(x)表示不小于x的最小2幂,最优的组合要么是floor2(x)加上其它一些项,或者是ceil2(x)减去其它一些项。
于是可以得到求f(x)的一个递归过程如下:

def f(x):    if x == 0: return 0    l = floor2(x)    u = ceil2(x)    if l == u: return 1    return 1 + min(f(x - l), f(u - x))


算法2:

观察算法1会发现,有很多的计算都是重复的
如对于对于求f(23)
这里写图片描述

测试代码:

def floor2( x ):    a = 1    while a < x:        a = a * 2    if a == x:        return a    else:        return a / 2def ceil2( x ):    a = 1    while a < x:        a = a * 2    return adef f(x):    print x    if x == 0:        return 0    l = floor2(x)    u = ceil2(x)    if l == u:        return 1    return 1 + min(f(x - l), f(u - x))print f( 23 )

因此可以使用一个数组存储已经计算过的结果:

def f(x):    if x == 0: return 0    if x in table: return table[x]    l = floor2(x)    u = ceil2(x)    if l == u: return 1    r = 1 + min(f(x - l), f(u - x))    table[x] = r    return r


算法3:

算法2是一个从本身串层层向下递推的过程,因此需要额外内存存储结果,因此想到,可以改进算法2为由下向上的过程。

分析算法2的递推过程:

x=(1010110)2

xfloor2(x)=(0010110)2
ceil2(x)x=(10000000)2(1010110)2=(0101010)2=~x+1

我们会发现,xfloor2(x)表示的是自身串从左往右数,第二个1之后的子串,ceil2(x)x表示的是自身串从左往右数,第一个0之后的子串取反加1

因此,算法3的思路就是,从右往左遍历自身串,用u表示以1开头的串的f(x)值,用d表示以0开头的串取反加一的f(x)

从右往左扫,扫到1则更新u,扫到0则更新d

自身串最右边刚开始的那些0是没有用的,一直扫到1开始更新,u、d赋初值1

代码中的>>1不要当成除以2,而是看做二进制字符串的右移。&1 == 1表示最后一位是1.

def f(x):    if x == 0: return 0    while x & 1 == 0: x = x >> 1    u = 1    d = 1    x = x >> 1    while x > 0:             if x & 1 == 1:            u = min(u, d) + 1        else:            d = min(u, d) + 1        x = x >> 1    return u



2017年9月6日更新

今天重新看了这篇文章,发现自己竟然看不懂了,what?现在终于又懂了,再把思路重新梳理一下。

前两个方法没什么好说的,关键是第三个方法,里面的u与v。

u指的是当前二进制字符串(以1开头)从左往右第二个1开头的新串的f(x)值,v指的是当前二进制字符串从左往右第一个0开头的新串取反加一得到的新新串的f(x)值(f(x)中的x指的是新串与新新串)

举个例子就懂了,这个方法确实很神奇

1010110
初始,
u: 10: 1
v: 10: 1
PS:10表示代表的串,1表示需要二进制数的个数

计算110,因为1开头,所以更新u
u=u+1: 100 + u
u=v+1 : 1000 - v
结果:u: 110: 2

计算1010(原串:0110),更新v
v=u+1: 10000 - u
v=v+1: 1000 + v
结果:v: 1010: 2

计算10110,更新u
u=u+1: 10000 + u
u=v+1: 100000 - v
结果:u: 10110: 3

计算101010(原串:010110),更新v
v=u+1: 1000000 - u
v=v+1: 100000 + v

以此类推

参考文章:

http://blog.csdn.net/howardemily/article/details/74938030
http://blog.csdn.net/howardemily/article/details/74938033

原创粉丝点击