1. 程式人生 > >二叉樹的序列化和反序列化

二叉樹的序列化和反序列化

nbsp node 使用遞歸 輸入 特殊字符 this tail 改變 代碼實現

http://blog.csdn.net/qq_27703417/article/details/70958692

先序遍歷二叉樹,如果遇到空節點,就在str的末尾加上“#!”,“#”表示這個節點為空,節點值不存在,當然你也可以用其他的特殊字符,“!”表示一個值的結束。如果遇到不為空的節點,假設節點值為3,就在str的末尾加上“3!”。現在請你實現樹的先序序列化。

先序遍歷

  1. import java.util.*;
  2. //使用遞歸先序遍歷對二叉樹進行序列化
  3. public class TreeToString {
  4. public String toString(TreeNode root) {
  5. //註意:Java中String是不可改變的,不能進行引用傳遞,改為使用StringBuffer
  6. StringBuilder res=new StringBuilder("");
  7. //調用遞歸方法完成二叉樹遍歷序列化
  8. this.preOrder(root,res);
  9. //返回結果
  10. return res.toString();
  11. }
  12. //遞歸方法,用來先序遍歷二叉樹同時將其序列化為字符串
  13. private void preOrder(TreeNode root, StringBuilder str){
  14. //遞歸結束的邊界條件
  15. if(root==null){
  16. str.append("#!");
  17. return;
  18. }
  19. //先遍歷根結點
  20. str.append(root.val+"!");
  21. //遍歷左子樹
  22. this.preOrder(root.left,str);
  23. //遍歷右子樹
  24. this.preOrder(root.right,str);
  25. }
  26. }

反序列化

所謂反序列化是根據一個字符串重新建立一棵二叉樹,反序列化是序列化的逆過程,對於一個字符串,首先按照分隔符!將其分割為字符串數組,每個字符串元素代表一個結點,然後開始重建二叉樹。由於每個結點再字符串中只保留了一個val值,因此需要根據結點的值val重新構建TreeNode結點對象,並且為這個結點對象的left和right進行賦值。

反序列化比序列化要難,其實代碼實現是類似的,只也是使用遞歸,只是這時候是反向的遞歸,比較抽象,要逐漸理解。已知一個用!分割的字符串是某個二叉樹按照先序遍歷順序序列化得到的字符串,將其反序列化建立一棵二叉樹,註意,要進行反序列化必須要知道這個字符串是按照什麽順序序列化得到的,只有按照相同的遍歷順序對其進行反序列化才能恢復正確的二叉樹。一般使用先序遍歷順序進行序列化和反序列化。在反序列化時,首先得到一個字符串數組strs[]表示字符串序列拆分得到的字符串數組,數組的每個元素字符串對應一個結點的值,可以是3!或者是#!,分別表示一個非空的結點或者是空結點。即要求實現的功能是:根據給定的字符串數組strs[],重建一棵二叉樹並返回這棵二叉樹的頭結點root。

分析:對於字符串數組strs[],第1個元素是根結點,第2個元素是左結點,第3個元素可能是第2個結點的左結點或者是第1個結點的右結點,要根據第2個結點是否為null來確定,即對於strs[],裏面的元素必然是按照:根結點à左結點à左結點à左結點(null)à右結點à左結點à左結點(null)à右結點的順序來排列的,因此總是先遞歸地恢復建立左結點,當遇到null時,說明這條路徑結束了,node結點的left為null,應該返回到node結點並開始恢復一個右結點,此時相當於一個新的重復的過程,可以把這個右結點當做root開始新的遞歸過程。

即要求實現一個遞歸方法private TreeNode deSerialize(String[] strs);對於一個(或者部分)字符串數組,恢復一棵二叉樹,並返回這棵二叉樹的根結點。

逐個遍歷數組strs[],當遇到“#”說明這是一個空結點,在這個根結點的後面不可能建立二叉樹,於是相當於建立子樹工作完成,返回根結點即返回null即可;如果遇到的是非空的字符串,例如“3”,表明這是一個非空的結點,首先建立這個結點TreeNode newNode=new TreeNode(3);但是此時僅僅恢復了一個結點,還要恢復它的子樹,並且是先恢復左子樹,再恢復右子樹。如何恢復左子樹?顯然要根據數組strs[]的下一個元素開始的數組部分來恢復一棵二叉樹,這就是這個遞歸函數的功能(根據一個或者部分字符串數組來建立一棵二叉樹),於是調用自身這個遞歸函數即可,只是此時使用的字符串向後面移動了1個元素而已。

調用完這個函數後,就要認為結點③的左子樹已經恢復完畢了,於是開始恢復結點③的右子樹newNode.right;恢復右子樹的過程還是一樣的,也是相同的邏輯(根據一個或者部分字符串數組來建立一棵二叉樹),只是此時恢復的二叉樹連接到的不是newNode.left上面而是newNode.right上面,當調用完這個函數後,就認為結點newNode的右子樹已經恢復完成了,於是整個newNode的val有了,left、right都有了值,於是整棵二叉樹就建立了,此時根據函數功能的要求,要返回這棵建立起來的二叉樹的根結點,於是返回newNode即可。

常識:沒有構造方法時默認有參數為空的構造函數可以不寫;當寫有含參的構造方法時,如果不寫參數為空的構造方法,就不能再使用TreeNode newNode=new TreeNode()這種構造方法,如果要用就必須顯示地定義參數為空的構造方法。

  1. public class Solution {
  2. //已知由先序遍歷得到的字符串str,將其恢復為一棵二叉樹,並返回根結點
  3. TreeNode Deserialize(String str) {
  4. //特殊輸入
  5. if(str==null||str.length()<=0) return null;
  6. //將字符串按照","拆分為數組
  7. String[] strs=str.split(",");
  8. //調用遞歸方法deSerializeCore()方法來實現重建二叉樹的功能,返回根結點
  9. TreeNode root=this.deSerializeCore(strs);
  10. //註意返回結果
  11. return root;
  12. }
  13. //註意:這裏關鍵是要設計一個成員變量index用於在每次遞歸調用時能夠使用不同的字符串來建立根結點
  14. int index=0;
  15. //設計一個遞歸方法deSerializeCore用於使用strs[]數組的後面部分元素來建立一棵二叉樹,並返回根結點
  16. //遞歸方法可以有返回值或者沒有返回值,不影響使用,如果有返回值要註意接收
  17. private TreeNode deSerializeCore(String[] strs){
  18. if("#".equals(strs[index])){
  19. //如果遇到的是#表示空節點,不再建立子樹,這個結點null就是子樹的根結點返回
  20. //千萬註意,返回前要將index向下移動,之後使用的是strs[]中後面部分的元素
  21. index++;
  22. return null;
  23. }else{
  24. //如果不為空結點,則先恢復這個結點
  25. TreeNode newNode=new TreeNode(0);
  26. newNode.val=Integer.parseInt(strs[index]);
  27. //千萬註意在遞歸調用之前(使用了一個元素建立結點之後),要將index向後移動1位
  28. index++;
  29. //恢復左子樹
  30. newNode.left=this.deSerializeCore(strs);
  31. //恢復右子樹
  32. newNode.right=this.deSerializeCore(strs);
  33. //建立二叉樹完成,返回根結點
  34. return newNode;
  35. }
  36. }
  37. }

二叉樹的序列化和反序列化