二叉樹路徑查詢

廣大青年發表於2020-09-12

最近筆試做了這麼一道題,想和大家分享一下我的做法

目錄:1.題目

   2.題目分析

   3.功能與模組實現

   4.完整程式碼

   5.總結

 

一、題目

二叉樹路徑查詢

 

給定一棵二叉樹(結構如下),其中每個節點值為整數。給定一個值K,求所有滿足如下條件的路徑並將路徑上節點的值列印出來:

1、路徑方向必須向下,即只能從父節點指向子節點

2、路徑並不是必須從根節點開始或在葉節點結束。即樹上任意節點開始向下走到任意節點的路徑都允許。

3、路徑上的節點得分之和等於給定值K。節點得分=節點值+節點所在層(根節點為0,之後每層+1)。

 

l  示例:給定二叉樹[5,3,7,9,null,11,2,4,-1, null,null,2,-2],K=22

輸出:

5 3 9 -1

5 7 2 2

3 9 4

解釋:如第一個路徑5 3 9 -1,路徑上節點得分分別為5+0,3+1,9+2,-1+3,和為22

 

l  輸入格式: 第一行為一個整數K,第二行為一個二叉樹的層次遍歷序列,其中空子樹用 null 表示,每兩個數字或者null之間用空格分隔,例如:

22

5 3 7 9 null 11 2 4 -1 null null 2 -2

需要注意的是,null節點的子節點不會顯式的寫出來,如上例中第二行值為3的節點的右子樹為空,則該右空子樹的左右子樹不會再以null表示。

 

l  輸出格式: 分為多行,每行為一個滿足條件的路徑上節點的值的序列,例如:

5 3 9 -1

5 7 2 2

3 9 4

 

 

現有如下輸入:

35

5 4 8 11 null 13 4 7 2 null null 5 1 8 null 7 10 6 null null null

請用程式將正確結果輸出

 

二、題目分析

 1.題目表面上想要查詢出所有符合條件的路徑,其實更深層次考察的是,如何構造二叉樹

  2.如題目所述,二叉樹的層次遍歷序列是直接從中控臺輸入的,僅僅依靠此序列來層次構造二叉樹。這與我們以往的說法不同,通常需要兩條序列(比如前序和中序)來構造二叉樹,或者構造完全二叉樹的時候,可以直接使用前序遍歷序列。

  3.題目還有一個要求,null節點的子節點不會顯式的寫出來,如上例中第二行值為3的節點的右子樹為空,則該右空子樹的左右子樹不會再以null表示。

  4.層次構造二叉樹過程:

  

 

  

 

 

 

   

 

 

 

    

  第五步和第六步中間紅色欄位很重要,這是子節點找父節點的思路

  5.上面的分析是從上到下,按層次分析,通過父節點找其子節點,很好理解。但是程式碼實現無法做到這一點,我們只能由子節點找其父節點,怎麼說呢?

  原因是,父節點找其子節點,我們通常會想到用遞迴,非常簡單,直接套用公式(n-NULLSUM)*2-x,這裡n表示父節點下標,x表示左右(左為一,右為二),一直找下去。但是仔細會發現,這裡的NULLSUM值可能不正確,

 

     

 

 

 

 

   在遞迴構造二叉樹的過程中,無法發做到層次構造,它更像前序遍歷構造形式。比如上圖,從根節點的右節點7開始,讀到11,此時11節點的下標是5,此時(5-NULLSUM)*2+1=11得到左子節點2的下標,這裡NULLSUM為什麼是0呢?11節點的前面有null節點啊,NULLSUM不應該是1嗎?我們在看一下NULLSUM的定義:統計當前節點前面出現null值的節點。嗯我們理解的定義沒有問題,問題出現在遞迴呼叫上,它不層次演算法,而是一頭扎到底再回頭的那種,這就導致讀到11這個節點的時候,跳過了下標為4的null節點,故NULLSUM值還是為0。

  6.基於以上問題,如何做到層次構造二叉樹呢?

  這裡我想到了一個辦法,那就是讓子節點找父節點,這樣做的好處是,一對一思想,找到父節點,就可以跳到下一個節點繼續尋找其父節點。那麼還有個問題,怎麼知道當前節點是父節點的左節點還是右節點?其實很簡單,可以在第四點的層次構造二叉樹過程圖中可以發現,每個節點的左節點的下標一定是奇數,右節點的下標一定是偶數,那麼可以根據當前節點的下標奇偶性判斷其是左節點還是右節點。

  子節點找父節點,可以通過(n-NULLSUM)*2-x這個公式的逆運算算出父節點的下標n,在利用樹的遍歷查詢,即可找到父節點

  7.不知道大家發現第六點又產生了個問題,那就是利用樹的遍歷查詢,因為從中控臺輸入一連串序列,這個序列中的數可以不唯一,可重複,導致構造的樹每個節點的值不唯一,那麼樹的遍歷就不好使了。如何解決這個問題呢?其實很簡單,既然節點的值不唯一,那我們可以在樹的資料結構裡,給節點增加一個下標變數用來標識該節點,比如:

 

 1 public class TreeNode {
 2     public int val;
 3     /*
 4      * 由於題目給的二叉樹中節點值不唯一,
 5      * 增加treeIndex做唯一標識
 6      */
 7     public int treeIndex;//對應陣列下標
 8     public TreeNode left;
 9     public TreeNode right;
10     //由於陣列為String型別,需要轉型為整型,方便後面運算
11     public TreeNode(String x) {
12         val = Integer.parseInt(x);
13     }
14     
15     public TreeNode() {
16         
17     }
18 
19     @Override
20     public String toString() {
21         return "TreeNode [val=" + val + "]";
22     }
23     
24     
25 }

 

 

 

 

 三、功能和模組實現

  1、建立二叉樹

  1.1、尋找父節點

  

 1 public TreeNode searchNode(TreeNode root,int index) {//廣度優先搜尋,查詢父節點
 2         if(root==null||index<0)return null;
 3         LinkedList<TreeNode> list = new LinkedList<>();//連結串列,這裡我將其作為佇列
 4         list.add(root);//把資料加入到佇列尾部
 5         while(!list.isEmpty()) {
 6             TreeNode node = list.poll();
 7             if(node.treeIndex==index) 
 8                 return node;
 9             if(node.left!=null)
10                 list.add(node.left);
11             if(node.right!=null)
12                 list.add(node.right);
13         }
14         
15         return null;
16     }

 

 

  1.2、處理傳入的序列

  

 1 public TreeNode create(String[] levelOrder) {//考慮到給的陣列有null值,故用String型別
 2         if(levelOrder.length==0)
 3             return null;
 4         TreeNode root = new TreeNode(levelOrder[0]);//根節點
 5         LinkedList<Integer> list = new LinkedList<>();//連結串列,這裡我將其作為佇列
 6         for(int i=1;i<levelOrder.length;i++) {
 7             if(levelOrder[i]==null||"null".equals(levelOrder[i])) {
 8                 list.add(i);
 9                 continue;
10             }
11             TreeNode node = new TreeNode(levelOrder[i]);
12             node.treeIndex = i;
13             
14             LinkedList<Integer> newList = new LinkedList();
15             for (Iterator iterator = list.iterator(); iterator.hasNext();) {
16                 newList.add((Integer) iterator.next());
17                 
18             }
19             buildTree(root,node,i,newList,levelOrder);
20             
21         }
22         return root;
23     }

  1.3、建立二叉樹

  

 1 //建立樹
 2     public TreeNode buildTree(TreeNode root,TreeNode node,int i,LinkedList<Integer> list,String[] levelOrder) {
 3         int NULLSUM = compareIndex(list,levelOrder,i);
 4         /*
 5          * 如題目給的示例:給定二叉樹[5,3,7,9,null,11,2,4,-1, null,null,2 ,-2]
 6          *                 index: 0,1,2,3,  4 ,5 ,6,7, 8,   9 , 10 ,11,12
 7          * 
 8          *                 5
 9          *                / \
10          *               3    7
11          *              /    / \
12          *          9    11  2
13          *         / \      / \
14          *        4   -1   2  -2
15          * 思路:1.定義NULLSUM變數記錄null節點個數
16          *        2.通過compareIndex函式計算該節點的父節點層及以上出現null節點個數
17          *        3.(i-2)/2+NULLSUM可以計算出該節點的父節點
18          */
19         if(i%2==0) {
20             TreeNode parent = searchNode(root,(i-2)/2+NULLSUM);
21             while(parent==null) {
22                 NULLSUM++;
23                 parent = searchNode(root,(i-2)/2+NULLSUM);
24             }
25             parent.right = node;
26         }else {
27             TreeNode parent = searchNode(root,(i-1)/2+NULLSUM);
28             while(parent==null) {
29                 NULLSUM++;
30                 parent = searchNode(root,(i-1)/2+NULLSUM);
31             }
32             parent.left = node;
33         }
34         
35         return root;
36     }

  為什麼在第21和28行設立while迴圈判斷找到的父節點是否為null?還記得下面這張圖嗎,父節點為null時,就會跳到下一個不為null的節點來代替指向原本父節點的子節點。逆過來,子節點找父節點,如果父節點時null,那可以跳到下一個直至不為null的父節點

  2、二叉樹路徑查詢

  2.1、路徑查詢入口

  

1 /*
2      * 傳入的引數分別為根節點root、定值K和當前節點所在層數dept(這個非常好用,因為傳入的根節點有可能只是樹中某節點)
3      */
4     public List<List<Integer>> pathSumEntry(TreeNode root,int K,int dept){
5         List<List<Integer>> result = new LinkedList<List<Integer>>();//用於儲存所有匹配路徑
6         List<Integer> currentResult = new LinkedList<Integer>();//用於儲存找到的當前匹配的路徑
7         pathSum(root,K,currentResult,result,dept);
8         return result;
9     }

  2.2、路徑查詢主函式

 1 /*
 2      * 這裡主要使用遞迴加回溯的思想
 3      */
 4     public void pathSum(TreeNode root,int K,List<Integer>currentResult,List<List<Integer>>result,int dept) {
 5         if(root==null)return;
 6         currentResult.add(new Integer(root.val));
 7         if(root.left==null&&root.right==null&&K==root.val+dept) {
 8             result.add(new LinkedList(currentResult));
 9         }else {
10             pathSum(root.left,K-root.val-dept,currentResult,result,dept+1);
11             pathSum(root.right,K-root.val-dept,currentResult,result,dept+1);
12         }
13         currentResult.remove(currentResult.size()-1);
14     }

 

四、完整程式碼

  1.樹的資料結構

二叉樹路徑查詢
 1 public class TreeNode {
 2     public int val;
 3     /*
 4      * 由於題目給的二叉樹中節點值不唯一,
 5      * 增加treeIndex做唯一標識
 6      */
 7     public int treeIndex;//對應陣列下標
 8     public TreeNode left;
 9     public TreeNode right;
10     //由於陣列為String型別,需要轉型為整型,方便後面運算
11     public TreeNode(String x) {
12         val = Integer.parseInt(x);
13     }
14     
15     public TreeNode() {
16         
17     }
18 
19     @Override
20     public String toString() {
21         return "TreeNode [val=" + val + "]";
22     }
23     
24     
25 }
View Code

  2.構造二叉樹類

二叉樹路徑查詢
  1 public class BuildTree {
  2     
  3     public TreeNode searchNode(TreeNode root,int index) {//廣度優先搜尋,查詢父節點
  4         if(root==null||index<0)return null;
  5         LinkedList<TreeNode> list = new LinkedList<>();//連結串列,這裡我將其作為佇列
  6         list.add(root);//把資料加入到佇列尾部
  7         while(!list.isEmpty()) {
  8             TreeNode node = list.poll();
  9             if(node.treeIndex==index) 
 10                 return node;
 11             if(node.left!=null)
 12                 list.add(node.left);
 13             if(node.right!=null)
 14                 list.add(node.right);
 15         }
 16         
 17         return null;
 18     }
 19     
 20     
 21     public TreeNode create(String[] levelOrder) {//考慮到給的陣列有null值,故用String型別
 22         if(levelOrder.length==0)
 23             return null;
 24         TreeNode root = new TreeNode(levelOrder[0]);//根節點
 25         LinkedList<Integer> list = new LinkedList<>();//連結串列,這裡我將其作為佇列
 26         for(int i=1;i<levelOrder.length;i++) {
 27             if(levelOrder[i]==null||"null".equals(levelOrder[i])) {
 28                 list.add(i);
 29                 continue;
 30             }
 31             TreeNode node = new TreeNode(levelOrder[i]);
 32             node.treeIndex = i;
 33             
 34             LinkedList<Integer> newList = new LinkedList();
 35             for (Iterator iterator = list.iterator(); iterator.hasNext();) {
 36                 newList.add((Integer) iterator.next());
 37                 
 38             }
 39             buildTree(root,node,i,newList,levelOrder);
 40             
 41         }
 42         return root;
 43     }
 44 
 45 
 46     //建立樹
 47     public TreeNode buildTree(TreeNode root,TreeNode node,int i,LinkedList<Integer> list,String[] levelOrder) {
 48         int NULLSUM = compareIndex(list,levelOrder,i);
 49         /*
 50          * 如題目給的示例:給定二叉樹[5,3,7,9,null,11,2,4,-1, null,null,2 ,-2]
 51          *                 index: 0,1,2,3,  4 ,5 ,6,7, 8,   9 , 10 ,11,12
 52          * 
 53          *                 5
 54          *                / \
 55          *               3    7
 56          *              /    / \
 57          *          9    11  2
 58          *         / \      / \
 59          *        4   -1   2  -2
 60          * 思路:1.定義NULLSUM變數記錄null節點個數
 61          *        2.通過compareIndex函式計算該節點的父節點層及以上出現null節點個數
 62          *        3.(i-2)/2+NULLSUM可以計算出該節點的父節點
 63          */
 64         if(i%2==0) {
 65             TreeNode parent = searchNode(root,(i-2)/2+NULLSUM);
 66             while(parent==null) {
 67                 NULLSUM++;
 68                 parent = searchNode(root,(i-2)/2+NULLSUM);
 69             }
 70             parent.right = node;
 71         }else {
 72             TreeNode parent = searchNode(root,(i-1)/2+NULLSUM);
 73             while(parent==null) {
 74                 NULLSUM++;
 75                 parent = searchNode(root,(i-1)/2+NULLSUM);
 76             }
 77             parent.left = node;
 78         }
 79         
 80         return root;
 81     }
 82     
 83     /*
 84      * 比較下標所指向的值,判斷當前節點的父節點下標所在陣列位置的值是否等於null
 85      * list為儲存空值的下標佇列,從頭到尾取值,並計算比較當前節點下標比棧值的子節點大
 86      * 若大於,這NULLSUM++,否則停止,返回NULLSUM值
 87      */
 88     public int compareIndex(LinkedList<Integer> list,String[] order,int i) {
 89         int sum,NULLSUM = 0;//記錄陣列中null的數量
 90         if(list==null&&list.size()==0) {
 91             return 0;
 92         }
 93         while(!list.isEmpty()&&i>list.poll()*2) {
 94             NULLSUM++;
 95         }
 96         return NULLSUM;
 97     }
 98     
 99     
100 }
View Code

  3.路徑查詢類

二叉樹路徑查詢
 1 public class PathSum {
 2 
 3     /*
 4      * 傳入的引數分別為根節點root、定值K和當前節點所在層數dept(這個非常好用,因為傳入的根節點有可能只是樹中某節點)
 5      */
 6     public List<List<Integer>> pathSumEntry(TreeNode root,int K,int dept){
 7         List<List<Integer>> result = new LinkedList<List<Integer>>();//用於儲存所有匹配路徑
 8         List<Integer> currentResult = new LinkedList<Integer>();//用於儲存找到的當前匹配的路徑
 9         pathSum(root,K,currentResult,result,dept);
10         return result;
11     }
12     /*
13      * 這裡主要使用遞迴加回溯的思想
14      */
15     public void pathSum(TreeNode root,int K,List<Integer>currentResult,List<List<Integer>>result,int dept) {
16         if(root==null)return;
17         currentResult.add(new Integer(root.val));
18         if(root.left==null&&root.right==null&&K==root.val+dept) {
19             result.add(new LinkedList(currentResult));
20         }else {
21             pathSum(root.left,K-root.val-dept,currentResult,result,dept+1);
22             pathSum(root.right,K-root.val-dept,currentResult,result,dept+1);
23         }
24         currentResult.remove(currentResult.size()-1);
25     }
26 }
View Code

  4.主函式

二叉樹路徑查詢
 1 public class Main {
 2     
 3     public static void main(String[] args) {
 4         Scanner input = new Scanner(System.in);
 5         System.out.print("輸入K:");
 6         int K = input.nextInt();
 7         System.out.println("輸入陣列(以'#'結束,例如:5 3 7 9 null 11 2 4 -1 null null 2 -2 #)");
 8         List<String> list = new ArrayList<String>();//集合接收輸入串
 9         String cin = null;
10         while(!"#".equals((cin=input.next()))) {
11             list.add(cin);
12             
13         }
14         //將集合轉成字串陣列
15         String[] levelOrder = new String[list.size()];
16         for(int i=0;i<=list.size()-1;i++) {
17             levelOrder[i] = list.get(i);
18         }
19         
20         //從左到右構造二叉樹,並尋找路徑和等於K的路徑
21         BuildTree tree = new BuildTree();
22         TreeNode root = tree.create(levelOrder);
23         printTree(root,K,0);
24     }
25     
26     public static void printTree(TreeNode root,int K,int dept) {
27         if(root==null)return;
28         PathSum pathSum = new PathSum();
29 
30         List<List<Integer>> result = pathSum.pathSumEntry(root, K,dept);
31         for (List resultList : result) {
32             System.out.println(resultList);
33         }
34         
35         printTree(root.left,K,dept+1);
36         printTree(root.right,K,dept+1);
37     }
38 }
View Code

 

 

五、總結

  一開始我還以為這道題就是個簡單的路徑查詢算題,採用遞迴加回溯演算法分分鐘鍾就可以解決這道題,當我再仔細看題的時候,才知道這不是一道簡簡單單的回溯演算法題。還有二叉樹如何構建問題,一看到構建二叉樹題,還是覺得簡單,結果自己還是太嫩了!沒錯,這是筆者第一次做分層構造二叉樹題,筆者的思考可以參考題目分析部分,這裡有筆者的思考的部分過程。第一次做分層構造二叉樹,所以花費了不少時間,考慮不周還望各位老師同學指點,如果大家有更好的分層構造二叉樹想法,可以分享連結到評論區,比如可以直接父節點找子節點。

  每日一囧,微笑面對生活,我是懂先森

 

相關文章