开坑第二天,要完成的是二叉树。按字符串建树,完成先序递归/非递归的遍历。
目的字符串为:A(B(D,E(H(J,K(L,M(,N))))),C(F,G(,I)))
第一步:建树
实验分成两个java文件进行,一个包装功能函数,一个按顺序执行函数。
tree.java中的变量以及构造函数如下:
tree left;
tree right;
char e;
static int i=0;
public tree(char e)
{
this.left=null;
this.right=null;
this.e=e;
}
二叉树的建树,是先建立顶端节点然后初始化,然后建立新节点附着在上层节点的左或右。
建树我使用的是递归方式:
public void create(char []a)
{
tree p=this;
if(a[i]=='(')
{ i++;
if(a[i]==',')
i++;
}
if(a[i]==')'||a[i]==',')
{
i++;
return;
}
if(a[i]>='A'&&a[i]<='Z')
{
if(a[i-1]!=',')
{
tree q=new tree(a[i]);
p.left=q;
i++;
p.left.create(a);
}
if(a[i]==')'||a[i]==',')
{
i++;
return;
}
tree o=new tree(a[i]);
p.right=o;
i++;
p.right.create(a);
if(a[i]==')'||a[i]==',')
i++;
}
}
从图和字符串对应代码:若为*(则意味着开始(前字符的字节点建立,若(*后为字符,则为左节点,若为逗号加字符,则为右节点,若为字符加逗号加字符则意味着左右节点都有。当然存在左右括号包裹一个逗号,即无子节点,这部分在代码中我分开呈现,即没独立设置一个if去判断。
而逗号意味着先返回上级节点,然后判断有无字符,有则进入右节点无则返回。
右括号则证明需要返回到上一级的节点。
先序遍历
因为时间问题,只完成了先序遍历,通过递归和非递归的方式。
递归遍历
public void show()//递归遍历
{
System.out.print(this.e+" ");
if(this.left!=null)
this.left.show();
if(this.right!=null)
this.right.show();
}
递归遍历很简单,输出-进入左节点-进入右节点,而修成中序遍历和后序遍历也很简单,只要修改输出就好不详谈。
非递归遍历
非递归遍历的思路很简单,输出节点,然后判断左右节点是否存在:
(1)左右节点均存在:存储右节点,进入左节点
(2)有左无右节点或有右无左节点:进入左节点/右节点
(3)左右节点为空:返回上一个节点。
1)若为左节点返回:判断右节点是否存在,存在则进入;
2)若为右节点返回:则再次往上返回
该非递归的难点在于,如何存放已输出的节点,因为存放的节点数未知,不可能建立巨大的数组存放,浪费空间。故我选择联合栈使用:
栈的结构为:
tree num;
stack next;
int flag;
public stack(){
this.next=null;
}
public stack(tree e){
this.next=null;
this.num=e;
flag=0;
}
与最基础的栈相比,加入了int flag变量,用于判定是否有左右节点/左右节点进入了没
public void show2()//非递归遍历
{
tree p=this;
stack a=new stack();
System.out.print(p.e+" ");
do{
if(p.left!=null||p.right!=null)
{
a.put(p);
if(p.left!=null)
a.next.flag+=2;
if(p.right!=null)
a.next.flag++;
}
else
p=a.peek();
if(a.next.flag>1)
{
p=p.left;
a.next.flag-=2;
}
else if(a.next.flag>0)
{
p=p.right;
a.next.flag--;
}
if(a.next.flag==0)
a.pop();
System.out.print(p.e+" ");
}while(!a.empty()||p.left!=null||p.right!=null);
}
我的思路是:先输出节点内容,然后判断是否存在左右节点。
若有左右节点,就证明必须再次查找该节点然后进入它的右节点,故放入栈中;哪怕只存在一边节点也无妨,为了令代码简略而设置这样。
而我设置的栈的flag变量,是用于判断在栈中的节点是否要加入右节点/已经进入了右节点。
我给该变量设置初值为0,若有左节点+2,若有右节点+1;即:
左右节点均在 | 仅有左节点 | 仅有右节点 |
---|---|---|
3 | 2 | 1 |
然后设置进入左/右的条件,因为是先序遍历,所以必先进入左节点,由表格可得:
当进入左节点的时候,flag必大于1;进入右节点的时候flag等于1。故可用此控制进入左节点或者右节点,而当flag等于0的时候左右节点的进不去,故控制可达成。而当栈顶的flag等于0的时候,因为能进入必有节点,故证明此时它的节点均进入了,所以可以把栈顶舍弃了。
而循环的跳出条件也很重要,首先必须栈空,若栈未空证明栈中的节点的子节点未进入;而后面两个条件是防止指向栈中最后一个节点的右节点时,判定栈空,跳出循环,遍历失效。因为我编写的代码是,先指向栈顶的子节点,再进行判定,若栈顶的flag为0则出栈,这就导致存在误跳出循环的可能:指向了新的节点,但此时栈空。故加入新的判断条件,因为输出的最后一个节点,左右节点必为空,所以可以使用该条件同时判定。
P.S. 代码可能存在一点瑕疵,某意义上我是按照字符串制作的二叉树建树和遍历。