在基于栈的运行时环境中,每次函数调用都在栈顶产生新的桢,并且将控制权交给被调用函数,当被调用函数返回时,相对应的桢从栈顶弹出,将控制权重新交给调用方,调用方从函数调用的下一条语句处继续执行。桢中保存的信息,主要分为两类:
本文将说明如何通过模拟桢和栈消除递归。
需要说明的是消除递归不一定非要使用栈,因为递归只是算法设计的一种方式。比如对于斐波那契数列而言,可以使用递推的方式实现:
package main
import "fmt"
func Fibo(n int) int {
var result int
first := 1
second := 2
if n <= 2 {
return n
}
for i := 3; i <= n; i++ {
result = first + second
first = second
second = result
}
return result
}
func main() {
// 1 2 3 5 8 13
fmt.Println(Fibo(6))
}
但是使用栈,一定可以消递归。
下面以快速排序为例进行说明:
package main
import (
"cmp"
"fmt"
)
// 分区处理
func partition[T cmp.Ordered](arr []T, start, end int) int {
i, j, pivot := start, end, arr[start]
for i < j {
for ; j > i; j-- {
if arr[j] < pivot {
arr[i] = arr[j]
i++
break
}
}
for ; i < j; i++ {
if arr[i] > pivot {
arr[j] = arr[i]
j--
break
}
}
}
arr[i] = pivot
return i
}
// QuickSort 是快速排序的递归实现
func QuickSort[T cmp.Ordered](arr []T) {
quickSort(arr, 0, len(arr)-1)
}
func quickSort[T cmp.Ordered](arr []T, start, end int) {
if start >= end {
return
}
m := partition(arr, start, end)
quickSort(arr, start, m-1)
quickSort(arr, m+1, end)
}
// 快速排序的消递归实现
type frame[T cmp.Ordered] struct {
// 实参
arr []T
start int
end int
// 局部变量
m int
// 前一个桢的桢指针
pre *frame[T]
// 返回地址(函数调用的下一条指令的地址)
addr int
// 返回结果
result any
}
func (f *frame[T]) execute(addr int, _ any) *frame[T] {
switch addr {
case 0:
if f.start >= f.end {
return nil
}
f.m = partition(f.arr, f.start, f.end)
// 返回新的桢
return NewFrame(f.arr, f.start, f.m-1, f, 1)
case 1:
// 返回新的桢
return NewFrame(f.arr, f.m+1, f.end, f, 2)
case 2:
// 设置执行结果,并且返回 nil,表明调用结束
f.result = nil
return nil
default:
panic("invalid addr")
}
}
func NewFrame[T cmp.Ordered](arr []T, start, end int, pre *frame[T], addr int) *frame[T] {
return &frame[T]{
arr: arr,
start: start,
end: end,
pre: pre,
addr: addr,
}
}
// QuickSortStack 是快速排序的消递归实现
func QuickSortStack[T cmp.Ordered](arr []T) {
var stack []*frame[T]
var addr = 0
var result any = nil
var activeFrame *frame[T]
var nextFrame *frame[T]
// 将第一个桢压栈
stack = append(stack, NewFrame(arr, 0, len(arr)-1, nil, addr))
// 循环处理
for len(stack) > 0 {
activeFrame = stack[len(stack)-1]
nextFrame = activeFrame.execute(addr, result)
if nextFrame == nil {
addr = activeFrame.addr
result = activeFrame.result
stack = stack[:len(stack)-1]
} else {
stack = append(stack, nextFrame)
addr = 0
result = nil
}
}
}
func main() {
arr := []int{11, -1, 22, 1, 3, -4, 22, 999}
QuickSortStack(arr)
fmt.Println(arr)
}
QuickSortStack 是递归函数 QuickSort 的消递归实现。它的执行流程主要是:
addr 保存桢对象的返回地址,result 保存桢对象的返回值,初始时,addr 为 0,也就说,所有桢第一次都从位置 0 开始执行addr 和 result 调用该桢对象的 execute 方法
execute 方法返回 nil,说明进入递归返回段。首先将这个执行结束的桢对象从栈顶弹出,然后使用 addr 和 result 保存该桢对象的返回地址和返回值,在下次循环时,将会从位置 addr 继续执行前一个桢对象execute 方法返回一个桢对象,说明进入递归前进段。首先将这个新的桢对象压入栈顶,然后将 addr 和 result 清零,在下次循环时,将从位置 0 开始执行这个新的桢对象result 的值就是递归调用的返回值1,当函数间接的调用自身时,比如 f 调用 g,g 又调用 f,此时: