1. 淺談二叉樹遍歷

        共 22446字,需瀏覽 45分鐘

         ·

        2022-07-31 00:24

        這里就對(duì)二叉樹的遍歷方式進(jìn)行總結(jié)

        abstract.jpeg

        基于遞歸的遍歷

        前序遍歷

        前序遍歷:即對(duì)二叉樹按照當(dāng)前節(jié)點(diǎn)、左子樹、右子樹的順序進(jìn)行遍歷。顯然借助遞歸,非常容易實(shí)現(xiàn)、理解

        /**
         * 基于遞歸的前序遍歷
         */

        class Solution {
            public List<Integer> preorderTraversal(TreeNode root) {
                List<Integer> list = new LinkedList<>();
                dfs(root, list);
                return list;
            }

            private void dfs(TreeNode cur, List<Integer> list) {
                if( cur==null ) {
                    return;
                }

                // 1. 處理當(dāng)前節(jié)點(diǎn)
                list.add( cur.val );

                // 2. 處理左子樹
                dfs(cur.left, list);

                // 3. 處理右子樹
                dfs(cur.right, list);
            }
        }

        中序遍歷

        中序遍歷:即對(duì)二叉樹按照左子樹、當(dāng)前節(jié)點(diǎn)、右子樹的順序進(jìn)行遍歷。顯然借助遞歸,非常容易實(shí)現(xiàn)、理解

        /**
         * 基于遞歸的中序遍歷
         */

        class Solution {
            public List<Integer> inorderTraversal(TreeNode root) {
                List<Integer> list = new LinkedList<>();
                dfs(root, list);
                return list;
            }

            private void dfs(TreeNode cur, List<Integer> list) {
                if( cur==null ) {
                    return;
                }

                // 1. 處理左子樹
                dfs(cur.left, list);

                // 2. 處理當(dāng)前節(jié)點(diǎn)
                list.add( cur.val );

                // 3. 處理右子樹
                dfs(cur.right, list);
            }
        }

        后序遍歷

        后序遍歷:即對(duì)二叉樹按照左子樹、右子樹、當(dāng)前節(jié)點(diǎn)的順序進(jìn)行遍歷。顯然借助遞歸,非常容易實(shí)現(xiàn)、理解

        /**
         * 基于遞歸的后序遍歷
         */

        class Solution {
            public List<Integer> postorderTraversal(TreeNode root) {
                List<Integer> list = new LinkedList<>();
                dfs(root, list);
                return list;
            }

            private void dfs(TreeNode cur, List<Integer> list) {
                if( cur==null ) {
                    return;
                }

                // 1. 處理左子樹
                dfs(cur.left, list);

                // 2. 處理右子樹
                dfs(cur.right, list);

                // 3. 處理當(dāng)前節(jié)點(diǎn)
                list.add( cur.val );
            }
        }

        基于迭代的遍歷

        前序遍歷

        不同于遞歸隱式維護(hù)棧的便捷,在通過迭代進(jìn)行遍歷時(shí)需要我們顯式地維護(hù)一個(gè)棧。具體地,在前序遍歷當(dāng)中。首先需要處理對(duì)當(dāng)前節(jié)點(diǎn)進(jìn)行處理、然后沿著左子樹一直入棧。當(dāng)左子樹遍歷完畢, 通過出棧獲取父節(jié)點(diǎn)來對(duì)右子樹再進(jìn)行處理

        /**
         * 基于迭代的前序遍歷
         */

        class Solution {
            public List<Integer> preorderTraversal(TreeNode root) {
                List<Integer> res = new LinkedList<>();
                LinkedList<TreeNode> stack = new LinkedList<>();

                while ( root!=null || !stack.isEmpty() ) {
                    while (root!=null) {
                        // 先處理當(dāng)前節(jié)點(diǎn)
                        res.add( root.val );
                        // 然后沿左子樹一直入棧
                        stack.addLast( root );
                        root = root.left;
                    }

                    // 左子樹遍歷完畢, 通過父節(jié)點(diǎn)來對(duì)右子樹再進(jìn)行處理
                    TreeNode parent = stack.removeLast();
                    root = parent.right;
                }

                return res;
            }
        }

        中序遍歷

        不同于遞歸隱式維護(hù)棧的便捷,在通過迭代進(jìn)行遍歷時(shí)需要我們顯式地維護(hù)一個(gè)棧。具體地,在中序遍歷當(dāng)中。首先沿著左子樹一直入棧。當(dāng)左子樹遍歷完畢, 通過出棧獲取父節(jié)點(diǎn)并對(duì)其進(jìn)行處理,最后對(duì)右子樹再進(jìn)行處理


        /**
         * 基于迭代的中序遍歷
         */

        class Solution {
            public List<Integer> inorderTraversal(TreeNode root) {
                List<Integer> res = new LinkedList<>();
                LinkedList<TreeNode> stack = new LinkedList<>();

                while ( root!=null || !stack.isEmpty() ) {
                    while ( root!=null ) {
                        // 先沿左子樹一直入棧
                        stack.addLast( root );
                        root = root.left;
                    }

                    // 左子樹遍歷完畢, 獲取父節(jié)點(diǎn)
                    TreeNode parent = stack.removeLast();
                    // 處理父節(jié)點(diǎn)的值
                    res.add( parent.val );
                    // 通過父節(jié)點(diǎn)對(duì)右子樹再進(jìn)行處理
                    root = parent.right;
                }

                return res;
            }
        }

        后序遍歷

        后序遍歷的順序是左、右、當(dāng)前。如果直接使用棧按這個(gè)順序進(jìn)行遍歷輸出會(huì)比較麻煩。故我們可以先按照當(dāng)前、右、左的順序進(jìn)行迭代遍歷,顯然這個(gè)過程與前序遍歷非常類似,只是需要把代碼中涉及左、右子樹的調(diào)換下即可。最后對(duì)遍歷結(jié)果進(jìn)行翻轉(zhuǎn)。這樣遍歷結(jié)果就由當(dāng)前、右、左 就變?yōu)?左、右、當(dāng)前。即我們所需的后序遍歷結(jié)果

        /**
         * 基于迭代的后序遍歷
         */

        class Solution {
            public List<Integer> postorderTraversal(TreeNode root) {
                List<Integer> res = new LinkedList<>();
                LinkedList<TreeNode> stack = new LinkedList<>();

                // 先按 根、右、左 的順序進(jìn)行遍歷
                while ( root!=null || !stack.isEmpty() ) {
                    while (root!=null) {
                        // 先處理當(dāng)前節(jié)點(diǎn)
                        res.add( root.val );
                        // 然后沿右子樹一直入棧
                        stack.addLast( root );
                        root = root.right;
                    }

                    // 右子樹遍歷完畢, 通過父節(jié)點(diǎn)獲取左子樹再進(jìn)行處理
                    TreeNode parent = stack.removeLast();
                    root = parent.left;
                }

                // 對(duì)遍歷結(jié)果進(jìn)行翻轉(zhuǎn)
                // 這樣遍歷結(jié)果就由 根、右、左 就變?yōu)?nbsp;左、右、根, 即后序遍歷
                Collections.reverse( res );
                return res;
            }
        }

        層序遍歷

        對(duì)于二叉樹的層序遍歷就簡單很多了。我們借助隊(duì)列的FIFO特性即可輕松實(shí)現(xiàn)

        class Solution {
            public List<Integer> levelOrder(TreeNode root) {
                List<Integer> res = new LinkedList<>();
                if (root == null) {
                    return res;
                }

                Queue<TreeNode> queue = new LinkedList<>();
                queue.add(root);

                while ( !queue.isEmpty() ) {
                    // 彈出隊(duì)首元素, 進(jìn)行處理
                    TreeNode cur = queue.poll();
                    res.add( node.val );

                    // 將當(dāng)前節(jié)點(diǎn)的左、右子節(jié)點(diǎn)加入隊(duì)列
                    if( node.left != null ) {
                        queue.add( node.left );    
                    }           
                    if( node.right != null ) {
                        queue.add( node.right );    
                    }
                }

                return res;
            }
        }

        基于Morris算法的遍歷

        基本原理

        Morris算法為二叉樹的遍歷提供了新的思路,其通過充分利用樹中葉子節(jié)點(diǎn)存在大量空指針這一特點(diǎn)。實(shí)現(xiàn)了常數(shù)級(jí)別的空間復(fù)雜度。在進(jìn)一步介紹該算法之前,我們先來說明一個(gè)概念——mostRight節(jié)點(diǎn)。其用于表示當(dāng)前節(jié)點(diǎn)cur的左子樹的最右節(jié)點(diǎn)。例如下圖的一個(gè)二叉樹。如果當(dāng)前節(jié)點(diǎn)為1,則mostRight節(jié)點(diǎn)即為5;如果當(dāng)前節(jié)點(diǎn)為3,則mostRight節(jié)點(diǎn)即為6

        figure 1.jpeg

        實(shí)際上,Morris算法非常簡單。其基本遍歷流程如下:

        1. 將當(dāng)前節(jié)點(diǎn)cur初始化為root
        2. 當(dāng)前節(jié)點(diǎn)cur如果不存在左子樹。則將當(dāng)前節(jié)點(diǎn)指向其右子樹,以便遍歷當(dāng)前節(jié)點(diǎn)的右子樹
        3. 當(dāng)前節(jié)點(diǎn)cur如果存在左子樹,則先獲取cur節(jié)點(diǎn)的mostRight節(jié)點(diǎn)
          • 如果mostRight節(jié)點(diǎn)的右子節(jié)點(diǎn)為空,此時(shí)說明cur節(jié)點(diǎn)的左子樹還未遍歷。故:一方面,我們將mostRight節(jié)點(diǎn)的右子節(jié)點(diǎn)設(shè)置為cur節(jié)點(diǎn);另一方面,將當(dāng)前節(jié)點(diǎn)指向其左子樹,以便遍歷當(dāng)前節(jié)點(diǎn)的左子樹
          • 如果mostRight節(jié)點(diǎn)的右子節(jié)點(diǎn)為當(dāng)前節(jié)點(diǎn)cur,此時(shí)說明cur節(jié)點(diǎn)的左子樹已經(jīng)遍歷完成。故:一方面,我們將mostRight節(jié)點(diǎn)的右子節(jié)點(diǎn)設(shè)置為null空指針;另一方面,將當(dāng)前節(jié)點(diǎn)指向其右子樹,以便遍歷當(dāng)前節(jié)點(diǎn)的右子樹
        4. 重復(fù)Step 2、Step 3,直到當(dāng)前節(jié)點(diǎn)cur為null時(shí)為止

        該算法遍歷過程的示意圖如下,可以看到當(dāng)一個(gè)節(jié)點(diǎn)的左子樹未遍歷完時(shí),Morris算法會(huì)利用原葉子節(jié)點(diǎn)的null空指針修改樹。而當(dāng)該節(jié)點(diǎn)的左子樹遍歷后,會(huì)把對(duì)樹的修改進(jìn)行撤回。以恢復(fù)樹原有的結(jié)構(gòu)

        figure 2.jpeg

        前序遍歷

        這里基于Morris算法來實(shí)現(xiàn)前序遍歷。問題的關(guān)鍵就在于何時(shí)對(duì)當(dāng)前節(jié)點(diǎn)進(jìn)行處理。顯然這里有兩個(gè)時(shí)機(jī),一方面,遍歷過程中發(fā)現(xiàn)當(dāng)前節(jié)點(diǎn)的左子樹為空。則在對(duì)當(dāng)前節(jié)點(diǎn)的右子樹進(jìn)行遍歷前,需要對(duì)當(dāng)前節(jié)點(diǎn)進(jìn)行處理;另一方面,遍歷過程中發(fā)現(xiàn)當(dāng)前節(jié)點(diǎn)的左子樹不為空,則在對(duì)當(dāng)前節(jié)點(diǎn)的左子樹進(jìn)行遍歷前,需要對(duì)當(dāng)前節(jié)點(diǎn)進(jìn)行處理

        /**
         * 基于Morris算法的前序遍歷
         */

        class Solution {
            public List<Integer> preorderTraversal(TreeNode root) {
                List<Integer> res = new LinkedList<>();
                if( root == null ) {
                    return res;
                }

                TreeNode cur = root;
                // 當(dāng)前節(jié)點(diǎn)cur的左子樹的最右節(jié)點(diǎn)
                TreeNode mostRight = null;

                while ( cur != null ) {
                    // 當(dāng)前節(jié)點(diǎn)的左子樹為空
                    if( cur.left == null ) {
                        // 處理當(dāng)前節(jié)點(diǎn)
                        res.add( cur.val );
                        // 處理當(dāng)前節(jié)點(diǎn)的右子樹
                        cur = cur.right;
                    } else {    // 當(dāng)前節(jié)點(diǎn)的左子樹不為空
                        // 尋找當(dāng)前節(jié)點(diǎn)cur的左子樹的最右節(jié)點(diǎn)
                        mostRight = cur.left;
                        while ( mostRight.right!=null && mostRight.right!=cur ) {
                            mostRight = mostRight.right;
                        }

                        if( mostRight.right == null) {  // 說明cur節(jié)點(diǎn)的左子樹未遍歷
                            // 處理當(dāng)前節(jié)點(diǎn)
                            res.add( cur.val );
                            // 處理當(dāng)前節(jié)點(diǎn)的左子樹
                            mostRight.right = cur;
                            cur = cur.left;
                        } else if ( mostRight.right == cur ) {  // 說明cur節(jié)點(diǎn)的左子樹已經(jīng)遍歷完成
                            // 處理當(dāng)前節(jié)點(diǎn)的右子樹
                            mostRight.right = null;
                            cur = cur.right;
                        }
                    }
                }

                return res;
            }
        }

        中序遍歷

        這里基于Morris算法來實(shí)現(xiàn)中序遍歷。問題的關(guān)鍵就在于何時(shí)對(duì)當(dāng)前節(jié)點(diǎn)進(jìn)行處理。顯然這里有兩個(gè)時(shí)機(jī),一方面,遍歷過程中發(fā)現(xiàn)當(dāng)前節(jié)點(diǎn)的左子樹為空。則在對(duì)當(dāng)前節(jié)點(diǎn)的右子樹進(jìn)行遍歷前,需要對(duì)當(dāng)前節(jié)點(diǎn)進(jìn)行處理;另一方面,遍歷過程中發(fā)現(xiàn)當(dāng)前節(jié)點(diǎn)的左子樹不為空,則在對(duì)當(dāng)前節(jié)點(diǎn)的左子樹完成遍歷后,需要對(duì)當(dāng)前節(jié)點(diǎn)進(jìn)行處理

        /**
         * 基于Morris算法的中序遍歷
         */

        class Solution {
            public List<Integer> inorderTraversal(TreeNode root) {
                List<Integer> res = new LinkedList<>();
                if( root == null ) {
                    return res;
                }

                TreeNode cur = root;
                // 當(dāng)前節(jié)點(diǎn)cur的左子樹的最右節(jié)點(diǎn)
                TreeNode mostRight = null;

                while ( cur != null ) {
                    // 當(dāng)前節(jié)點(diǎn)的左子樹為空
                    if( cur.left == null ) {
                        // 處理當(dāng)前節(jié)點(diǎn)
                        res.add( cur.val );
                        // 處理當(dāng)前節(jié)點(diǎn)的右子樹
                        cur = cur.right;
                    } else {    // 當(dāng)前節(jié)點(diǎn)的左子樹不為空
                        // 尋找當(dāng)前節(jié)點(diǎn)cur的左子樹的最右節(jié)點(diǎn)
                        mostRight = cur.left;
                        while ( mostRight.right!=null && mostRight.right!=cur ) {
                            mostRight = mostRight.right;
                        }

                        if( mostRight.right == null) {  // 說明cur節(jié)點(diǎn)的左子樹未遍歷
                            // 處理當(dāng)前節(jié)點(diǎn)的左子樹
                            mostRight.right = cur;
                            cur = cur.left;
                        } else if ( mostRight.right == cur ) {  // 說明cur節(jié)點(diǎn)的左子樹已經(jīng)遍歷完成
                            // 處理當(dāng)前節(jié)點(diǎn)
                            res.add( cur.val );
                            // 處理當(dāng)前節(jié)點(diǎn)的右子樹
                            mostRight.right = null;
                            cur = cur.right;
                        }
                    }
                }

                return res;
            }
        }

        Note

        這里給出本文中關(guān)于樹節(jié)點(diǎn)的類定義

        /**
         * 樹的節(jié)點(diǎn)
         */

        class TreeNode {
            TreeNode left;
            
            TreeNode right;
            
            int val;
            
            TreeNode() {
            }
            
            TreeNode(int val) {
                this.val = val; 
            }
            
            TreeNode(int val, TreeNode left, TreeNode right) {
                this.val = val;
                this.left = left;
                this.right = right;
            }
        }


        瀏覽 54
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 青娱乐永久在线视频 | 午夜福利在线观看视频 | 国产极品人妖ts91热爆 | 国产插逼视频 | 操女人逼逼视频 |