【问题标题】:Recursively assert number of moves in Tower of Hanoi递归断言河内塔中的移动次数
【发布时间】:2016-06-16 18:52:49
【问题描述】:

我们在 Uni 获得的任务之一是编写一个函数,该函数递归地打印出 河内塔谜题的动作:

public static void move(int number, char start, char help, char end) {
    if (number == 1) {
        print("Move the top disk from " + start + " to " + end);
    } else {
        move(number - 1, start, end, help);
        print("Move the top disk from " + start + " to " + end);
        move(number - 1, help, start, end);
    }
}

现在我们必须想出一个函数来计算 n 元素的移动次数,并使用断言来检查我们使用此函数的代码的有效性。

显然,该函数由以下公式给出:f(n) = 2*f(n-1) + 1n > 1f(n) = 1n = 1。我们可以求解这个递归方程并得到f(n) = 2^n - 1

通过将static int count = 0;添加到类的顶部并在每个print语句之后递增它,我们可以获得移动的总数:

public static void move(int number, char start, char help, char end) {
    if (number == 1) {
        print("Move the top disk from " + start + " to " + end);
        count++;
    } else {
        move(number - 1, start, end, help);
        print("Move the top disk from " + start + " to " + end);
        count++;
        move(number - 1, help, start, end);
    }
}

然后在函数调用之后添加一个断言,用递归方程的求解形式检查counter 的值:

move(n, 'A', 'B', 'C');
assert count == Math.pow(2,n) - 1 : "Number of movements isn't correct.";

这很好用。但是,我很想知道是否有一种方法可以在递归函数本身内部使用assert,并使用等式的递归形式检查移动的数量——比如assert count == 2*f(n-1) + 1。我们可能不得不更改 count 的使用方式,但我不知道如何(或者是否可能)。

注意:print() 仅代表标准 System.out.println()

编辑:我更喜欢不需要更改 move 函数签名的解决方案(或者有人说如果没有这样的更改,这绝对是不可能的)

【问题讨论】:

    标签: java recursion assert assertion


    【解决方案1】:

    一种方法是将计数作为参数添加到函数中

    public static int move(int number, char start, char help, char end, int count)

    最初的调用类似于

    int count == Math.pow(2,n) - 1
    move(n,'A','B','C',count);
    

    然后在函数内部

    public static int move(int number, char start, char help, char end,int count){
        if(number == 1){
            print("Move the top disk from " + start + " to " + end);
            assert count == 1; 
            return 1;
        }else{
            int subCount1 = move(number-1,start,end,help, (count-1)/2);
            print("Move the top disk from " + start + " to " + end);
            int subCount2 = move(number-1,help,start,end, (count-1)/2);
            assert count == (subCount1 + subCount2 + 1);
            return count; // it's the same as returning 2*f(n-1)+1;
        }
    }
    

    count 参数用作预期的断言值。 这是纯粹的直觉,可能需要一些细微的改变。 (count-1)/2 部分我不是 100%。

    编辑 如果您不想更改方法签名,请尝试以下操作:

    public static void move(int number, char start, char help, char end) {
        if (number == 1) {
            print("Move the top disk from " + start + " to " + end);
            count++;
        } else {
            int stepsBeforeMove1 = count;
            move(number - 1, start, end, help);
            int stepsAfterMove1 = count;
            print("Move the top disk from " + start + " to " + end);
            count++;
            int stepsBeforeMove2 = count; //this is just for the sake of clarity
            move(number - 1, help, start, end);
            int stepsAfterMove2 = count;
            assert ((stepsAfterMove1-stepsBeforeMove1) + (stepsAfterMove2-stepsBeforeMove1) + 1) == Math.pow(2,number) - 1;
        }
    }
    

    【讨论】:

    • 是的,这可能会正常工作,但是,我更喜欢不需要更改函数签名的方法:) (但是,这可能是实现它的唯一可能性工作)
    【解决方案2】:

    如果您将游戏板与求解器分开(所以Towers 有一个move 方法,而Solversolve(towers)),您可以将Towers 装饰为仪器move。但是您必须摆脱静态方法,并且您会得到稍微过度设计的 OO 代码而不是过程代码。

    【讨论】:

    • 是的,这应该是一个具有main 函数和move 函数的类。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-11
    • 1970-01-01
    • 1970-01-01
    • 2013-08-28
    • 2021-07-06
    • 1970-01-01
    • 2017-02-04
    相关资源
    最近更新 更多