一、结点类的定义
这里只是进行了简单的定义,结点类包括结点数据域和左右孩子引用,结点类是静态内部类。
static final class Node{
Integer data = null;
Node right;//左子树
Node left;//右子树
Node(Integer data){
this.data = data;
}
Node(){
this(null);
}
public String toString() {
return data+"";
}
}
二、根据先序遍历的顺序构造二叉树
假如有一个如下图的二叉树,按照先序遍历的顺序建树需要提供该树的先序遍历结果,并且子树为空时要用特殊字符表示。假设用0表示子树为空,则提供的结果应该为pre={1,2,3,0,0,4,0,0,5,0,0}。然后根据pre来建树。
先序遍历的顺序递归建树:
//root是树根结点
public BinaryTree(List<Integer> t){
root = initBiTree(root,t);
}
public Node initBiTree(Node p,List<Integer> t){
if(!t.isEmpty()){ //t不为空说明还有结点,树还没建完,这个判断应该可以不要
int e = t.get(0); //取出第一个数,这个数就是当前根结点的值
t.remove(0);
if(e!=0){ //e为0表示该结点不为空
p = new Node(e); //创建根结点
p.left = initBiTree(p.left,t); //递归建立左子树
p.right = initBiTree(p.right,t); //递归建立右子树
}
else{ //e为0表示该结点为空
p = null;
}
}
return p;
}
三、先序遍历二叉树
1、递归遍历
public void preOrder(Node t){
if(t!=null){
System.out.println(t);
preOrder(t.left);
preOrder(t.right);
}
}
2、非递归遍历
非递归遍历使用栈实现,首先将根结点入栈,并且入栈的时候就输出,这样就实现了根先输出。然后将左结点入栈,直到左结点为空时,弹出栈顶元素p,并且将p的右结点入栈,循环往复即可。
/*
* 先序遍历二叉树,非递归实现
* 1.先将结点p输出并入栈,然后p指向其左结点pl,如果pl不是空,则也输出并入栈。(这里就是输出根,然
*后找左子树,又输出根)
* 2.如果pl为空,那么从栈中弹出一个结点,这个结点一定是pl的双亲结点p,然后将p指向p的右子树pr,这
*时又回到了第一步
*/
public void preOrderN(){
Node p = root;
Stack<Node> s = new Stack<>();
/*1.p!=null的作用
*当栈里面的最后一个结点p弹出时,表示以p结点为根的树的左子树已经输出完毕了,但是右子树还没有
*输出,当p=p.right后如果p==null说明右子树为空,则不用输出,否则要继续输出
* 2.!s.isEmpty()的作用
* 明显当结点处于栈中时,说明他的左子树也没输出完毕
*/
while(p!=null||!s.isEmpty()){
if(p!=null){
System.out.printf(p);//将根节点输出
s.push(p); //将根节点入栈
p = p.left; //指向其左结点
}
else{
p = s.pop(); //弹出的结点为原来p的双亲结点
p = p.right; //指向p的右结点
}
}
}
四、中序遍历输出
1、递归遍历
public void inOrder(Node t){
if(t!=null){
inOrder(t.left);
System.out.printf(t);
inOrder(t.right);
}
}
2、非递归遍历
中序遍历非递归实现与先序非递归实现类似,只需要改变输出根节点的时间,在结点弹出时输出。
1、先将结点p入栈,然后将p指向其左结点pl,如果pl不为空,则又将pl入栈(根入栈,寻找左子树,左子树根再入栈)
2、如果pl为空,那么从栈中弹出一个结点p并输出(因为入栈时是先入的根节点然后入的左子树根节点,所以弹出时先弹出的是根的左结点),然后将p指向p的右结点,回到1。
public void inOrderN(){
Node p = root;
Stack<Node> s = new Stack<>();
while(p!=null||!s.isEmpty()){
if(p!=null){
s.push(p); //将根结点入栈
p = p.left(); //指向右结点
}
else{
p = s.pop(); //由于左结点比根节点后入栈,所以左结点会先弹出
System.out.printf(p); //输出
p = p.right; //指向右结点
}
}
}
四、后序遍历
1、递归实现
public void postOrder(Node t){
if(t!=null){
postOrder(t.left);
postOrder(t.right);
System.out.printf(t);
}
}
2、非递归实现
后序遍历的递归实现
public void postOrderN(){
Node p = root;
Stack<Node> s = new Stack<>();
Node last = null; //表示上次输出的结点
if(p==null)
return;
do{
while(p!=null){ //将左结点入栈
s.push(p);
p = p.left();
}
bool flag = true;//可以看做是左结点为空的标记
//到这里已经可以保证栈顶结点的左结点为空,所以后面只需要考虑它的右结点
while(!s.isEmpty()&&flag){
p = s.peek(); //当前结点等于栈顶结点
/*
* 如果该结点的右结点为空,或者上次访问的是它的右结点则表示它的左右结点都已经访问完了,
* 现在就可以开始输出该结点了
*/
if(p.right == null||last = p.right){
last = s.pop();
System.out.printf(p);
}
else{
p = p.right; //如果右结点没被访问,那么处理右结点
flag = false;
}
}
}while(!s.isEmpty());
}
五、根据先序和中序遍历的结果构建树
假设二叉树如下,它的先序遍历结果为pre={A,B,C,D,E},中序遍历结果为in={C,B,D,A,E}
1、通过pre可以知道A为根结点
2、然后在in数组中找到A的位置,A的左边(CBD,下标从0到2)为左子树,A的右边(E,下标从4到4)为右子树
3、通过先序的第二个为B,可以得到CBD的根结点为B,以此类推。
这里通过递归实现。
List<Node> pre,in; //pre中存的是先序遍历结果,in中存的是中序遍历结果
public void piInit(List<Node> pre,List<Node> in){
this.pre = pre;
this.in = in;
root = initByPi(0,pre.size(),p); //一开始的边界是从0到pre的长度
}
private Node initByPi(int l,int r,Node p){
int vis=-1;
for(int i=l;i<r;i++){ //找到根节点的位置,并将位置赋值给vis
if(pre.get(0)==in.get(i)){
vis = i;
break;
}
}
p = new Node(pre.get(0)); //创建根节点
pre.remove(0);
if(l<vis) //l<vis说明p的左子树存在
p.left = initByPi(l,vis,p.left);
if(vis+1<r) //说明p的右子树存在
p.right = initByPi(vis+1,r,p.right);
return p;
}
六、根据中序和后序遍历的结果构建树
假设二叉树同上,它的中序遍历结果为pre={C,B,D,A,E},后序遍历结果为post={C,D,B,E,A},过程与五类似
1、通过post可以知道A为根结点
2、然后在in数组中找到A的位置,A的左边(CBD,下标从0到2)为左子树,A的右边(E,下标从4到4)为右子树
3、通过先序的第二个为E,可以得到E的根结点为E,通过后序中的CDB可以得到CBD的根节点为B。
List<Node> in,post; //in中序遍历结果,post后序遍历的结果
public void Ipinit(List<Node> in,List<Node> post){
this.in = in;
this.post = post;
Collections.reverse(post); //把post反转一下,便于操作
root = initByIp(0,pre.size(),root);
}
private Node initByIp(int l,int r,Node p){
int vis=-1;
for(int i=l;i<r;i++){
if(post.get(0) == in.get(i)){
vis = i;break;
}
}
p = new Node(e);
post.remove(0);
//后序遍历数组反转后,从数组的第一个开始刚好是根右左的顺序
if(vis+1<r) //说明右子树存在
p.right = initByIp(vis+1,r,p.right);
if(l<vis) //说明左子树存在
p.left = initByIp(l,vis,p.left);
return p;
}
最后贴一组测试数据,上面的代码不保证一定正确。。。。。。
树是这样的
Integer[] a = new Integer[] {1,2,3,0,0,4,0,0,5,6,7,0,0,8,0,0,0};
Integer[] b = new Integer[] {1,2,3,4,5,6,7,8}; //先序
Integer[] c = new Integer[] {3,2,4,1,7,6,8,5}; //中序
Integer[] d = new Integer[] {3,4,2,7,8,6,5,1}; //后序
List<Integer> t = new ArrayList<>(Arrays.asList(a));
List<Integer> pre = new ArrayList<>(Arrays.asList(b));
List<Integer> in = new ArrayList<>(Arrays.asList(c));
List<Integer> post = new ArrayList<>(Arrays.asList(d));