遞歸函數轉非遞歸(通用方法)

轉換思路

https://zhuanlan.zhihu.com/p/151322731
https://zhuanlan.zhihu.com/p/151380432

舉例

以斐波拉契數列生成爲例,典型的遞歸版本如下

int fibonacci(int n) {
        if (n == 0 || n == 1) {
            return 1;
        } else {
            return fibonacci(n - 1) + fibonacci(n - 2);
        }
    }

找到其中的方法調用,標記上 flag,並分析需要的局部變量,將代碼改寫爲

    int fibonacci(int n) {
        // flag 0 函數入口
        if (n == 0 || n == 1) {
            return 1;
        } else {
            int result0 = fibonacci(n - 1);
            // flag 1 第一個函數調用結束

            // 拿到第一個函數的返回值,保存爲局部遍歷
            int localVariable0 = result0;

            int result1 = fibonacci(n - 2);
            // flag 2 第二個函數調用結束

            // 拿到第二個函數的返回值
            int localVariable1 = result1;
            return localVariable0 + localVariable1;
        }
    }

可見函數的棧幀爲

{null/*result*/,0/*flag*/,n/*入參*/,null/*第一個局部變量*/,null/*第二個局部變量*/}

由此轉寫爲非遞歸形式

int fibonacciNoRecur(int n) {
        Deque<Object[]> stack = new ArrayDeque<>(64); //棧 調用層數最大 64

        // 當前函數(fibonacciNoRecur)的棧幀,棧幀size=1,僅用來接收遞歸調用的返回值
        Object[] frameOfFibonacciNoRecur = {null};
        stack.push(frameOfFibonacciNoRecur); // 入棧

        // 函數調用第一層
        // 棧幀含義:
        // [0] 接收調用函數的返回值
        // [1] flag,標記遞歸函數執行的位置
        // [2] 入參,只有一個,即 n
        // [3] [4] 兩個局部變量
        Object[] callFrame = {null, 0, n, null, null};
        stack.push(callFrame);

        while (stack.size() > 1) {// 如果棧大於 1,表示遞歸沒有結束
            Object[] frame = stack.peek();// 獲取當前棧幀,peek()不退棧
            int arg0 = (int) frame[2];// 當前入參
            int flag = (int) frame[1];// 當前 flag 標誌位
            switch (flag) {
                case 0: // flag == 0 函數入口
                    if (arg0 == 0 || arg0 == 1) {
                        stack.pop();// 退棧
                        stack.peek()[0] = 1;// 將返回值給調用者,即此時棧頂棧幀 0 號位置
                    } else {
                        //函數調用,開闢棧空間
                        stack.push(new Object[]{null, 0, arg0 - 1, null, null});
                        frame[1] = 1;// 修改當前棧幀的 flag=1
                    }
                    break;
                case 1: // flag == 1 第一個函數調用結束
                    int return1 = (int) frame[0];// 拿到返回值,位於當前棧幀的 0 號位置
                    frame[3] = return1;// 將返回值存入局部變量
                    stack.push(new Object[]{null, 0, arg0 - 2, null, null});// 第二次函數調用
                    frame[1] = 2;// 修改當前棧幀的 flag=2
                    break;
                case 2:
                    int return2 = (int) frame[0];// 拿到返回值,位於當前棧幀的 0 號位置
                    frame[4] = return2;// 將返回值存入局部變量
                    int localVariable0 = (int) frame[3];// 取出局部變量
                    int localVariable1 = (int) frame[4];
                    // 退棧
                    stack.pop();// 退棧
                    stack.peek()[0] = localVariable0 + localVariable1;// 將返回值給調用者,即此時棧頂棧幀 0 號位置
            }
        }

        // 遞歸調用結束,此時棧頂即當前函數(fibonacciNoRecur)的棧幀
        frameOfFibonacciNoRecur = stack.pop();
        return (int) frameOfFibonacciNoRecur[0];
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章