【问题标题】:Keeping track of which recursive calls have been done already - C#跟踪已经完成的递归调用 - C#
【发布时间】:2015-11-15 14:06:42
【问题描述】:

我正在解决this problem,其中要求我提供 20 x 20 网格中从一个角到另一个角的最短路径数。我知道这是一个简单的组合问题,但我面临的挑战是实现一个解决它的方法。

我的想法是使用递归:到网格点 (m,n) 的路径数等于到 (m-1,n) 的路径数加上到 (m,n-1) 的路径数.我从以下代码开始:

using System;

class Program
{
    static void Main()
    {
        long noOfPaths = CountPaths(15, 15);
        Console.WriteLine(noOfPaths);
        Console.ReadKey();
    }

    static long CountPaths(int m, int n)
    {
        if (m == 0) return 1;
        else if (n == 0) return 1;

        return CountPaths(m-1,n) + CountPaths(m,n-1);
    }
}

这很好用,并返回正确数量的路径,但是随着网格大小的增加,它的运行时间会急剧增加,而且我无法达到 20x20。上述的主要问题之一是它不止一次地在同一个网格点上进行递归调用,我想要一些关于跟踪这一点的最佳方法的建议。到目前为止,我已经在这个站点周围找到了关于“全局变量”的帖子,我的解决方案是创建一个可以从任何地方访问的数组。下面的代码解决了我的问题,而且速度也相当快。

using System;

namespace problem15
{
class Program
{
    static void Main()
    {
        long noOfPaths = CountPaths(Values.m-1, Values.n-1);
        Console.WriteLine(noOfPaths);
        Console.ReadKey();
    }

    static long CountPaths(int m, int n)
    {
        if (m == 0) return 1;
        else if (n == 0) return 1;

        if (Values.A[m - 1, n] == 0) Values.A[m - 1, n] = CountPaths(m - 1, n);
        if (Values.A[m, n - 1] == 0) Values.A[m, n - 1] = CountPaths(m, n - 1);

        return Values.A[m-1,n] + Values.A[m,n-1];
    }
}

static class Values
{
    static public int m = 21, n = 21;
    static public long[,] A = new long[m, n];
}
}

这是解决问题的好方法,还是被认为是“糟糕的形式”?另外,我知道这个问题还有更多的优化,例如到 (k,l) 的路径数与到 (l,k) 的路径数相同。

【问题讨论】:

  • 解决此问题的常用方法是使用 动态编程 解决方案之一(使用记忆的递归 - 在 C# 中不推荐)或使用表格 - 只需 google 即可获得 动态规划 你会很快找到这个确切的问题
  • @Carsten 谢谢。据我所知,动态编程是将解决方案保存到您已经完成的子问题中,如果稍后出现子问题,您将使用保存的解决方案而不是重新计算它。在我看来,这类似于我对问题的第二种解决方案所做的。
  • 这比 StackOverflow 更适合 CodeReview。

标签: c# recursion


【解决方案1】:

你的递归解决方案没问题。

其他递归解决方案也可以,它是以“动态编程”方式实现的递归选项。

还有一种迭代和数学方法。

你可以看到更多here。来自官网。

【讨论】:

    【解决方案2】:

    您的第二个解决方案中的想法很好,这是一种众所周知的技术,称为Memoization。但是实现不是。使用共享(“全局”)状态高度限制了该方法的使用,这还不包括您编写它的方式,它只能被调用一次并且参数是硬编码的。这是这样做的正确方法。

    让我们从第一个解决方案开始,将其封装在一个类中,并将非递归部分与递归部分分开:

    public class MyAlgorithms
    {
        public static long CountPaths(int m, int n)
        {
            // Agrument validations goes here
            return CountPathsRecursive(m, n);
        }
    
        private static long CountPathsRecursive(int m, int n)
        {
            if (m == 0 || n == 0) return 1;
            var count = CountPathsRecursive(m - 1, n) + CountPathsRecursive(m, n - 1);
            return count;
        }
    }
    

    并使用它

    using System;
    
    class Program
    {
        static void Main()
        {
            long noOfPaths = MyAlgorithms.CountPaths(21, 21);
            Console.WriteLine(noOfPaths);
            Console.ReadKey();
        }
    }
    

    现在您可以通过应用第二个想法优化实现而不影响使用

    public class MyAlgorithms
    {
        public static long CountPaths(int m, int n)
        {
            // Agrument validations goes here
            var counts = new long[m, n];
            return CountPathsRecursive(m, n, counts);
        }
    
        private static long CountPathsRecursive(int m, int n, long[,] counts)
        {
            if (m == 0 || n == 0) return 1;
            var count = counts[m - 1, n - 1];
            if (count == 0) counts[m - 1, n - 1] = count =
                CountPathsRecursive(m - 1, n, counts) + CountPathsRecursive(m, n - 1, counts);
            return count;
        }
    }
    

    希望您能理解。同样的方式,您可以更改实现以使用迭代算法、公式等。

    【讨论】:

    • 感谢您的全面解释!我相信这对我来说很有意义。我注意到您的 CountPathsRecursive 方法是私有的,而 CountPaths 是公共的。互联网上说私有方法只能从它们定义的类中调用,所以你的想法是希望程序的其他部分调用 CountPaths,因为这是正确的调用 CountPathsRecursive 的方法?这已得到证实,因为您的程序拒绝让我从 Main 调用 MyAlgorithms.CountPathsRecursive 方法。
    • 另外,您将数组“counts”传递给 CountPathsRecursive 方法。我担心每个递归调用都会创建一个占用空间的新数组,但似乎数组属于“引用类型”,这意味着无论您在后续递归调用中对名为“count”的数组做什么,它都会是受影响的原始数组。这是正确的吗?
    • @HowDoICSharply 全部正确:-)。首先是特定于实现的,不应该在外部调用。其次是对在公共方法中创建的数组的引用。在方法调用期间是一样的。就像您的全局数组一样,但只是在一个方法调用链中。
    • 谢谢。 :) 这比我原来做的更有意义,我会看看我是否可以采用这种思维方式。
    • @HowDoICSharply 我相信你会的。我看到了积极的态度。或者用尤达的方式说,“我没有黑暗的一面”:-)
    【解决方案3】:

    纯粹的 OO 解决方案只是为了好玩(既不快速也不贪婪;-)):

    有一个优势:您可以获得所有找到的路径及其完整的单元格列表。

       public class MyGrid {
            public int Width { get; protected set; }
            public int Height { get; protected set; }
    
            public MyCell[,] MyCells { get; protected set; }
    
            public List<MyPath> MyPathList;
    
            public MyGrid(int h, int w) {
                this.Width = w;
                this.Height = h;
    
                this.MyCells = new MyCell[this.Width, this.Height];
                for (int x = 0; x < Width; x++) {
                    for (int y = 0; y < Width; y++) {
                        this.MyCells[x, y] = new MyCell(this, x, y);
                    }
                }
    
                this.MyPathList = new List<MyPath>();
            }
    
            public int FindPaths() {
                this.MyPathList.Clear();
    
                var p = new MyPath(this);
                this.MyPathList.Add(p);
    
                var c = new MyCell(this,0,0);
                p.AddCellRecursive(c); 
    
                return MyPathList.Count;
            }
    
        }
        public class MyCell {
            public MyGrid myGrid { get; protected set; }
            public int X { get; protected set; }
            public int Y { get; protected set; }
            public MyCell(MyGrid gr, int x, int y) {
                this.myGrid = gr;
                this.X = x;
                this.Y = y;
            }
            public MyCell RightNeighbour {
                get {
                    if (this.X == this.myGrid.Width-1)
                        return null;
                    else
                        return this.myGrid.MyCells[this.X+1, this.Y];
                }
            }
            public MyCell BelowNeighbour {
                get {
                    if (this.Y == this.myGrid.Height-1)
                        return null;
                    else
                        return this.myGrid.MyCells[this.X, this.Y+1];
                }
            }
            public override string ToString() {
                return string.Format("{0}|{1}", this.X, this.Y);
            }
        }
        public class MyPath{
            public MyGrid myGrid { get; protected set; }
            public List<MyCell> MyCellList;
    
            public MyPath(MyGrid gr) {
                this.myGrid = gr;
                this.MyCellList = new List<MyCell>(); }
    
            public void AddCellRecursive(MyCell c) {
                this.MyCellList.Add(c);
    
                var r = c.RightNeighbour;
                var b = c.BelowNeighbour;
    
                MyPath second=null;
    
                if (b == null && r == null)
                    return;//end
    
                else if (r == null) {
                    second = this;
                }
                else {
                    second = this.Clone();
                    this.myGrid.MyPathList.Add(second);
                    this.AddCellRecursive(r);
                }
    
                if (b == null)
                    this.myGrid.MyPathList.Remove(second);
                else 
                    second.AddCellRecursive(b);
    
            }
    
            public MyPath Clone(){
                var retPath = new MyPath(this.myGrid);
                foreach (var c in MyCellList) {
                    retPath.MyCellList.Add(c);
                }
                return retPath;
            }
        }
    

    【讨论】:

      猜你喜欢
      • 2022-01-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-13
      • 1970-01-01
      • 1970-01-01
      • 2020-04-21
      • 2018-12-31
      相关资源
      最近更新 更多