目录
初学者对于二叉树的遍历(前序遍历,中序遍历,后序遍历,层序遍历),这些放在最早学习的内容,应该是非常熟悉的,但是渐渐的就会发现,对于如何创建一颗二叉树,初学者对此过程还不甚清晰。直接一个一个结点插入吗?当树的深度过大时,这显然是一个费时费力的过程。以下我们介绍二叉树的创建的两种方法——递归与非递归。
一、知识铺垫
创建任意一颗树,在不知道null值归属于哪个结点时,仅仅依靠 单独的遍历字符串 是没有办法把一棵树创建好的。
例如:当我们不知道null值归属与哪个结点时,则说明我们可以往遍历中的任意位置插入结点。
层序遍历:3,9,null,null,20,15,7
层序遍历:3,9,null,20,null,15,7
所以我们直到了单独的一种不知道null结点的遍历时无法准确创建出一棵树的,从遍历的特性我们可以知道,前序和后序可以确认根结点,中序可以确认左右子树,当我们要创建树时(遍历字符串中无null值),至少应该满足以下特性:
- 至少应提供两个以上的遍历字符串
- 一定要提供中序字符串
当然,当遍历字符串中带了null值时,任意一种遍历字符串均能准确建树,我们这里讨论不带null值的情况。
二、二叉树的创建之递归
例:当前序遍历以及中序遍历已经被提供时。
前序:A B K M N O P L C D E H I J F G(下述图片前序有误,以此为准)
中序:M N P O K L B A E D H I J F G C
最终树的图像:
代码:
public TreeNode root;
public BinaryTree(String preOrder,String inOrder){
root=createBinaryTreeByRecursive(preOrder,inOrder);
}
private TreeNode createBinaryTreeByRecursive(String preOrder,String inOrder){
return create(preOrder,inOrder,0,inOrder.length()-1);
}
private int index=0;//千万注意preOder的下标要放在递归函数外边,因为其不能回退
private BinaryTree.TreeNode create(String preOrder, String inOrder, int left, int right){
if(left>right){
return null;
}
//为每一个字符创建新的结点
BinaryTree.TreeNode node=new BinaryTree.TreeNode(preOrder.charAt(index));
//可加可不加
// if(left==right){
// index++;
// return node;
// }
int rootIndex=left-1;//记录根的下标
//找到根结点在中序中的位置,以便确定左右子树
for(int i=left;i<=right;i++){
if(preOrder.charAt(index)==inOrder.charAt(i)){
rootIndex=i;
break;
}
}
//在序列中找到了根节点的位置
if(rootIndex>=left) {
index++;
}
node.leftChild=create(preOrder,inOrder,left,rootIndex-1);
node.rightChild=create(preOrder,inOrder,rootIndex+1,right);
return node;
}
同理,给定后序和中序即将从后往前遍历后序代码
也可以增加HashMap来减少搜索根节点的过程
三、二叉树的创建之非递归(迭代)
在明白和熟悉了递归创建二叉树后,我们可以开始着手非递归创建二叉树了。而最主要的就是把左子树与右子树的范围确认好,所以我们可以利用栈,来判定其接下来是建左子树还是右子树。
代码:
private TreeNode createBinaryTreeByStack(String preOrder,String inOrder){
Stack<TreeNode> stack=new Stack<>();
int inIndex=0;
TreeNode root=new TreeNode(preOrder.charAt(0));
stack.push(root);
TreeNode cur=root;
for(int i=1;i<preOrder.length();i++){
if(stack.peek().val==inOrder.charAt(inIndex)){
//判断哪些结点左子树已经建完
while(!stack.empty()&&stack.peek().val==inOrder.charAt(inIndex)){
cur= stack.pop();
inIndex++;
}
cur.rightChild=new TreeNode(preOrder.charAt(i));
stack.push(cur.rightChild);
}else{
//左子树没有走完,继续建左子树
cur=stack.peek();
cur.leftChild=new TreeNode(preOrder.charAt(i));
stack.push(cur.leftChild);
}
}
return root;
}
时刻记住前序是根-左子树-右子树,而中序是左子树-根-右子树,所以栈中用来存储还未走右子树的根,当栈顶元素与中序中元素一致时,说明此节点的左子树已经走完了(建完了),要开始建右子树了,所以将其从栈中pop()出来,建立其右子树。
每一个结点都可以是根,可以把前序和中序理解成是无数个根按照一定的顺序排列在一起,所以当前序的根与中序的根一致时,可以表明,这个结点,已经把中序的左子树-根当中的左子树建完了。
同理可得,当是后序左子树-右子树-根和中序左子树-根-右子树的时候,我们应该从后遍历后序字符串,同时从后遍历中序字符串,当后序字符串中的元素与中序字符串中的元素一致的时候,我们可以认为,这个结点,已经建完了其右子树,要开始建其左子树了,从而将此结点从栈中pop出。
可以做题练手: