题目:请实现两个函数,分别用来序列化和反序列化二叉树。
• 我们知道可以从前序遍历序列和中序遍历序列中构造出一棵二叉树。
受此启发,我们可以先把一棵二叉树序列化成一个前序遍历序列和一个中序遍历序列,
然后在反序列化时通过这两个序列重构出原二叉树 。
• 这种思路有两个缺点:
• 一是该方法要求二叉树中不能有数值重复的节点。
• 二是只有当两个序列中所有数据都读出后才能开始反序列化。
• 如果两个遍历序列的数据是从一个流里读出来的,那么可能需要等待较长的时间。
实际上,如果二叉树的序列化是从根节点开始的,
那么相应的反序列化也应该在根节点的
数值读出来的时候就可以开始了。
因此,我们可以根据前序遍历的顺序来序列化二叉树,
因为前序遍历是从根节点开始的。
• 在遍历二叉树碰到null指针时,这些null指针序列化为一
个特殊的宇符(如’′)。另外,节点的数值之间要用一个特殊字符(如′,′隔开)。•根据这样的序列化规则,下图中的二叉树被序列化成字符串“1,2,4,,,,3,5,,,6,,”。
• 右图:一棵被序列化成字符串“1,2,4,,,$,3,5,
,,6,,"的二叉树。
• 我们接着以字符串该字符串为例分析如何
• 反序列化二叉树。第一个读出的数字是l。
• 由于前序遍历是从根节点开始的,这是根节
• 点的值。接下来读出的数字是2,根据前序
• 遍历的规则,这是根节点的左子节点的值。
• 同样,接下来的数字4是值为2的节点的左子节点。接着从
序列化字符串里读出两个字符’′,这表明值为4的节点的左、右子节点均为null指针,因此它是一个叶子节点。•接下来回到值为2的节点,重建它的右子节点。由于下一个字符是′’,这表明值为2的节点的右子节点为null指针。
这个节点的左、右子树都已经构建完毕,接下来回到根节
点,反序列化根节点的右子树。
• 下一个序列化字符串中的数字是3,因此右子树的根节点
的值为3。它的左子节点是一个值为5的叶节点,因为接下
来的三个字符是“5,,”。同样,它的右子节点是值
为6的叶节点,因为最后3个字符是 "6,,”。
• 如果总结前面序列化和反序列化的过程,就会发现我们都
是把二叉树分解成3部分:根节点、左子树和右子树。
• 我们在处理(序列化或反序列化)它的根节点之后再分别处
理它的左、右子树。这是典型的把问题边归分解然后逐个
解快的过程。
package helen.a;
import sun.reflect.generics.tree.Tree;
public class SerializeBiraryTree {
private int index=0;
public static class TreeNode{
int val=0;
TreeNode right=null;
TreeNode left=null;
TreeNode(){}
TreeNode(int val){
this.val=val;
}
}
public String serialize(TreeNode head){
StringBuilder sb=new StringBuilder();
if(head==null){
sb.append("$,");
}else {
sb.append(head.val+",");
sb.append(serialize(head.left));
sb.append(serialize(head.right));
}
return sb.toString();
}
// 8
// 6 10
// 5 7 9 11
public TreeNode deSerialize(String str){
TreeNode node=null;
if(str==null||str.length()==0){
return null;
}
int start=index;
while(str.charAt(index)!=','){
index++;
}
// 8
// 6 10
// 5 7 9 11
if(!str.substring(start,index).equals("$")){
node=new TreeNode(Integer.parseInt(str.substring(start,index)));
index++;
node.left=deSerialize(str);
node.right=deSerialize(str);
}else {
index++;
}
return node;
} private void printTree(TreeNode root){
printTreeNode(root);
if(root!=null){
if(root.left!=null){
printTree(root.left);
}
if(root.right!=null){
printTree(root.right);
}
}
}
private void printTreeNode(TreeNode root) {
if(root!=null){
System.out.printf("节点的值是:%d\n",root.val);
if(root.left!=null){
System.out.printf("节点的左子节点的值是:%d\n",root.left.val);
}else{
System.out.printf("节点的左子节点的值是空");
}
if(root.right!=null){
System.out.printf("节点的右子节点的值是:%d\n",root.right.val);
}else{
System.out.printf("节点的右子节点的值是空");
}
}else{
System.out.println("根节点为空!");
}
}
//判断是否是相同的树
private boolean isSameTree(TreeNode root1,TreeNode root2){
if(root1==null && root2==null){
return true;
}
if(root1==null || root2==null){
return false;
}
if(root1.val!=root2.val){
return false;
}
return isSameTree(root1.left,root2.left)&&isSameTree(root1.left, root1.left);
}
private TreeNode createBinaryTreeNode(int value){
TreeNode pNode=new TreeNode();
pNode.val=value;
pNode.left=null;
pNode.right=null;
return pNode;
}
private void connectTreeNodes(TreeNode pParent,TreeNode pLeft,TreeNode pRight){
if(pParent!=null){
pParent.left=pLeft;
pParent.right=pRight;
}
}
void test(String testName, TreeNode pRoot){
if(testName != null)
System.out.printf("%s begins: \n", testName);
//printTree(pRoot);
String s=serialize(pRoot);
System.out.println(s);
TreeNode pNewRoot = null;
pNewRoot=deSerialize(s);
//printTree(pNewRoot);
if(isSameTree(pRoot, pNewRoot))
System.out.printf("反序列化的树和原树是同一棵树\n\n");
else
System.out.printf("反序列化的树和原树不是同一棵树\n\n");
}
// 8
// 6 10
// 5 7 9 11
void test1(){
TreeNode pNode8 = createBinaryTreeNode(8);
TreeNode pNode6 = createBinaryTreeNode(6);
TreeNode pNode10 = createBinaryTreeNode(10);
TreeNode pNode5 = createBinaryTreeNode(5);
TreeNode pNode7 = createBinaryTreeNode(7);
TreeNode pNode9 = createBinaryTreeNode(9);
TreeNode pNode11 = createBinaryTreeNode(11);
connectTreeNodes(pNode8, pNode6, pNode10);
connectTreeNodes(pNode6, pNode5, pNode7);
connectTreeNodes(pNode10, pNode9, pNode11);
test("Test1", pNode8);
}
// 5
// 4
// 3
// 2
void test2(){
TreeNode pNode5 = createBinaryTreeNode(5);
TreeNode pNode4 = createBinaryTreeNode(4);
TreeNode pNode3 = createBinaryTreeNode(3);
TreeNode pNode2 = createBinaryTreeNode(2);
connectTreeNodes(pNode5, pNode4, null);
connectTreeNodes(pNode4, pNode3, null);
connectTreeNodes(pNode3, pNode2, null);
test("Test2", pNode5);
}
// 5
// 4
// 3
// 2
void test3(){
TreeNode pNode5 = createBinaryTreeNode(5);
TreeNode pNode4 = createBinaryTreeNode(4);
TreeNode pNode3 = createBinaryTreeNode(3);
TreeNode pNode2 = createBinaryTreeNode(2);
connectTreeNodes(pNode5, null, pNode4);
connectTreeNodes(pNode4, null, pNode3);
connectTreeNodes(pNode3, null, pNode2);
test("Test3", pNode5);
}
void test4(){
TreeNode pNode5 = createBinaryTreeNode(5);
test("Test4", pNode5);
}
void test5(){
test("Test5", null);
}
// 5
// 5
// 5
// 5
// 5
// 5 5
// 5 5
void test6(){
TreeNode pNode1 = createBinaryTreeNode(5);
TreeNode pNode2 = createBinaryTreeNode(5);
TreeNode pNode3 = createBinaryTreeNode(5);
TreeNode pNode4 = createBinaryTreeNode(5);
TreeNode pNode5 = createBinaryTreeNode(5);
TreeNode pNode61 = createBinaryTreeNode(5);
TreeNode pNode62 = createBinaryTreeNode(5);
TreeNode pNode71 = createBinaryTreeNode(5);
TreeNode pNode72 = createBinaryTreeNode(5);
connectTreeNodes(pNode1, null, pNode2);
connectTreeNodes(pNode2, null, pNode3);
connectTreeNodes(pNode3, pNode4, null);
connectTreeNodes(pNode4, pNode5, null);
connectTreeNodes(pNode5, pNode61, pNode62);
connectTreeNodes(pNode61, pNode71, null);
connectTreeNodes(pNode62, null, pNode72);
test("Test6", pNode1);
}
public static void main(String args[]){
new SerializeBiraryTree().test1();
new SerializeBiraryTree().test2();
new SerializeBiraryTree().test3();
new SerializeBiraryTree().test4();
new SerializeBiraryTree().test5();
new SerializeBiraryTree().test6();
}
}
反序列化的递归真的不好理解,可能是我想的太多了,
第一个node是根节点,然后后面的就是它的左子树和右子树了,子树递归出子树,一层一层虽然后面的名字也是node,但是已经不一样了,那个index设计的也很巧妙,不仅仅用来截取str得到node的值,还有一个标记的作用。当截取的部分为“$"时,node的左子树或右子树就不再向下延申了,反正就是很巧妙就是啦