有没有办法使此递归函数更快?

时间:2019-11-19 20:24:35

标签: c# math

我正在尝试获取总计为28的数字(这只是一个示例,但是可以更改目标数字)。下面的代码大约需要4-5分钟。有没有一种方法可以使总和为“ 28”的数字组合更快一些 输出示例 0,0,0,0,1,1,1,2,2,3,3,3

    private void Button1_Click(object sender, EventArgs e)
    {
        int up = 28;
        int rows = 12;

        int[] chosen = new int[rows + 1];
        CalculateCombination(chosen, 0, rows, 0, up - 1);
    }

    public void CalculateCombination(int[] chosen, int index, int r, int start, int end)
    {
        if (index == r)
        {
            return;
        }
        for (int i = start; i <= end; i++)
        {
            chosen[index] = i;
            CalculateCombination(chosen, index + 1, r, i, end);
            if (chosen.Sum() + chosen.Length == 28)
            {
                var ch = string.Join(",", chosen.Take(chosen.Length - 1));
                Debug.Write(ch);
                Debug.WriteLine("\t");
            }
        }
        return;
    }

1 个答案:

答案 0 :(得分:3)

首先,我假设您正在执行作业。 您需要引用可以帮助您的资源,而不是在学术背景下冒充我的工作,因此请确保在您获得任务帮助的文档中进行记录。不要只是复制解决方案并声明为您自己的解决方案。


更新:我从描述中误解了这个问题;我理解问题是要找到长度为12的单调非递减序列。问题已由原始发帖人在评论中阐明;该问题已得到解决。问题是要找到给定长度的所有非负序列,例如12,它们加起来等于给定值,例如28。有1676056044这样的序列,枚举它们都将花费一些时间。以下答案是针对我最初理解的问题。


是的,与您在此处所做的相比,可以更有效,更明确地解决此问题。让我们分解一下。

首先,每个递归问题都有一个或多个基本案例。这些情况是如此简单,以至于无法将其简化为一个更简单的问题。让我们做一个入口点,然后列出基本案例。我们想要什么?我们想要给定长度的所有序列,从最小到最大的顺序是这样,以使序列求和为特定值。

public static IEnumerable<IEnumerable<int>> SumsTo(int sum, int least, int length)
{

好的,基本情况是什么?

  • 如果长度为零且总和为零,那么空序列是唯一的解决方案。
  • 如果长度为零且总和为非零,则没有解决方案。

我们可以轻松实现以下规则:

  if (length == 0)
  {
    if (sum == 0)
      yield return Enumerable.Empty<int>();
    yield break;
  }

好的,处理长度为零。长度非零呢?那里有基本情况吗?是。如果least * length大于sum,那么将没有任何解决方案。

  if (least * length > sum)
    yield break;

好的。我们知道长度至少为1,并且肯定有解决方案。有什么解决方案?好吧,序列中必须有第一个数字,并且它可以是最小和总和之间的任何值,所以让我们编写一个循环。

  for (int current = least; current <= sum; current += 1)
  {

给定current的解决方案是什么?它是current,后跟一个大小为length-1的序列,其中最小项为current或更大,总计为sum - current。但是我们可以递归地计算出来!

    var head = Enumerable.Repeat(current, 1);
    foreach(var tail in SumsTo(sum - current, current, length - 1))
      yield return head.Concat(tail);
  }
}

我们完成了:

    Console.WriteLine(SumsTo(28, 0, 12).Count());

按预期方式打印3036,并花费一秒钟的时间;有3036种方法将12个数字从0到28到28求和。


锻炼:将类型Sum设置为(1)为空,或(2)整数,称为head,后跟Sum(为空或仅由较大的元素组成)称为尾巴。重写解决方案,使其返回IEnumerable<Sum>而不是IEnumerable<IEnumerable<int>>

锻炼:正确设置Sum类型的工具IEnumerable<int>

锻炼:我们一遍又一遍地产生一些款项;例如,在3036个长度为12的解决方案中,总和为28个问题,其中超过100个以“ 5,5”结尾,因此我们多次分配该对象。记住您的解决方案,以便在给定相同的参数时返回相同的Sum对象。

锻炼:使用此备忘的解决方案计算您保存的大致计算量;与您使用的内存量进行对比。内存使用率上升还是下降?计算速度是上升还是下降?这告诉您关于此问题的记忆效率的什么信息?您可以提出更好的记忆算法吗?

锻炼:我想到了一个特殊的字符串模式;我们称它们为frob字符串。它是这样的:()是一个字符串字符串,而(XY)X被字符串字符串替换的Y是字符串字符串。例如(()())(()(()()))都是frob字符串。制作一个方法static IEnumerable<string> AllFrob(int s),该方法给出所有带有s括号的frob字符串。因此AllFrob(1)只是()AllFrob(2)为空;没有带有两对括号的字符串字符串。 AllFrob(3)(()())AllFrob(4)为空,AllFrob(5)(()(()()))((()())())