算法的正确性和逻辑:最小步数到一

Posted

技术标签:

【中文标题】算法的正确性和逻辑:最小步数到一【英文标题】:Correctness and Logic of algorithm: minimum steps to one 【发布时间】:2015-07-13 19:21:11 【问题描述】:

问题陈述:

对于一个正整数,您可以执行以下 3 个步骤中的任何一个。

    从中减去 1。 (n = n - 1)

    如果它可以被 2 整除,则除以 2。(如果 n % 2 == 0 ,则 n = n / 2)

    如果它可以被 3 整除,则除以 3。(如果 n % 3 == 0 ,则 n = n / 3)

给定一个正整数 n,你的任务是找到从 n 到一的最小步数。

我的递归解决方案(在 C++ 中)比较了 N 可被 3 整除的所有 3 种情况,而一般解决方案只比较 2,但仍然给出了正确的解决方案。

int min_steps(int N) 
        if(N==1) return 0;    
        else
                if(N%3==0)
                       if(N%2==0) 
                          return (1+min(min_steps(N/3),min_steps(N/2),min_steps(N-1)));
                       else
                          return(1+min(min_steps(N/3),min_steps(N-1)));
                
                else if(N%2==0)
                        return(1+min(min_steps(N/2),min_steps(N-1)));
                
                else
                        return(1+min_steps(N-1));
        

但一般的解决方案是,

int min_steps(int N) 
        if(N==1) return 0;    
        else
                if(N%3==0)
                        return(1+min(min_steps(N/3),min_steps(N-1)));
                
                else if(N%2==0)
                        return(1+min(min_steps(N/2),min_steps(N-1)));
                
                else
                        return(1+min_steps(N-1));
        

我的问题是,为什么我们不比较所有 3 个案例,但仍然得出正确的解决方案。我无法遵循通用解决方案的算法。任何让我理解的帮助将不胜感激。

【问题讨论】:

【参考方案1】:

“通用解决方案”不正确。有时最好除以 2 然后减 1,而通用解决方案代码不允许这样做。

“通用解决方案”对 642 产生不正确的结果。

642 -> 214 -> 107 -> 106 -> 53 -> 52 -> 26 -> 13 -> 12 -> 4 -> 2 -> 1

不过,这是最佳的,短一点:

642 -> 321 -> 320 -> 160 -> 80 -> 40 -> 20 -> 10 -> 9 -> 3 -> 1

你可以看到一般的解决方案是从除以 3 开始的,而最优的解决方案是从除以 2 然后减去 1 开始的......这正是被删除的情况。

虽然它与您的问题没有直接关系,但这是我用来查找反例的代码(尽管自从我编写它以来已经大大整理了)。它使用您提供的两种算法,但会记住它们以实现指数级速度增长。它还使用从 min_steps 返回两个结果的技巧:不仅是最短路径的长度,还包括该路径中的第一步。这使得重构路径非常方便,无需编写太多额外代码。

def memoize(f):
    """Simple memoization decorator"""
    def mf(n, div2, cache=):
        if (n, div2) not in cache:
            cache[n, div2] = f(n, div2)
        return cache[(n, div2)]
    return mf

@memoize
def min_steps(n, div2):
    """Returns the number of steps and the next number in the solution.

    If div2 is false, the function doesn't consider solutions
    which involve dividing n by 2 if n is divisible by 3.
    """
    if n == 1:
        return 0, None
    best = min_steps(n - 1, div2)[0] + 1, n-1
    if n % 3 == 0:
        best = min(best, (min_steps(n // 3, div2)[0] + 1, n//3))
    if n % 2 == 0 and (div2 or n%3):
        best = min(best, (min_steps(n // 2, div2)[0] + 1, n//2))
    return best

def path(n, div2):
    """Generates an optimal path starting from n.

    The argument div2 has the same meaning as in min_steps.
    """
    while n:
        yield n
        _, n = min_steps(n, div2)

# Search for values of n for which the two methods of finding
# an optimal path give different results.
for i in xrange(1, 1000):
    ms1, _ = min_steps(i, True)
    ms2, _ = min_steps(i, False)
    if ms1 != ms2:
        print i, ms1, ms2
        print ' -> '.join(map(str, path(i, True)))
        print ' -> '.join(map(str, path(i, False)))

这是输出,包括运行时间:

$ time python minsteps.py 
642 10 11
642 -> 321 -> 320 -> 160 -> 80 -> 40 -> 20 -> 10 -> 9 -> 3 -> 1
642 -> 214 -> 107 -> 106 -> 53 -> 52 -> 26 -> 13 -> 12 -> 4 -> 2 -> 1
643 11 12
643 -> 642 -> 321 -> 320 -> 160 -> 80 -> 40 -> 20 -> 10 -> 9 -> 3 -> 1
643 -> 642 -> 214 -> 107 -> 106 -> 53 -> 52 -> 26 -> 13 -> 12 -> 4 -> 2 -> 1

real    0m0.009s
user    0m0.009s
sys 0m0.000s

【讨论】:

另外,它保证了我算法的正确性。【参考方案2】:

如果n 可以被3 整除并且 可以被2 整除,那么在下一步中先除以3 然后再除以2 也没关系,或者先通过2,然后在下一步中通过3

示例:18 = 3*3*2

a) 18/3 = 66/3 = 22/2 = 1

b) 18/2 = 99/2 = #!?#9/3 = 33/3 = 1 或 ...

【讨论】:

如果你有一个 6 的倍数,那么除以 2 然后减去 1 永远不是最优的?可能是这样,但并不明显。

以上是关于算法的正确性和逻辑:最小步数到一的主要内容,如果未能解决你的问题,请参考以下文章

bfs最短路与最小步数模型

英语到一阶逻辑

统计学习方法[6]——逻辑回归模型

Postgres/JDBC/逻辑复制 - 内存不足问题

将数据库内容从一种非常糟糕的结构迁移到一种非常合乎逻辑的结构的最佳实践?

机器学习算法总结--线性回归和逻辑回归