设计能存储char型数据的链式二叉树结构,并实现前/中/后序的递归遍历,前/中序非递归遍历。
目的:(1)理解和掌握如何逐字符读取char型数据;(2)理解并掌握“static方法可通过类名来调用”;(3)理解并掌握内部类的基本含义、构造和使用;(4)初步理解类的设计思想:何时需要设计类,类应具有哪些功能。
import java.util.Scanner;
class ReaderChar{//此类专门用于读取数据
String creatBinTreeStr; //用于建树所需的字符串
int pos; //当前读取的位置下标
ReaderChar(String s){ creatBinTreeStr=s; }//即:构造对象时将建树数据全部传入
char getChar(){
char x=creatBinTreeStr.charAt(pos); //获取pos位置的字符
pos++;
return x;
}
}
class BinTree{
char data;
BinTree L,R;
BinTree( char x ){ data=x; }//注:所有成员变量会有默认值:0/false/null
//注意:建树不同于创建BinTree对象,建树需要输入一组数据,形成一棵树,而构造函数,只能创建一个节点(而非一棵树)
BinTree creatBinTree(ReaderChar r){//r是建树数据的读取器,提供getChar()方法
//另外,不要在creatBinTree()中创建ReaderChar对象,因为涉及递归调用,每次都会创建该对象,一方面浪费空间,更为重要的是,这些ReaderChar对象是不同的对象,每次都会从0开始读取数据
//1、读取一个字符 x=getchar();
char x=r.getChar();
//2、判断:若x=='#' return NULL;
if(x=='#') return null;
//3、能走到此处,必然不是空树,故步骤为:造结点,并给该节点的三个域赋值
//即: t=新节点,t.data=x; t.L=creatBinTree(); t.R=creatBinTree();
BinTree t=new BinTree(x); t.L=creatBinTree(r); t.R=creatBinTree(r);
//4、return t;
return t;
}
void pre(){//前序遍历:无参。t是谁?或者说:对谁进行前序遍历?
//C版:void pre(t){ if(t==null)return ; printf(t.data); pre(t.L); pre(t.r); }--注意:有参
//java版:pre(t)---> t.pre(); t是谁?t是根,是this,this绝对不可能为null,但this的孩子可能为空
System.out.print(this.data+" ");//也可省略this
if( this.L!=null ) this.L.pre();
if( this.R!=null ) this.R.pre();
}
class Stack{//栈:内部类,实际上就是内部类型,一般不外用
BinTree[] s=new BinTree[20]; //数组
int top; //栈顶指针
boolean isEmpty(){//判空
return top==0;
}
void push(BinTree x){//入栈
s[top]=x; top++;
}
BinTree pop(){//出栈
top--; return s[top];
}
}
void preN(){//非递归前序遍历算法
//1、Stack s=new Stack(); 需要一个栈;
Stack s=new Stack();
//2、while(t不空||栈不空){ ---要解决:t的初值是谁?是根,即this
BinTree t=this;
while(t!=null || s.isEmpty()==false)
//if( t不空 ){ 访问t; push(t); t=t.L; } //t=t.L应理解成访问左子树
if(t!=null){ System.out.print(t.data+" "); s.push(t); t=t.L; }
//else{ t=pop(s); t=t.r; }//从栈中弹【出】一个元素,获取该元素的右子树
else {t=s.pop(); t=t.R; }
//}
}
void in(){//中序遍历
if( this.L!=null ) L.in();
System.out.print(data+" ");//也可省略this
if( this.R!=null ) R.in();
}
void post(){//后序遍历
if( this.L!=null ) L.post();
if( this.R!=null ) R.post();
System.out.print(data+" ");//也可省略this
}
}
class App{
public static void main(String[] x){
BinTree t=new BinTree('#');
System.out.print("请输入二叉树建树字符,#表示null:\n");
Scanner sc=new Scanner(System.in);
String s=sc.nextLine(); //读一行,直至回车结束(注:读取的字符串中不包含回车)
ReaderChar r=new ReaderChar(s);
t=t.creatBinTree(r);
System.out.print("\n pre="); t.pre();
System.out.print("\npreN="); t.preN();
System.out.print("\n in ="); t.in();
System.out.print("\npost="); t.post();
}
}
//AB##C##
//ABD###CE##F##
/*总结:1、二叉树,关键在建树,建树的关键在于获取getChar(),策略为:
用一个能提供getChar()操作的对象--》
===》需要一个类,该类中既有getchar(),又有建树数据;
===》为确保每次getChar()获取不同的值,还必须有个pos,以便记录每次读取的位置,并在读取字符后进行偏移
2、递归遍历操作 pre(t)-->t.pre();在pre()中如何知晓调用它的是t还是别的s?无法知晓。这些t或s有个统一的名称,叫this。另外,this不可能为空,但this.L/R可能为空
3、前序遍历的非递归:
(1)内部类--栈,因为该栈的数组元素类型只能是BinTree(why?),或者说该类仅供BinTree使用,故干脆将其放在BinTree的内部(这样也可避免外部使用);
(2)包裹内部类的类称作囿类(包围类)。内部类实际上是囿类的一个成员。换言之,内部类可以直接定义成员变量,也可直接用在成员方法中定义变量、使用内部类的方法,如preN()中,Stack s; s.pop()
作业:
1、根据非递归前序遍历,编写中序非递归遍历;
2、如果不ReaderChar类,程序应该怎么改?
3、把栈放在二叉树类的外面应该怎么改?
4、根据二叉树,编写m度树的创建,以及前/后序递归遍历、层次遍历 --- 完成此题,即被认为是【优秀】
*/
class Circle
{
double radius;
final double PI=3.14;
Circle()
{radius=0; }
Circle(double r)
{radius=r;}
double getRadius()
{return radius;}
double getPerimeter()
{return 2*PI*radius;}
double getArea()
{return PI*radius*radius;}
void disp()
{System.out.println(radius);
System.out.println(this.getPerimeter());
System.out.println(this.getArea());
}
}
class Cylinder extends Circle
{ double height;
Cylinder(double r,double h)
{super(r);
height=h;}
double getHeight()
{return height;}
double getVol()
{return PI*radius*radius*height;}
void disVol()
{//System.out.println(super.getArea());
System.out.println(this.getVol());}
}
class Ci_Cy
{public static void main(String[] args)
{Circle c1=new Circle(2.0);
Cylinder cy1=new Cylinder(2.0,3.0);
c1.disp();
cy1.disVol();
}
}
class SanJiao{
private double a,b,c;
public SanJiao(double x,double y,double z){
a=x; b=y; c=z;
//this.set(x,y,z);
}
public SanJiao(double x, double y){
this(x,x,y);
}
public SanJiao(double x){
this(x,x,x);//等边三角
}
public double heronArea(){
double p=(a+b+c)/2;
return Math.sqrt(p*(p-a)*(p-b)*(p-c));
}
public void set(int a,int b,int c )
{
this.a=a;
this.b=b;
this.c=c;
}
public String toString()
{
return "三角形的三边为:"+this.a+","+this.b+","+this.c;
}
public void showInfo(){
System.out.println(this); }
}
class Rt extends SanJiao{
public Rt(int a,int b)
{
super(a,b,Math.sqrt(a*a+b*b));
}
public Rt(int a,int b,int c)
{
super(a,b,c);
}
}
class App{
public static void main(String[] args){
Rt r=new Rt(3,4);
SanJiao s=new SanJiao(3,2,4);
r.showInfo();
}
}
/*3、设计带头结点的单链表LinkedList。该表存储和处理int型数据,包括用尾插法创建链表、输出表中全部数据、查找值为x的结点,删除值为x的结点作。
【目的】掌握this不得不用的场合,理解和掌握链式结构的定义、构造和使用。
C的链表构造
typedef struct kk{
int data;
struct kk *next;
}Node;
*/
import java.util.Scanner;
class LinkedList{//带头结点的单链表
int data;
LinkedList next;
LinkedList(int x){ data=x; next=null; }
void append(){
System.out.print("请输入一组数,以-1结束");//输入需要用到Scanner类
//Scanner类的用法:1、import导入;2、创建对象 new Scanner(System.in); 3、读取数据 nextInt();
Scanner sc=new Scanner(System.in);
int x=sc.nextInt(); //先读一个int型数据
LinkedList tail, p; //其中p表示新产生的元素,tail表示链表的表尾
tail=this; //初始时,tail=自己;
//注意:在main中调用方式:s.append(),这里的this就是s
while( x!=-1 ){
//造结点、给结点赋值、将结点链入队列、修改表尾、继续读
//p=new LinkedList(); p.data=x; p.next=null; //注意:java中的空指针要小些 ---无构造函数
p=new LinkedList(x);//有构造函数可以这样写
tail.next=p; tail=p;
System.out.print(p.data+" ");
x=sc.nextInt();
}
}
void show(){
//for(LinkedList p=head->next; p!=null; p=p->next)打印p.data;
for(LinkedList p=this.next; p!=null; p=p.next)
System.out.print(p.data+" ");
}
}
class App{
public static void main(String[] x){
//LinkedList s=new LinkedList(); //针对无构造函数的情形
LinkedList s=new LinkedList(0); //针对有构造函数的情形
s.append(); s.show();
}
}
/*总结:1、this不得不用的场合,tail=this;即单链表中,表达【表头】自己
即:调用s.append()是,append()中tail=this; 中的this就是s;
调用t.append()是,append()中tail=this; 中的this就是t;
2、构造函数的作用:不是构造(构造,即分配空间,由new实现,空间大小由构造函数名决定),而是初始化。
*/