【问题标题】:Recursive function to reverse the digit of an integer number递归函数反转整数的数字
【发布时间】:2018-04-13 16:39:46
【问题描述】:

我的目标是编写一个递归程序来反转整数的数字。当我测试第一个元素时,代码有效。但是,它不适用于其他两种情况,例如reverse(456) 打印 321654,reverse(731) 打印 321654137。 我认为问题在于public static variable reverted

有人可以帮我理解问题并解决它吗?

using System;  
public class Program
{
    public static void Main(string[] args)
    {
        reverse(123);
        reverse(456);
        reverse(731);
    }

    public static int reverted=0;

    public static void reverse(int number)
    {
        if (number!=0)
        {
            int remainder = number % 10;
            reverted = (reverted*10)+remainder;
            reverse(number/10);
        } 
        else
            Console.WriteLine(reverted);
    }
}

【问题讨论】:

  • “我认为问题在于公共静态变量已恢复” -- 什么问题?工厂给你发了一个有缺陷的整数吗?它是否超出规格并与相邻线摩擦?在纸上弄清楚每一步的所有变量值应该是什么。然后单步调试调试器中的代码,看看哪里出错了。然后修复它。
  • 直接的问题是你没有在调用之间重置reverted,所以当你开始第二个例子时它不是0。您可以通过调试代码看到这一点,这是学习编码时的一项重要技术。
  • 为此使用字符串方法可能很奇怪,但我想我会亲自用.ToString()Array.Reverse 解决这个问题。不使用递归方法。或者至少只是一个循环除以 10 并放入一个列表中。
  • 如果你正在创建一个改变静态的递归方法,那么你做的事情非常非常非常错误。
  • @Nyerguds:显然工业界没有明智的人会递归地解决这个问题,但工业界没有明智的人会首先解决这个问题。 给出的问题几乎可以肯定是针对课堂教学递归,而不是真实世界的示例。不幸的是,许多教授递归的课程都选择了像这样的问题,这些问题在没有递归的情况下更容易解决,然后学生们认为递归令人困惑和愚蠢。

标签: c# recursion digit


【解决方案1】:

我的目标是编写一个递归程序来反转整数的数字。

由于这是行业中没有人可能希望实现的目标,因此这对于一门课程来说很有可能。特别是,即使有这个问题,任何明智的人都不会用递归来解决这个问题。所以这是一个教递归的糟糕问题,因为非递归解决方案是如此简单。

你不太了解递归的可能性也很大。从某种意义上说,这是一个很好的练习,因为它需要您使用一种称为累加器的特殊技术来实现递归解决方案。

此外,即使您修复了错误,您的解决方案也不会产生所需的数字。它打印它,这是完全不同的。我们希望能够产生结果,而不仅仅是显示它。

(然后提出了一个有趣的问题,即如何处理适合 int 但其反向不适合的数字,例如 1000000009。让我们暂时忽略这些问题。)

让我们从基础开始。每个递归方法都有相同的结构:

  • 我们是否处于基本情况?如果是,则返回结果。
  • 我们不在基本情况下。
  • 将问题分解为更小的问题
  • 递归解决每个问题
  • 结合解决方案来解决更大的问题
  • 返回结果。

让我们天真地将此模式应用于您的问题,看看会发生什么。

基本情况很简单:

  • 从 0 到 9 的任何非负数的倒数都是它自己。

什么是递归情况?假设我们有 157 个

  • 将该数字除以 10 得到一个较小的数字,即 15
  • 反转较小的数字 -- 51
  • 将原始数字的低位数字附加为反向较小数字的高位数字,751 --- 等等,我们如何做到这一点

不清楚如何执行最后一个操作。

我们需要变得更聪明。

这就是你要做的。 (我省略了拒绝负数的错误检查。)

static int ReverseHelper(int number, int accumulator) =>
  number == 0 ? 
    accumulator : 
    ReverseHelper(number / 10, accumulator * 10 + number % 10);

public static int Reverse(int number) =>
  ReverseHelper(number, 0);

让我们看一些案例。

假设number0。然后ReverseHelper 返回0,很好。零工作。

假设number3。然后我们调用ReverseHelper(3, 0),它调用ReverseHelper(0, 3),它返回3。一位数有效。

假设number35。我们调用ReverseHelper(35, 0),它调用ReverseHelper(3, 5),它调用ReverseHelper(0, 53),它返回53。两位数有效。

等等。

练习:有一种直接的方法来处理反转 int 的问题,其中反转不适合 int;这是什么?

你明白我希望为什么使用累加器来调用这种技术。随着递归的进行,所需的结果逐渐累积,然后在递归达到其基本情况时返回。

现在,请注意这项技术与您的原始程序有何关联。 您正在使用静态字段作为累加器。永远不要那样做!曾经!递归方法的正确性不应依赖于外部状态。 相反,将您将递归使用的值传递到对辅助方法的递归调用中。

仔细研究这项技术。这是编写递归程序的强大技术。在函数式语言中,这样的操作称为foldreduce;向那些寻求进一步的灵感。在 C# 中,它被称为 Aggregate,用于生成序列的汇总结果。

使用累加器的递归模式是:

  • 我们是否处于基本情况?如果是,则返回累加器的结果。
  • 我们不在基本情况下。
  • 将问题分解为更小的问题
  • 递归地解决每个问题,通过将有关解决方案的信息累积到递归调用的累加器中
  • 结合解决方案来解决更大的问题
  • 返回结果。

为什么这项技术如此有价值?在 C# 中,这不太重要,但在 尾递归函数式语言中,编译器通常可以将使用累加器的递归方法优化为比非尾递归方法更有效的代码,因此还有待研究。

它在 C# 中很有价值,因为使用累加器的尾递归方法可以通过简单的程序转换过程轻松转换为非递归方法

让我们看看如何。我们有:

static int ReverseHelper(int number, int accumulator) =>
  number == 0 ? 
    accumulator : 
    ReverseHelper(number / 10, accumulator * 10 + number % 10);

让我们将其重写为一个语句:

static int ReverseHelper(int number, int accumulator)
{
  if (number == 0)
    return accumulator;
  else
    return ReverseHelper(number / 10, accumulator * 10 + number % 10);
  }
}

现在让我们为所有子表达式创建解释变量:

static int ReverseHelper(int number, int accumulator)
{
  if (number == 0)
    return accumulator;
  else 
  {
    int newNumber = number / 10;
    int newAccumulator = accumulator * 10 + number % 10;
    return ReverseHelper(newNumber, newAccumulator);
  }
}

到目前为止一切顺利。但是现在我们注意到我们可以把整个事情变成一个循环!

static int ReverseHelper(int number, int accumulator)
{
  while (true) 
  {
    if (number == 0)
      return accumulator;
    else 
    {
      int newNumber = number / 10;
      int newAccumulator = accumulator * 10 + number % 10;
      number = newNumber;
      accumulator = newAccumulator;
      // And now the loop starts over!
    }
  }
}

当然,我们看到这现在具有问题的直接非递归解决方案的形式。

这应该让您深入了解递归程序的本质:

  • 每个递归程序都可以转换为尾递归程序,但并不总是很容易看出如何。
  • 每个尾递归程序都可以转换为非递归循环。
  • 反之亦然!每个带有循环的程序都可以翻译成尾递归程序!

再次仔细研究这些简单的技巧。理解递归程序和非递归程序之间的关系是 CS 理论中的一个关键工具。

【讨论】:

  • 非常感谢您的课程。我确实试图更好地理解递归。到目前为止,我只是避免使用它,但我意识到有时它很有用,我需要强大的函数才能在多线程环境中工作。我将使用这个答案作为来源:)
  • 一般来说,考虑到C#代码的可读性和可维护性,你更喜欢纯递归方案、尾递归方案还是非递归方案?
【解决方案2】:

试试我的方法:

public static int reverse(int number, int result = 0, int reminder = 0)
{
    if (number == 0)
    {
        return result * 10 + reminder;
    }
    return reverse(number / 10, result * 10 + reminder, number % 10);
}

只需调用reverse(123) 之类的方法即可。另外两个参数resultreminder 是可选的,因为它们有一个默认值。

Demo here

编辑:

这是上面代码的简洁版本:

public static int reverse(int number, int result = 0)
{
    if (number == 0)
    {
        return result;
    }
    return reverse(number / 10, result * 10 + number % 10);
}

或者使用三元运算符:

public static int reverse(int number, int result = 0)
{
    return number == 0 ? result : reverse(number / 10, result * 10 + number % 10);
}

【讨论】:

  • 您的解决方案不错,但它可能更简洁,重复代码更少,界面更简单。请参阅我的回答,了解如何收紧代码。
【解决方案3】:

使用递归反转输入数字

    public static int ReverseRecursive(int number)
    {
        int remainder = number % 10;
        number = number / 10;
        if (number < 1)
            return remainder;
        return ReverseRecursive(number) + remainder * Convert.ToInt32(Math.Pow(10, number.ToString().Length));
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-03-26
    • 2013-02-25
    • 2019-06-25
    • 2014-12-03
    • 2022-01-09
    • 2013-11-05
    • 2016-07-17
    相关资源
    最近更新 更多