在之前的“求字符串最长不连续回文序列的深入研究“文章里我使用了性能欠佳的递归来解决问题,后为了达到消除递归的目的在网上看到了使用模拟状态机方法消除,感觉很不错,学习之后在这里进行一下总结。
其实这个方法很简单,主要步骤如下:
- 写出递归函数
- 根据递归函数画出状态机流程图,除了起止节点,将其他节点标序号
- 根据状态机创建Context类
- 定义状态机方法
首先介绍一下Context对象:
状态机中,会充斥着各种各样的条件(C),每个条件对应Context的一个返回boolean类型的方法,另外递归函数中同时充斥着上下文相关的变量(就是jvm局部变量表用到的那些),这些变量都要放到Context里作为成员变量。
Context还需要有parent引用,用来模拟出栈入栈(当然可以用真正的栈,就不需要parent引用了),还需要一个baby方法,返回新的Context对象并将this赋予新的parent,用来模拟入栈(同样也可以使用真正的栈来替代)
接下来举几个例子来说明如何使用这种方法消除递归。
递归求阶乘
1.写出递归函数
def f( n : Int ) : Int = {
if( n == 1 ) 1
else {
val t = f( n - 1 )
t * n
}
}
2.根据递归函数画出状态机流程图,并标号
其实状态图很简单,就是找条件找条件找条件。。。遇到递归就入栈,遇到return就判断栈空
上图中:
状态1.C1对应代码中的n==1
状态2.C2对应栈空(用对应于Context的parent==null)
Act1对应于t * n,也就是出栈拿到子上下文结果t后,使用t做一些事情
状态3.上下文入栈(包装)对应于调用f(n-1),在这里创建新Context对象
状态4.上下文出栈(拆装)用来模拟出栈返回结果
3.根据状态机创建Context类:
case class Context(parent : Context , n : Int , var result : Int){
def c1() = n == 1
def c2() = parent == null
def act1(resultFromChild : Int) = result = resultFromChild * n
def baby : Context = Context( this , n - 1 , 1 )
}
4.根据状态机创建状态机方法:
def stateMachine( _ctx: Context ): Int = {
var (ctx, stateNum) = (_ctx , 1 )
while( stateNum != -1 ){//-1 退出
stateNum match {
case 1 => stateNum = if( ctx.c1 ) 2 else 3
case 2 => stateNum = if( ctx.c2 ) -1 else 4
case 3 =>
ctx = ctx.baby
stateNum = 1
case 4 =>
ctx.parent.act1( ctx.result )
ctx = ctx.parent
stateNum = 2
case _ =>
}
}
ctx.result
}
6.客户端调用:
def main(args: Array[String]): Unit = {
println( stateMachine( Context( null , 10, 1 ) ) )
}
下面看一个复杂一点的,快速排序:
1.递归函数:
def sort( low : Int , high : Int , arr : Array[Int] ) : Unit ={
var ( l , h , p ) = ( low , high , arr( low ) )
while( l < h ){
while ( l < h && arr( h ) >= p ) h -= 1
if( l < h ) swap( l , h , arr )
while( l < h && arr( l ) <= p ) l += 1
if( l < h ) swap( l , h , arr )
}
if( l > low ) sort( low , l - 1 , arr )
if( h < high ) sort( l + 1 , high , arr )
}
def swap(i: Int, j: Int, arr: Array[Int]) = {
val t = arr(i)
arr(i) = arr(j)
arr(j) = t
}
可以看到在sort里面两处调用递归,这个时候在出栈处理的时候有些不同
2.画出状态图:
在有多处调用递归的时候,Context需要新加一个callsite成员,这样恢复上下文后,调用ctx的route方法判断下一状态是多少
3.创建Context类:
case class Context(
var parent : Context,
var l : Int , var h : Int,
var low : Int , var high : Int,
var callsite : Int
)
{
override def clone(): AnyRef = Context(parent,low,high,low,high,callsite)
//条件,c1,c2,c3...太多,这里写到一起
def c(i : Int) : Boolean = i match {
case 1 => l < h
case 2 => c( 1 ) && arr( h ) >= arr( low )
case 3 => c( 1 )
case 4 => c( 1 ) && arr( l ) <= arr( low )
case 5 => c( 1 )
case 6 => l > low
case 7 => h < high
case 8 => parent == null
}
//同理,写到一起
def act(i : Int) : Unit = i match {
case 1 => h -= 1
case 2 => swap( l , h , arr )
case 3 => l += 1
case 4 => act( 2 )
}
def route = callsite match {
case 6 => 7
case 7 => 8
}
def baby(callsite : Int) : Context= {
val res = this.clone.asInstanceOf[Context]
res callsite_= callsite
res parent_= this
callsite match {
case 6 =>
res high_= l - 1
res h_= l - 1
case 7 =>
res low_= l + 1
res l_= l + 1
}
res
}
}
4.创建状态机
def stateMachine( _ctx: Context ): Unit = {
var (ctx, nextState , callsite) = (_ctx , 1 , 0)
//如果满足,则转到ify状态,否则转到ifn状态
def ~~ ( ynum : Int , nnum: Int , ify : => Any = None , ifn : => Any = None ) ={
val y = ctx c nextState
if( y ) ify else ifn
nextState = if( y ) ynum else nnum
}
while( nextState != -1 ){//-1 退出
nextState match {
case 1 => ~~ ( 2 , 6 )
case 2 => ~~ ( 2 , 3 , ctx act 1 )
case 3 => ~~ ( 4 , 4 , ctx act 2 )
case 4 => ~~ ( 4 , 5 , ctx act 3 )
case 5 => ~~ ( 1 , 1 , ctx act 4 )
case 6 => ~~ ( 10 , 7 , callsite = 6)
case 7 => ~~ ( 10 , 8 , callsite = 7)
case 8 => ~~ ( -1 , 9 )
case 9 =>
nextState = ctx.route
ctx = ctx.parent
case 10 =>
ctx = ctx baby callsite
nextState = 1
case _ =>
}
}
}
5.客户端调用
var arr : Array[Int] = _
def main(args: Array[String]): Unit = {
arr = Array(2,3,5,1,6)
stateMachine( Context(null , 0 , arr.length - 1 , 0 , arr.length - 1 , 0) )
println( arr toList )//List(1,2,3,5,6)
}