二叉樹深度遞迴套娃理解
阿新 • • 發佈:2021-07-22
注:理解遞迴前,首先得有些jvm基礎,瞭解下棧和棧幀。下面我便圍繞棧來理解求二叉樹深度的遞迴套娃。
b棧視訊連結:https://www.bilibili.com/video/BV1iJ411E7xW?p=95&spm_id_from=pageDriver(平時充電)
首先上程式碼:
二叉樹定義:
package cn.itcast.algorithm.tree; import cn.itcast.algorithm.linear.Queue; public class BinaryTree<Key extends Comparable<Key>, Value> {//記錄根結點 private Node root; //記錄樹中元素的個數 private int N; private class Node { //儲存鍵 public Key key; //儲存值 private Value value; //記錄左子結點 public Node left; //記錄右子結點 public Node right; public Node(Key key, Value value, Node left, Node right) {this.key = key; this.value = value; this.left = left; this.right = right; } } //獲取樹中元素的個數 public int size() { return N; } //向樹中新增元素key-value public void put(Key key, Value value) { root = put(root, key, value); }//向指定的樹x中新增key-value,並返回新增元素後新的樹 private Node put(Node x, Key key, Value value) { //如果x子樹為空, if (x==null){ N++; return new Node(key,value, null,null); } //如果x子樹不為空 //比較x結點的鍵和key的大小: int cmp = key.compareTo(x.key); if (cmp>0){ //如果key大於x結點的鍵,則繼續找x結點的右子樹 x.right = put(x.right,key,value); }else if(cmp<0){ //如果key小於x結點的鍵,則繼續找x結點的左子樹 x.left = put(x.left,key,value); }else{ //如果key等於x結點的鍵,則替換x結點的值為value即可 x.value = value; } return x; } //查詢樹中指定key對應的value public Value get(Key key) { return get(root,key); } //從指定的樹x中,查詢key對應的值 public Value get(Node x, Key key) { //x樹為null if (x==null){ return null; } //x樹不為null //比較key和x結點的鍵的大小 int cmp = key.compareTo(x.key); if (cmp>0){ //如果key大於x結點的鍵,則繼續找x結點的右子樹 return get(x.right,key); }else if(cmp<0){ //如果key小於x結點的鍵,則繼續找x結點的左子樹 return get(x.left,key); }else{ //如果key等於x結點的鍵,就找到了鍵為key的結點,只需要返回x結點的值即可 return x.value; } } //刪除樹中key對應的value public void delete(Key key) { delete(root, key); } //刪除指定樹x中的key對應的value,並返回刪除後的新樹 public Node delete(Node x, Key key) { //x樹為null if (x==null){ return null; } //x樹不為null int cmp = key.compareTo(x.key); if (cmp>0){ //如果key大於x結點的鍵,則繼續找x結點的右子樹 x.right = delete(x.right,key); }else if(cmp<0){ //如果key小於x結點的鍵,則繼續找x結點的左子樹 x.left = delete(x.left,key); }else{ //如果key等於x結點的鍵,完成真正的刪除結點動作,要刪除的結點就是x; //讓元素個數-1 N--; //得找到右子樹中最小的結點 if (x.right==null){ return x.left; } if (x.left==null){ return x.right; } Node minNode = x.right; while(minNode.left!=null){ minNode = minNode.left; } //刪除右子樹中最小的結點 Node n = x.right; while(n.left!=null){ if (n.left.left==null){ n.left=null; }else{ //變換n結點即可 n = n.left; } } //讓x結點的左子樹成為minNode的左子樹 minNode.left = x.left; //讓x結點的右子樹成為minNode的右子樹 minNode.right = x.right; //讓x結點的父結點指向minNode x = minNode; } return x; } //查詢整個樹中最小的鍵 public Key min(){ return min(root).key; } //在指定樹x中找出最小鍵所在的結點 private Node min(Node x){ //需要判斷x還有沒有左子結點,如果有,則繼續向左找,如果沒有,則x就是最小鍵所在的結點 if (x.left!=null){ return min(x.left); }else{ return x; } } //在整個樹中找到最大的鍵 public Key max(){ return max(root).key; } //在指定的樹x中,找到最大的鍵所在的結點 public Node max(Node x){ //判斷x還有沒有右子結點,如果有,則繼續向右查詢,如果沒有,則x就是最大鍵所在的結點 if (x.right!=null){ return max(x.right); }else{ return x; } } //獲取整個樹中所有的鍵 public Queue<Key> preErgodic(){ Queue<Key> keys = new Queue<>(); preErgodic(root, keys); return keys; } //獲取指定樹x的所有鍵,並放到keys佇列中 private void preErgodic(Node x,Queue<Key> keys){ if (x==null){ return; } //把x結點的key放入到keys中 keys.enqueue(x.key); //遞迴遍歷x結點的左子樹 if (x.left!=null){ preErgodic(x.left,keys); } //遞迴遍歷x結點的右子樹 if (x.right!=null){ preErgodic(x.right,keys); } } //使用中序遍歷獲取樹中所有的鍵 public Queue<Key> midErgodic(){ Queue<Key> keys = new Queue<>(); midErgodic(root,keys); return keys; } //使用中序遍歷,獲取指定樹x中所有的鍵,並存放到key中 private void midErgodic(Node x,Queue<Key> keys){ if (x==null){ return; } //先遞迴,把左子樹中的鍵放到keys中 if (x.left!=null){ midErgodic(x.left,keys); } //把當前結點x的鍵放到keys中 keys.enqueue(x.key); //在遞迴,把右子樹中的鍵放到keys中 if(x.right!=null){ midErgodic(x.right,keys); } } //使用後序遍歷,把整個樹中所有的鍵返回 public Queue<Key> afterErgodic(){ Queue<Key> keys = new Queue<>(); afterErgodic(root,keys); return keys; } //使用後序遍歷,把指定樹x中所有的鍵放入到keys中 private void afterErgodic(Node x,Queue<Key> keys){ if (x==null){ return ; } //通過遞迴把左子樹中所有的鍵放入到keys中 if (x.left!=null){ afterErgodic(x.left,keys); } //通過遞迴把右子樹中所有的鍵放入到keys中 if (x.right!=null){ afterErgodic(x.right,keys); } //把x結點的鍵放入到keys中 keys.enqueue(x.key); } //使用層序遍歷,獲取整個樹中所有的鍵 public Queue<Key> layerErgodic(){ //定義兩個佇列,分別儲存樹中的鍵和樹中的結點 Queue<Key> keys = new Queue<>(); Queue<Node> nodes = new Queue<>(); //預設,往佇列中放入根結點 nodes.enqueue(root); while(!nodes.isEmpty()){ //從佇列中彈出一個結點,把key放入到keys中 Node n = nodes.dequeue(); keys.enqueue(n.key); //判斷當前結點還有沒有左子結點,如果有,則放入到nodes中 if (n.left!=null){ nodes.enqueue(n.left); } //判斷當前結點還有沒有右子結點,如果有,則放入到nodes中 if (n.right!=null){ nodes.enqueue(n.right); } } return keys; } //獲取整個樹的最大深度 public int maxDepth(){ return maxDepth(root); } //獲取指定樹x的最大深度 private int maxDepth(Node x){ if (x==null){ return 0; } //x的最大深度 int max=0; //左子樹的最大深度 int maxL=0; //右子樹的最大深度 int maxR=0; //計算x結點左子樹的最大深度 if (x.left!=null){ maxL = maxDepth(x.left); } //計算x結點右子樹的最大深度 if (x.right!=null){ maxR = maxDepth(x.right); } //比較左子樹最大深度和右子樹最大深度,取較大值+1即可 max = maxL>maxR?maxL+1:maxR+1; return max; } }
test:
package cn.itcast.algorithm.test; import cn.itcast.algorithm.linear.Queue; import cn.itcast.algorithm.tree.BinaryTree; public class BinaryTreeMaxDepthTest { public static void main(String[] args) { //建立樹物件 BinaryTree<String, String> tree = new BinaryTree<>(); //往樹中新增資料 tree.put("E", "5"); tree.put("B", "2"); tree.put("A", "1"); // tree.put("A", "1"); // tree.put("D", "4"); // tree.put("F", "6"); // tree.put("H", "8"); // tree.put("C", "3"); int maxDepth = tree.maxDepth(); System.out.println(maxDepth); } }
為了便於理解,我只插入了三個節點,而且還都是左子樹:
打斷點除錯,一直遞迴到最後:
發現這個棧裡面建立了五個棧幀,第一個棧幀是main函式,第二是 maxDepth()方法,第三、第四、第五個是遞迴呼叫 maxDepth(Node x)方法產生的棧幀。
首先我們來看下第三個棧幀裡面建立的基本變數:
第四個棧幀裡面建立的變數:
第五個棧幀中建立的變數:
其實就是每次遞迴,都會產生棧幀,棧幀中建立方法中的變數。 結束最後一層遞迴,也就是第五次棧幀釋放後。第四層棧幀中變數的值變化如下:
結束這層遞迴,也就是釋放第四個棧幀,結果如下:
再結束最後一層遞迴,釋放棧幀:(第二個棧幀只是方法的封裝,無展示價值)
1111
個人學習筆記,記錄日常學習,便於查閱及加深,僅為方便個人使用。