二叉树的二叉链表实现
对二叉树的操作由二叉树结点类和二叉树类共同。采用二叉链表的二叉树结点类和二叉树类设计如下。
1.二叉链表结点类
声明二叉树的二叉链表结点类BinaryNode< T >如下,T指定结点的元素类型。
package Tree;
public class BinaryNode<T> { //二叉树的二叉链表结点类,T指定结点的元素类型
public T data; //数据域,存储数据元素
public BinaryNode<T> left,right; //地址域,分别指向左、右孩子结点
//构造结点,data指定元素,left、right分别指向左孩子和右孩子结点
public BinaryNode(T data,BinaryNode<T> left,BinaryNode<T> right){
this.data=data;
this.left=left;
this.right=right;
}
public BinaryNode(T data) { //构造元素为data的叶子结点
this(data,null,null);
}
public String toString() { //返回结点数据域的描述字符串
return this.data.toString();
}
public boolean isLeaf() { //判断是否叶子结点
return this.left==null&&this.right==null;
}
}
2.采用二叉链表存储的二叉树类声明
声明二叉树类BinaryTree< T >如下,采用二叉链表存储,其中成员变量root指向二叉树的根结点。
package Tree;
public class BinaryTree<T> {
public BinaryNode<T> root; //根结点,二叉链表结点结构
public BinaryTree() { //构造空二叉树
}
public boolean isEmpty() { //判断是否空二叉树
return this.root==null;
}
...... //稍后给出其他成员方法的声明和实现
}
3.二叉树插入结点
在二叉树的二叉链表中插入一个结点,需要修改该结点的父母结点的left域或right域。因此,插入函数要指定插入结点作为哪个结点的左孩子还是右孩子。
我们在BinaryTree< T >类声明以下重载的成员方法,插入结点。
public BinaryNode insert(T x){ //插入x作为根结点,原根结点作为x的左孩子;返回插入结点
public BinaryNode<T> insert(T x){ //插入x作为根结点,原根结点作为x的左孩子;返回插入结点
return this.root=new BinaryNode<T>(x,this.root,null);
}
//插入x为parent结点的左/右孩子,leftChild指定孩子,取值为true(左)、false(右)
//parent的原左/右孩子成为x结点的左/右孩子;返回插入结点
//若x==null,不插入,返回null。若parent==null,java抛出空对象异常
public BinaryNode<T> insert(BinaryNode<T> parent,T x,boolean leftChild){
if(x==null) {
return null;
}
if(leftChild) { //插入x为parent结点的左/右孩子,返回插入结点
return parent.left=new BinaryNode<T>(x,parent.left,null);
}
return parent.right=new BinaryNode<T>(x,null,parent.right);
}
4.二叉树删除子树
在二叉树中删除一个结点,不仅要修改其父母结点的left或者right域,还要约定如何调整子树结构的规则,即删除一个结点,原先以该结点为根的子树则变成由原左子树和右子树组成的森林,约定一种规则使这个森林组成一课子树。此处,因为无法约定左右子树的合并规则,只能删除以一个结点为根的一棵子树。
BinaryTree< T >类声明以下成员方法,删除子树,Java自动收回被删除子树占用的存储空间。
//删除parent结点的左或者右子树,leftChild指定子树,取值为true(左)、false(右)
public void remove(BinaryNode<T> parent,boolean leftChild) {
if(leftChild) {
parent.left=null; //若parent==null,Java抛出空对象异常
}else {
parent.right=null;
}
}
public void clear() { //删除二叉树的所有结点
this.root=null;
}
5.二叉树孩子优先遍历算法(深度优先遍历DFS)
1.前序遍历二叉树算法描述
由于先根次序遍历规则是递归的,采用递归算法实现的递归方法必须要有参数,通过不同的实际参数区别递归调用执行中的多个方法。而二叉树类必须提供从根结点开始遍历的成员方法,因此,每种递归的遍历算法由两个重载的成员方法实现。
BinaryTree< T >类声明以下重载的成员方法,以先根次序遍历二叉树,采用递归算法实现递归的先根次序遍历规则。
public void preorder() { //输出先根次序遍历序列
preorder(this.root); //先根次序遍历以root结点为根的二叉树
System.out.println();
}
private void preorder(BinaryNode<T> p) { //先根次序遍历以root结点为根的二叉树
if(p!=null) {
System.out.println(p.data.toString()+""); //先访问当前结点元素
preorder(p.left); //按先根次序遍历p的左子树,递归调用,参数为左孩子
preorder(p.right); //按先根次序遍历p的右子树,递归调用,参数为右孩子
}
}
一棵二叉树由多棵子树组成,一个结点也是一棵子树的根。二叉树基于遍历的递归方法,必须以某个结点p为参数,表示遍历以p结点为根的子树。preorder(p)算法说明如下。
(1)p从root根结点开始执行,表示遍历一棵二叉树。
(2)当p指向某个结点时,按先根次序访问结点元素后,再分别遍历其左子树、右子树。由于遍历子树的规则相同,只是子树的根结点不同,所以,递归调用当前preorder()方法,参数分别是p结点的左孩子p.left和右孩子p.right。
(3)当p为空子树时,当前递归方法执行结束,返回调用方法。
2.先根、中根、后跟次序遍历二叉树
BinaryTree< T >类声明以下重载的成员方法,分别以先根、中根、后跟三种次序遍历二叉树,都是递归算法,三者之间的区别只是在于访问结点的时机不同。
public void preorder() { //输出先根次序遍历序列
preorder(this.root); //先根次序遍历以root结点为根的二叉树
System.out.println();
}
private void preorder(BinaryNode<T> p) { //先根次序遍历以root结点为根的二叉树
if(p!=null) {
System.out.println(p.data.toString()+""); //先访问当前结点元素
preorder(p.left); //按先根次序遍历p的左子树,递归调用,参数为左孩子
preorder(p.right); //按先根次序遍历p的右子树,递归调用,参数为右孩子
}
}
public String toString() { //返回先根次序遍历二叉树所有结点的描述字符串,包括空子树标记
return toString(this.root);
}
private String toString(BinaryNode<T> p) { //返回先根次序遍历以p为根的子树描述串,递归算法
if(p==null) {
return"^"; //输出空子树标记
}
return p.data.toString()+""+toString(p.left)+toString(p.right); //递归调用
}
public void inorder() { //输出中根次序遍历序列
inorder(this.root);
System.out.println();
}
public void inorder(BinaryNode<T> p) {
if(p!=null) {
inorder(p.left); //中根次序遍历以p结点为根的子树,递归方法
System.out.println(p.data.toString()+"");
inorder(p.right); //中根次序遍历p的右子树,递归调用
}
}
public void postorder() { //输出后跟次序遍历序列
postorder(this.root);
System.out.println();
}
public void postorder(BinaryNode<T> p) { //后跟次序遍历以p结点为根的子树,递归方法
if(p!=null) {
postorder(p.left);
postorder(p.right);
System.out.println(p.data.toString()+""); //后访问当前结点元素
}
}
6.构造二叉树
图示法能够直观描述二叉树的逻辑结构,但不便于作为计算机输入的表达方式。
由二叉树的特性可知,构造一棵二叉树必须明确以下两种关系:
1.结点与其父母结点以及孩子结点之间的层次关系。
2.兄弟结点左或右的次序关系。
以下讨论三种能够唯一确定一棵二叉树的表示法。
(1)由二叉树的一种遍历序列不能唯一确定一棵二叉树
已知一棵二叉树,可唯一确定其先根、中根、后跟和层次遍历序列;反之,已知二叉树的一种遍历序列却不能唯一确定一棵二叉树。
例如,已知一棵二叉树的先根遍历序列是AB,则能够确定A是根结点,并且B是A的孩子结点,但不能确定是哪个孩子,可能是左孩子,也可能是右孩子。因此,这会得到两种结果。这是因为先根遍历序列只反映父母与孩子结点之间的层次关系,没有反映兄弟结点间的左右次序。
(2)先根和中根序列表示
由于先根次序或后根次序反映父母与孩子结点从层次关系,中根次序反映兄弟结点间的左右次序。所以我们已知二叉树的先根和中根两种次序的遍历序列,可唯一确定一棵二叉树。
下面我们来证明一下,设数组prelist和inlist分别表示一棵二叉树的先根和中根次序遍历序列,两序列长度均为n。
1.由先根遍历次序可知,该二叉树根为prelist[0];该根结点必定在中根次序遍历序列中,设根结点在中根序列inlist中的位置为i(-1<i<n),即有inlist[i]=prelist[0]。
2.由中根遍历次序可知,inlist[i]之前的结点在根的左子树上,后面的结点在根的右子树上。因此,根的左子树由i个结点组成。以此递归,keey1唯一确定一棵二叉树。