算法:五步教你消除递归

来源:互联网 发布:中国房地产 知乎。 编辑:程序博客网 时间:2024/05/29 17:07

http://www.cnblogs.com/happyframework/p/3485960.html

背景

递归对于分析问题比较有优势,但是基于递归的实现效率就不高了,而且因为函数栈大小的限制,递归的层次也有限制。本文给出一种通用的消除递归的步骤,这样您可以在分析阶段采用递归思想,而实现阶段采用非递归算法。

函数的调用过程

函数的调用是基于栈,每次调用都涉及如下操作:

  • 调用开始时:将返回地址和局部变量入栈。
  • 调用结束时:出栈并将返回到入栈时的返回地址。

使用堆中分配的栈消除递归

递归版本

代码

复制代码
 1         public static int Triangle(int n) 2         { 3             // 地址:2 4             if (n == 1) 5             { 6                 // 地址:4 7                 return n; 8             } 9 10             /*   11              *   地址:4  地址:312              *     /      /13              *    /      /14              *   /      /            */15             return n + Triangle(n - 1);16         }
复制代码

非递归版本

代码

复制代码
 1         private class StackFrame 2         { 3             public int N; 4             public int ReturnAddress; 5         } 6  7         public static int Triangle2(int n) 8         { 9             var stack = new Stack<StackFrame>();10             var currentReturnValue = 0;11             var currentAddress = 1;12 13             while (true)14             {15                 switch (currentAddress)16                 {17                     case 1:18                         {19                             stack.Push(new StackFrame20                             {21                                 N = n,22                                 ReturnAddress = 523                             });24                             currentAddress = 2;25                         }26                         break;27                     case 2:28                         {29                             var frame = stack.Peek();30                             if (frame.N == 1)31                             {32                                 currentReturnValue = 1;33                                 currentAddress = 4;34                             }35                             else36                             {37                                 stack.Push(new StackFrame38                                 {39                                     N = frame.N - 1,40                                     ReturnAddress = 341                                 });42                                 currentAddress = 2;43                             }44                         }45                         break;46                     case 3:47                         {48                             var frame = stack.Peek();49                             currentReturnValue = frame.N + currentReturnValue;50                             currentAddress = 4;51                         }52                         break;53                     case 4:54                         {55                             currentAddress = stack.Pop().ReturnAddress;56                         }57                         break;58                     case 5:59                         {60                             return currentReturnValue;61                         }62                 }63             }
复制代码

消除过程

第一步:识别递归版本中的代码地址

  • 第一个代表:原始方法调用。
  • 倒数第一个代表:原始方法调用结束。
  • 第二个代表:方法调用入口。
  • 倒数第二个代表:方法调用出口。
  • 递归版本中的每个递归调用定义一个代码地址。

假如递归调用了 n 次,则代码地址为:n + 4。

复制代码
 1         public static int Triangle(int n) 2         { 3             // 地址:2 4             if (n == 1) 5             { 6                 // 地址:4 7                 return n; 8             } 9 10             /*   11              *   地址:4  地址:312              *     /      /13              *    /      /14              *   /      /            */15             return n + Triangle(n - 1);16         }
复制代码

第二步:定义栈帧

栈帧代表了代码执行的上下文,将递归版本代码体中用到的局部值类型变量定义为栈帧的成员变量,为啥引用类型不用我就不多说了,另外还需要定义一个返回地址成员变量。

1         private class StackFrame2         {3             public int N;4             public int ReturnAddress;5         }

第三步:while 循环

在 while 循环之前声明一个 stack、一个 currentReturnValue 和 currentAddress。

复制代码
 1         public static int Triangle2(int n) 2         { 3             var stack = new Stack<StackFrame>(); 4             var currentReturnValue = 0; 5             var currentAddress = 1; 6  7             while (true) 8             { 9             }10         }
复制代码

第四步:switch 语句。

复制代码
 1         public static int Triangle2(int n) 2         { 3             var stack = new Stack<StackFrame>(); 4             var currentReturnValue = 0; 5             var currentAddress = 1; 6  7             while (true) 8             { 9                 switch (currentAddress)10                 {11                     case 1:12                         {13                         }14                         break;15                     case 2:16                         {17                         }18                         break;19                     case 3:20                         {21                         }22                         break;23                     case 4:24                         {25                         }26                         break;27                     case 5:28                         {29                         }30                 }31             }32         }
复制代码

第五步:填充 case 代码体。

将递归版本的代码做如下变换:

  • 函数调用使用:stack.push(new StackFrame{…}); 和 currentAddress = 2;
  • 引用的局部变量变为,比如:n,变为:stack.Peek().n
  • return 语句变为:currentReturnValue = 1; 和 currentAddress = 4; 
  • 倒数第一个 case 代码体为:return currentReturnValue;

最终的效果就是上面的示例。

汉诺塔练习

复制代码
  1 using System;  2 using System.Collections.Generic;  3 using System.Linq;  4 using System.Text;  5 using System.Threading.Tasks;  6   7 namespace DataStuctureStudy.Recursives  8 {  9     class HanoiTest 10     { 11         public static void Hanoi(int n, string source, string middle, string target) 12         { 13             if (n == 1) 14             { 15                 Console.WriteLine(String.Format("{0}->{1}", source, target)); 16             } 17             else 18             { 19                 Hanoi(n - 1, source, target, middle); 20                 Console.WriteLine(String.Format("{0}->{1}", source, target)); 21                 Hanoi(n - 1, middle, source, target); 22             } 23         } 24  25         public static void Hanoi2(int n, string source, string middle, string target) 26         { 27             var stack = new Stack<StackFrame>(); 28             var currentAddress = 1; 29  30             while (true) 31             { 32                 switch (currentAddress) 33                 { 34                     case 1: 35                         { 36                             stack.Push(new StackFrame 37                             { 38                                 N = n, 39                                 Source = source, 40                                 Middle = middle, 41                                 Target = target, 42                                 ReturnAddress = 5 43                             }); 44                             currentAddress = 2; 45                         } 46                         break; 47                     case 2: 48                         { 49                             var frame = stack.Peek(); 50                             if (frame.N == 1) 51                             { 52                                 Console.WriteLine(String.Format("{0}->{1}", frame.Source, frame.Target)); 53                                 currentAddress = 4; 54                             } 55                             else 56                             { 57                                 stack.Push(new StackFrame 58                                 { 59                                     N = frame.N - 1, 60                                     Source = frame.Source, 61                                     Middle = frame.Target, 62                                     Target = frame.Middle, 63                                     ReturnAddress = 3 64                                 }); 65                                 currentAddress = 2; 66                             } 67                         } 68                         break; 69                     case 3: 70                         { 71                             var frame = stack.Peek(); 72                             Console.WriteLine(String.Format("{0}->{1}", frame.Source, frame.Target)); 73                             stack.Push(new StackFrame 74                             { 75                                 N = frame.N - 1, 76                                 Source = frame.Middle, 77                                 Middle = frame.Source, 78                                 Target = frame.Target, 79                                 ReturnAddress = 4 80                             }); 81                             currentAddress = 2; 82                         } 83                         break; 84                     case 4: 85                         currentAddress = stack.Pop().ReturnAddress; 86                         break; 87                     case 5: 88                         return; 89                 } 90             } 91         } 92  93         private class StackFrame 94         { 95             public int N; 96             public string Source; 97             public string Middle; 98             public string Target; 99             public int ReturnAddress;100         }101     }102 }
复制代码

二叉树遍历练习

这个练习是我之前采用的方式看,思想和上面的非常相似。

复制代码
  1 using System;  2 using System.Collections.Generic;  3 using System.Linq;  4 using System.Text;  5 using System.Threading.Tasks;  6   7 namespace DataStuctureStudy.Recursives  8 {  9     class TreeTest 10     { 11         public static void Test() 12         { 13             RecursiveTraverse(Node.BuildTree()); 14             StackTraverse(Node.BuildTree()); 15         } 16  17         private class Node 18         { 19             public Node Left { get; set; } 20  21             public Node Right { get; set; } 22  23             public int Value { get; set; } 24  25             public static Node BuildTree() 26             { 27                 return new Node 28                 { 29                     Value = 1, 30                     Left = new Node 31                     { 32                         Value = 2, 33                         Left = new Node 34                         { 35                             Value = 3 36                         }, 37                         Right = new Node 38                         { 39                             Value = 4 40                         } 41                     }, 42                     Right = new Node 43                     { 44                         Value = 5, 45                         Left = new Node 46                         { 47                             Value = 6 48                         }, 49                         Right = new Node 50                         { 51                             Value = 7 52                         } 53                     } 54                 }; 55             } 56         } 57  58         private static void RecursiveTraverse(Node node) 59         { 60             if (node == null) 61             { 62                 return; 63             } 64  65             RecursiveTraverse(node.Left); 66             Console.WriteLine(node.Value); 67             RecursiveTraverse(node.Right); 68         } 69  70         private enum CodeAddress 71         { 72             Start, 73             AfterFirstRecursiveCall, 74             AfterSecondRecursiveCall 75         } 76  77         private class StackFrame 78         { 79             public Node Node { get; set; } 80  81             public CodeAddress CodeAddress { get; set; } 82         } 83  84         private static void StackTraverse(Node node) 85         { 86             var stack = new Stack<StackFrame>(); 87             stack.Push(new StackFrame 88             { 89                 Node = node, 90                 CodeAddress = CodeAddress.Start 91             }); 92  93             while (stack.Count > 0) 94             { 95                 var current = stack.Peek(); 96  97                 switch (current.CodeAddress) 98                 { 99                     case CodeAddress.Start:100                         if (current.Node == null)101                         {102                             stack.Pop();103                         }104                         else105                         {106                             current.CodeAddress = CodeAddress.AfterFirstRecursiveCall;107                             stack.Push(new StackFrame108                             {109                                 Node = current.Node.Left,110                                 CodeAddress = CodeAddress.Start111                             });112                         }113                         break;114                     case CodeAddress.AfterFirstRecursiveCall:115                         Console.WriteLine(current.Node.Value);116 117                         current.CodeAddress = CodeAddress.AfterSecondRecursiveCall;118                         stack.Push(new StackFrame119                         {120                             Node = current.Node.Right,121                             CodeAddress = CodeAddress.Start122                         });123                         break;124                     case CodeAddress.AfterSecondRecursiveCall:125                         stack.Pop();126                         break;127                 }128             }129         }130     }131 }
复制代码

备注

搞企业应用的应该用不到这种消除递归的算法,不过学完以后对递归的理解也更清晰了。

 

原创粉丝点击