改进C#中的递归方法

时间:2012-01-13 06:23:30

标签: c# algorithm recursion

这是我的代码:

    static int cardGameValue(List<int> D, int myScore, int opponentScore)
    {
        if (D.Count == 0) return myScore;
        else if (D.Count == 1)
        {
            opponentScore += D[0];
            return myScore;
        }
        else
        {
            if (D[0] <= D[D.Count - 1])
            {
                opponentScore += D[D.Count - 1];
                D.RemoveAt(D.Count - 1);
            }
            else
            {
                opponentScore += D[0];
                D.RemoveAt(0);
            }

            int left = cardGameValue(new List<int>(D.GetRange(1, D.Count - 1)), myScore + D[0], opponentScore);

            int right = cardGameValue(new List<int>(D.GetRange(0, D.Count - 1)), myScore + D[D.Count - 1], opponentScore);

            if (left >= right)
            {
                return left;
            }
            else
            {
                return right;
            }
        }
    }
}

我的代码采用了一组牌,代表了与确定对手比赛时的最高分数。在你的每个对手的比赛结束后,你有2个选择,直到所有牌都被选中。有没有办法以某种方式存储我的迭代结果,所以我可以改进我的算法?那么递归不会做不必要的迭代?因为在40或50张牌之后它变得非常慢。

3 个答案:

答案 0 :(得分:3)

您只能访问列表D中的第一个或最后一个元素。您可以传递完整的卡片列表(甚至更好:作为int数组)以及第一个和最后一个位置的索引,而不是传递完整的列表。

计算结束后计算对手得分的速度要快得多:myScoreopponentScore加起来是卡的总和,所以你可以在O(n)中做到这一点时间。这样,您就可以删除所有更新opponentScore的代码。

您也不需要传递myScore。如果您让cardGameValue返回仅从剩余卡片中获得的最佳分数。

最后,如果您使用第一个和最后一个索引,则可以将分数存储在2D数组中,并按firstlast编制索引。如果所有牌都有正值,那么如果至少剩下两张牌,则得分必须为正。

因此,在通话开始时,您会检查缓存的分数是否为正数。如果是,您可以立即归还。如果没有,则必须计算它,然后将其存储在缓存数组中。

这就是你最终的结果:

static int cardGameValue(int[] D, int first, int last) {
    scores = new int[last + 1, last + 1];
    return cardGameValue(D, first, last, scores);
}

static int cardGameValue(int[] D, int first, int last, int[,] scores) {
    // If we have at most 1 card, our score is 0:
    if (first >= last)
        return 0;

    // Otherwise, get the score from the cache array. 
    // If it is positive, return the value.
    int score = scores[first, last];
    if (score > 0)
        return score;

    // Keep the original first and last 
    // for filling in the computed value later.
    int firstOriginal = first;
    int lastOriginal = last;

    // Let the opponent pick a card:
    if (D[first] <= D[last])
        last--;
    else
        first++;

    // Choose our best card:
    int left = D[first] + cardGameValue(D, first + 1, last, scores);
    int right = D[last] + cardGameValue(D, first, last - 1, scores);
    score = Math.Max(left, right);

    // and enter the score into the cache array:
    scores[firstOriginal, lastOriginal] = score;

    // Finally, return the computed score.
    return score;
}

即使是300张卡,我的机器上运行时间不到1 毫秒

答案 1 :(得分:2)

您可以尝试动态编程,只需将中间结果存储在一些合适的数据结构中,当您需要调用递归调用时,只需使用存储的值!

您可以使用int的二维数组来存储结果。 [i][j]处的元素会将游戏结果与D[i]通过D[j]存储在一起。从第0行开始,然后使用结果填充第1行,依此类推。这将在O(n ^ 2)时间内计算结果。

答案 2 :(得分:1)

这些行正在分配新的List对象,更不用说GetRange方法本身会创建一个新的List对象,因此每次执行时,这两行代码总共创建了4个新的List对象。如果列表中有大量项目,这可能相对昂贵。

        int left = cardGameValue(new List<int>(D.GetRange(1, D.Count - 1)), myScore + D[0], opponentScore);

        int right = cardGameValue(new List<int>(D.GetRange(0, D.Count - 1)), myScore + D[D.Count - 1], opponentScore);

您可以修改方法签名以获取startIndex和length参数,以便每次调用cardGameValue都可以重用相同的List实例。

static int cardGameValue(List<int> D, int startIndex, int length, int myScore, int opponentScore)

也许像这样进行递归调用:

        int left = cardGameValue(D, startIndex + 1, length - 1, myScore + D[startIndex], opponentScore);

        int right = cardGameValue(D, startIndex, length - 1, myScore + D[startIndex + length - 1], opponentScore);

即。引用0索引的代码(例如D[0]D.RemoveAt(0))需要修改为使用startIndex,例如D[startIndex]D.RemoveAt(startIndex)引用D.Count的代码需要使用startIndex + length重新提及。 更正:引用D.Count - 1的代码需要替换为length - 1startIndex + length - 1(取决于上下文),但仅引用D.Count的代码只是替换为length