带列表的Python回合制递归

时间:2015-08-27 03:13:51

标签: python list recursion

我正在研究我的一个CS课程的问题,我非常有信心我有正确的想法,就在我实施它时,我没有得到正确答案。

游戏是:有两个玩家和一个数字列表(即[3,7,8,1,6,4,5])。每个玩家轮流从列表的任一端挑选一个数字。一旦选择了一个号码,它就会从列表中删除,然后对手可以从这个新列表中选择他们想要的结尾。目标是在列表为空时获得最大的数字总和。

我的想法:我们假设我们从简单列表[1,2,3,4,5]开始。当玩家1从开头或结尾(1或5)中选择一个号码时,我们现在有一个较小的列表可供对手选择。那么让我举一个使用这个列表的例子:

我选择5.新的列表是[1,2,3,4],对手可以选择。我不知道他们会选择列表的哪一端,但我知道它只能是1或者4.如果是1,那么当轮到我时,我就离开了[2,3,4]。如果他们选4,我就离开了[1,2,3]。如果我选择1,它们会被[2,3]保留,如果我选择3,它们会被[1,2]等留下,直到列表中没有数字为止。对手也在尽最大努力获得最高分,所以他们不会只是贪婪地选择更多的数字。球员同样聪明,所以他们都会使用完全相同的策略来获得最高分。

每次在较小的列表上这是一个明显的递归问题。

注意:我不是在寻找代码。因为它是一个cs课程,我真的很想给我一些关于我可能做错的提示,以便我可以学习而不是给出代码。

这是我写的代码:

def Recursive(A):
    # check if there is only one item left. If so, return it 
    if len(A) == 1:
         return A[0]

    # take the left item and recurse on the list if the opponent 
    # were to take the left side, and the list if the opponent 
    # were to take the right number
    takeLeftSide = A[0] + max(Recursive(A[1:-1]), Recursive(A[2:len(A)]))
    takeRightSide = A[-1] + max(Recursive(A[0:-2]), Recursive(A[1:-1]))

    return max(takeLeftSide, takeRightSide)

if __name__ == '__main__':
    A = [5,8,1,3,6]
    print Recursive(A) 

我相信我应该期待12,但在某些情况下我的输出给了我19和14。

我很感激帮助,我已经在这里工作了几个小时,我知道一旦你尝试深入了解递归的东西就会变得混乱和混乱。

3 个答案:

答案 0 :(得分:0)

这是我尝试的代码(使用您的输入)。它与你的几乎完全相同。

@Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status,
            int newState) {
            String intentAction;
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                intentAction = ACTION_GATT_CONNECTED;
                mConnectionState = STATE_CONNECTED;
                broadcastUpdate(intentAction);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                mBluetoothGatt.discoverServices();
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                intentAction = ACTION_GATT_DISCONNECTED;
                mConnectionState = STATE_DISCONNECTED;
                if(mBluetoothGatt != null){
                    mBluetoothGatt.close();
                    mBluetoothGatt = null;
                }
                broadcastUpdate(intentAction);
            }
        }

正确答案是19。

原因是:假设我们正在寻找最高的可能分数(不是两个人都使用最佳策略的分数),那么游戏将如下:

def r(a):
    if len(a) == 1:
        return a[0]
    takeLeft = a[0] + max(r(a[1:-1]), r(a[2:]))
    takeRight = a[-1] + max(r(a[:-2]), r(a[1:-1]))
    return max(takeLeft, takeRight)

所以P1有19,P2有4。

编辑:这是在使用GIVEN(略有改动)的递送代码的最佳情况下发生的事情

P1: 6          Leftover: [5,8,1,3]
P2: 3          Leftover: [5,8,1]
P1: 6 + 5      Leftover: [8,1]
P2: 3 + 1      Leftover: [8]
P1: 6 + 5 + 8  Leftover: []

以下是游戏在这种情况下的表现:

def r(a):
    if len(a) == 1:
        return a[0]
    if len(a) == 2 or len(a) == 3:
        if a[0] > a[-1]:
            return True
        return False
    takeLeft = a[0] + max(r(a[1:-1]), r(a[2:]))
    takeRight = a[-1] + max(r(a[:-2]), r(a[1:-1]))
    if takeLeft > takeRight:
        return True
    return False

def game(a):
    p1 = 0
    p2 = 0
    p1Turn = True
    while len(a) > 0:
        isLeft = r(a)
        nextValue = -1
        if isLeft: #if it's from the left
            nextValue = a[0]
            a = a[1:]
        else: #if it's from the right
            nextValue = a[-1]
            a = a[:-1]
        if p1Turn:
            p1 += nextValue
        else:
            p2 += nextValue
        p1Turn = not p1Turn
    print "P1:", p1, "P2:", p2

答案 1 :(得分:0)

为了让两个玩家“同样聪明”,他们应该使用相同的方法(算法)来选择一个侧面,每轮。

为了对其进行编程,您应该根据给定的列表开发用于选择边(线的前面或后面)的算法,并且每次为不同的播放器调用此算法。

为了证明这一点,我选择了一种相对原始的贪婪算法,它总是选择两者之间的较大:

def pick_greedy(A):
    if len(A) == 0:
        result = 0
    elif len(A) == 1:
        result = A.pop()
    elif A[0] > A[-1]:
        result = A[0]
        A = A[1:]
    else:
        result = A[-1]
        A = A[:-1] 
    return A, result


if __name__ == '__main__':
    A = [5,8,1,3,6]
    x = 0
    y = 0
    while A:
        A, _ = pick_greedy(A)
        x += _
        A, _ = pick_greedy(A)
        y += _
        print "Player 1: {}; Player 2: {}".format(x, y)

<强>输出

Player 1: 6; Player 2: 5
Player 1: 14; Player 2: 8
Player 1: 15; Player 2: 8

答案 2 :(得分:0)

您当前实施的一个问题是,播放器1应该获得次要呼叫的 min ,而不是最大值(播放器2将获得这些呼叫的最大值)。如果进行了更改,则会得到预期结果:12

takeLeftSide  = A[0]  + min(Recursive(A[1:-1]), Recursive(A[2:len(A)]))
takeRightSide = A[-1] + min(Recursive(A[0:-2]), Recursive(A[1:-1]))

当前实现的其他三个问题:(a)输入列表的大小增加非常慢; (b)如果输入列表具有偶数个值,则引发IndexError; (c)如果输入列表为空,则引发RuntimeError

但是,这是另一种思考问题的方法:

如果列表中存在偶数个值,则玩家1可以通过决定是使用偶数索引获取所有值还是使用奇数索引<的所有值来保证获胜或平局/强>

如果初始列表中有奇数个值,则播放器1只需要在左与右之间做出决定。然后,初始选择会为玩家2创建一个大小合适的列表情况,玩家2将回归到上述策略。

因此,玩家1可以预测玩家2的移动,并且应该相应地进行初始左右选择。

def f(xs):
    evens = sum(xs[i] for i in range(0, len(xs), 2))
    odds  = sum(xs[i] for i in range(1, len(xs), 2))
    if len(xs) % 2 == 0:
        return max(evens, odds)
    else:
        lft = xs[0]
        rgt = xs[-1]
        return max(
            lft + min(odds, evens - lft),
            rgt + min(odds, evens - rgt),
        )

if __name__ == '__main__':
    from random import randint
    tests = [
        (0,     []),
        (5,     [5]),
        (30,    [20, 1, 15, 9, 19]),
        (12,    [5, 8, 1, 3, 6]),
        ('big', [randint(0, 100) for _ in xrange(0, 99999)]),
    ]
    for exp, vals in tests:
        print exp, f(vals)

与递归实现不同,此方法为O(N),因此具有能够处理非常大的列表的优势。