您的位置:首页 > 其它

算法:五步教你消除递归

2017-11-03 16:40 302 查看


算法:五步教你消除递归


背景

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


函数的调用过程

函数的调用是基于栈,每次调用都涉及如下操作:
调用开始时:将返回地址和局部变量入栈。
调用结束时:出栈并将返回到入栈时的返回地址。


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


递归版本

代码

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  地址:3
12              *     /      /
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 StackFrame
20                             {
21                                 N = n,
22                                 ReturnAddress = 5
23                             });
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                             else
36                             {
37                                 stack.Push(new StackFrame
38                                 {
39                                     N = frame.N - 1,
40                                     ReturnAddress = 3
41                                 });
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  地址:3
12              *     /      /
13              *    /      /
14              *   /      /            */
15             return n + Triangle(n - 1);
16         }



第二步:定义栈帧

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

1         private class StackFrame
2         {
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                         else
105                         {
106                             current.CodeAddress = CodeAddress.AfterFirstRecursiveCall;
107                             stack.Push(new StackFrame
108                             {
109                                 Node = current.Node.Left,
110                                 CodeAddress = CodeAddress.Start
111                             });
112                         }
113                         break;
114                     case CodeAddress.AfterFirstRecursiveCall:
115                         Console.WriteLine(current.Node.Value);
116
117                         current.CodeAddress = CodeAddress.AfterSecondRecursiveCall;
118                         stack.Push(new StackFrame
119                         {
120                             Node = current.Node.Right,
121                             CodeAddress = CodeAddress.Start
122                         });
123                         break;
124                     case CodeAddress.AfterSecondRecursiveCall:
125                         stack.Pop();
126                         break;
127                 }
128             }
129         }
130     }
131 }



备注

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

框架地址:http://happy.codeplex.com 

博客地址:http://www.cnblogs.com/happyframework
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: