589. N叉樹的前序遍歷-用寫CPU指令執行器的方式寫遞歸遍歷

做題目前隨便說點

  • 樹是一種抽象數據類型,一種具有樹結構形式的數據集合。

  • 節點個數確定,有層次關係。

  • 有根節點。

  • 除了根,每個節點有且只有一個父節點。

  • 沒有環路。

  • 所有數據結構都可以用鏈表表示或者用數組表示,樹也一樣。

589. N叉樹的前序遍歷

給定一個 N 叉樹,返回其節點值的前序遍歷。

例如,給定一個 3叉樹 :

1
| \
3 2 4
|
5 6
返回其前序遍歷: [1,3,5,6,2,4]。

說明: 遞歸法很簡單,你可以使用迭代法完成此題嗎?

來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/n-ary-tree-preorder-traversal
著作權歸領釦網絡所有。商業轉載請聯繫官方授權,非商業轉載請註明出處。

解題:

審題:

代碼框架如下:

class Solution {

    
Node  recursive(Node node) {
        // 邊界判斷
        if(...) {
            return node;
        }
        
        // 先序遍歷node
        
       // 遍歷子節點
       for( Node son : node.children ) {
            recursive(son);
        }
        
        // 後序遍歷node

        // 返回值。
        return node;
    }
}
  • 多叉樹沒有絕對意義上的中序遍歷。中序遍歷是二叉樹獨有的概念,除非你把n叉樹的分支分爲兩堆?這個看起來有點怪怪的。

開始解題:


/*
// Definition for a Node.
class Node {
    public int val;
    public List<Node> children;
    public Node() {}
    public Node(int _val) {
        val = _val;
    }
    public Node(int _val, List<Node> _children) {
        val = _val;
        children = _children;
    }
};
*/
class Solution {
    public static List<Integer> list;
    Node recursive(Node node) {
        // 邊界判斷
        if(node == null) {
            return node;
        }
        list.add(node.val);
       // 遍歷子節點
       for( Node son : node.children ) {
            recursive(son);
        }
        // 返回值。
        return node;
    }
    public List<Integer> preorder(Node root) {
        list =  new ArrayList();
        recursive(root);
        return list;
    }
}



總結:

迭代法(模擬計算機的遞歸函數執行過程):

  • 計算機的本質就是一個while循環,然後不停的執行函數指令。
  • 我們可以定義一個函數體,然後將root節點作爲函數的參數構造出函數體,交給一個while循環執行。
  • 其實我們知道一個計算機處理器,除了一個while循環,還是一個函數棧stack;
  • 其他與遞歸有關的細節我們可以耦合到函數體裏面去(畢竟我們又不是寫虛擬機對吧,不需要做到這個高級的封裝。)


/*
// Definition for a Node.
class Node {
    public int val;
    public List<Node> children;
    public Node() {}
    public Node(int _val) {
        val = _val;
    }
    public Node(int _val, List<Node> _children) {
        val = _val;
        children = _children;
    }
};
*/
class Solution {
    // 定義函數體,等價於一份遞歸代碼
    class FunsBody {
        Node arg;
        public List<Node> otherCallArg;
        int returnVal;
        int programCounter; 
        public FunsBody(Node node) {
            this.arg = node;
            this.otherCallArg = node.children; 
            this.returnVal = node.val;
            this.programCounter = node.children.size();
        }
    }
    // 定數函數輸出接口
    class SystemOut {
        public List<Integer> list;
        SystemOut() {
            this.list = new ArrayList();
        }
        public void print(int returnVal) {
            this.list.add(returnVal);
        }
        public List<Integer> getConsole() {
            return this.list;
        }
    }
    public List<Integer> postorder(Node root) {
        SystemOut systemOut =  new SystemOut();
        Stack<FunsBody> funcStack = new Stack();
        // 參數校驗
        if( root == null ) {
            return systemOut.getConsole();
        }
        
        // 將代碼指令放入函數棧
        funcStack.push(new FunsBody(root));
        
        // 執行函數
        while(!funcStack.isEmpty()) {
            FunsBody func = funcStack.pop();
            if(func.programCounter == 0) {
                // 由於這裏我們要模擬後序遍歷,所以要等所有指令執行完成,函數才能返回
                systemOut.print(func.returnVal);
            } else {
                // 保護函數現場,將代碼指令放入函數棧
                funcStack.push(func);
                // 將新函數的代碼指令放入函數棧頂部等待執行
                funcStack.push(new FunsBody(func.otherCallArg.get(func.otherCallArg.size() - func.programCounter)));
                // 調整該函數的PC程序計數器。
                func.programCounter--;
            }
        }
        return systemOut.getConsole();
    }
}

  • 上面我們定義了一個函數體,函數體包含了現場PC程序計數器,以及參數、其他外部函數調用指令和返回值表達式。
  • 還定義了一個函數棧和while循環,while會不停的遍歷函數棧中的函數體,如果函數沒有外部函數調用(程序計數器將要指向函數體結束位置時),就執行返回值表達式。
  • 如果執行外部函數需要,先保存現場到函數棧,然後將外部函數體入棧頂,調整程序計數器計數器。

代碼框架如下:



/*
// Definition for a Node.
class Node {
    public int val;
    public List<Node> children;
    public Node() {}
    public Node(int _val) {
        val = _val;
    }
    public Node(int _val, List<Node> _children) {
        val = _val;
        children = _children;
    }
};
*/
class Solution {
    // 定義函數體,等價於一份遞歸代碼
    class FunsBody {
        // 參數
        Node arg;
         // 外部函數調用
        public List<Command> otherCall;
        // 返回值表達式
        int returnVal;
        // 程序計數器
        int programCounter; 
        
    }
   
    public List<Integer> postorder(Node root) {
        
        // 邊界判斷
        if( ....) {
            return systemOut;
        }
        // 開機調用main函數
        
        // 將代碼指令放入函數棧,調用main入口函數
        funcStack.push(new FunsBody(root));
        
        // 執行函數
        while(!funcStack.isEmpty()) {
            FunsBody func = funcStack.pop();
            // 程序指針指向完成,返回函數值
            if(func.programCounter == 0) {
                // 由於這裏我們要模擬後序遍歷,所以要等所有指令執行完成,函數才能返回
                systemOut.print(func.returnVal);
            } else {
                // 否則執行外部函數
                // 保護函數現場,將代碼指令放入函數棧
                funcStack.push(func);
                // 將新函數的代碼指令放入函數棧頂部等待執行
                funcStack.push(func.nextOutFuncBody);
                // 調整該函數的PC程序計數器。
                func.programCounter--;
            }
        }
        // 釋放計算機資源,然後關機
        return ...; 
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章