为什么要建立线索二叉树
基本的二叉树结构通常包括结点信息、左右孩子指针。
n个结点的二叉树,其指针数目共有2n个——一个结点两个,n个当然2n个
而这个2*n个指针,满打满算叶子指针都是空,有明确指向的指针 只有n-1个,多达n+1个指针指向为空;
另外,我们如果要对一个结点做处理,肯定要寻找到它,哪怕我们用了前序,后序,中序去寻找,那也是在一个迭代过程中。
如前序遍历一个二叉树,结果为:A B C D E F G
我们可以看出在遍历过程中,D E 作为左右子树,是连续输出的,可在结构上,并不能直接由D 到 E
为了更便于对二叉树的结点进行寻找,等操作
线索化二叉树要做的就是:利用结点Q指向null 的指针p1和p2,使其指向前驱结点和后继结点
如何得到线索二叉树
线索二叉树是在遍历二叉树的基础上,在其遍历顺序上,将没有指针直接指向的结点,改变空指针方向。
准备条件:
**tag标记:**需要标记改变指针的结点。
前结点信息 pre:记录前结点信息,这样可以将后继结点,指向其前驱结点
对于后序线索二叉树,我们需要一个保存双亲信息的指针parent
class CBNode<E>{
E data;
CBNode rchild;
CBNode lchild;
int Ltag;
int Rtag;
CBNode parent;
public CBNode(E data) {
this.data = data;
this.rchild = null;
this.lchild = null;
this.parent=null;
this.Ltag=0;
this.Rtag=0;
}
public CBNode(){
}
}
遍历生成二叉树
public CBNode CreatBTree(CBNode node){
System.out.println("输入一个结点信息:");
String ele=new Scanner(System.in).nextLine();
if(ele.equals("#")){
return null;
}
if(root==null){
node=new CBNode(ele);
root=node;
System.out.println("插入根结点的左子树:");
root.lchild=CreatBTree(root.lchild);
if(root.lchild!=null)
root.lchild.parent=root;
System.out.println("插入根结点的柚子树:");
root.rchild=CreatBTree(root.rchild);
if (root.rchild!=null)
root.rchild.parent=root;
return root;
}else{
node=new CBNode();
node.data=ele;
System.out.println("插入"+node.data+"结点的左子树:");
node.lchild=CreatBTree(node.lchild);
System.out.println("插入"+node.data+"结点的柚子树:");
node.rchild=CreatBTree(node.rchild);
if (node.lchild!=null)
node.lchild.parent=node;
if (node.rchild!=null)
node.rchild.parent=node;
return node;
}
}
前序线索二叉树
空指针,往往在叶子结点上,所以我们和遍历二叉树一样,先遍历得到最左边的叶子结点Y,同时不断更新pre结点,以保存相对根结点,
当遇到叶子结点之后,就将其左孩子指针指向pre结点,
而遍历过程中pre指针会改为Y的后继结点,此时它的会改变指针 指向其根节点
代码部分为:
if(p.lchild==null){
p.Ltag=1;
p.lchild=pre;
}
if(pre!=null&&pre.rchild==null){
pre.rchild=p;
pre.Rtag=1;
}
pre=p;
这里是线索化的核心部分,和遍历二叉树类似,他在遍历循环
if(p.Ltag==0){
BeThread(p.lchild);
}
if(p.Rtag==0){
BeThread(p.rchild);
}
之前,就是前序线索,在中间就是中序线索,在后面就是后序线索二叉树。
在遍历前序二叉树的时候,根据其输出规律,我们也要先遍历到其左边的根节点,从这开始输出。
然后根据标记 选择进入其左右孩子
public void Beoutputtree(CBNode tree){
if (tree==null)
return;
else {
while (tree.Ltag==0){//找到最边上一个结点
System.out.println(tree.data);
tree=tree.lchild;
}
//tree=H
while (tree!=null){
System.out.println(tree.data);
if(tree.rchild==null)//最后一个结点右孩子为空时跳出
return;
if (tree.Rtag==1){
tree=tree.rchild;
}else {
tree=tree.lchild;
}
}
}
}
同理:中序线索二叉树
// 中序线索化 将进入左右孩子分支 放在首位 线索化放中间
public void MidThread(CBNode p){
if (p==null)
return;
else {
MidThread(p.lchild);
if (p.lchild==null){
p.Ltag=1;
p.lchild=pre;
}
if(pre!=null&&pre.rchild==null){
pre.Rtag=1;
pre.rchild=p;
}
pre=p;
MidThread(p.rchild);
}
}
public void Mioutputtree(CBNode tree){
if (tree!=null){//先直接遍历到最左边的左子树
while (tree.Ltag==0){
tree=tree.lchild;
}
while (tree!=null){
System.out.println(tree.data);
if(tree.Rtag==1){//如果右子树 是线索,回到那
tree=tree.rchild;
}else{//如果不是线索 那么我们没有必要再走一次 不然进入两个结点的死循环
tree=tree.rchild;
if(tree==null)
return;
while (tree.Ltag==0&&tree!=null){
tree=tree.lchild;
}
}
}
}
}
这里要注意的是,在遍历输出的时候,我们容易回到某个根结点,然后按照前序会进入右孩子,而这会进入重复,解决是,根据tag,如果其右孩子原本就存在,那么我们应该先进入其右孩子,然后进入右孩子的右孩子。
后续遍历
后续线索二叉树,在遍历的时候,因为线索化使得右孩子的空指针指向其相对根节点,而要到达线索化的下一个结点,则没有直接指针指向,
解决方法:通过相对根结点过去,所以需要用到双亲指针
public void aftThread(CBNode p){
if(p==null){
return;
}else {
if (p.Ltag==0){
aftThread(p.lchild);
}
if(p.Rtag==0){
aftThread(p.rchild);
}
if(p.lchild==null){
p.lchild=pre;
p.Ltag=1;
}
if(pre!=null&&pre.rchild==null){
pre.rchild=p;
pre.Rtag=1;
}
pre=p;
}
}
//后续线索化输出
//后续线索二叉树中,存在 相邻的左右结点 没有直接联系
//通过回溯父结点 来处理
public void aftOutputree(CBNode tree){
if (tree==null){
return;
}
while (tree!=null&&tree.Ltag==0){//先到最左端
tree=tree.lchild;
}
CBNode p=null;//保留遍历过程中上一个结点信息
while (tree!=null){
if(tree.Rtag==1){
System.out.println(tree.data);
p=tree;
tree=tree.rchild;
}else {
if (tree.rchild==p){//说明往右走会重复循环——需要往双亲走
System.out.println(tree.data);
//可能遇到根结点 后续到根节点就返回
if (tree==root)
return;
p=tree;
tree=tree.parent;
}else {//进入双亲结点 且进入右分支 寻找最左结点
tree=tree.rchild;
while (tree!=null&&tree.Ltag==0){
tree=tree.lchild;
}
}
}
}
}
全部代码
package com.treetest.test01;
import java.util.Scanner;
public class clueBTree {
public static void main(String[] args) {
CBTree tree=new CBTree();
tree.CreatBTree(tree);
CBNode pre=null;
//前序线索
/* tree.BeThread(tree.root);
tree.Beoutputtree(tree.root);*/
//中序线索
tree.MidThread(tree.root);
tree.Mioutputtree(tree.root);
//后序线索
/*tree.aftThread(tree.root);
tree.aftOutputree(tree.root);*/
//System.out.println(tree.root);
}
}
/*线索化二叉树 在进行遍历二叉树的时候,所得是一个单向链表,如果我们在遍历过程 按照遍历顺序将每个结点前后加上线索,即可以形成一个双向链表*/
class CBNode<E>{
E data;
CBNode rchild;
CBNode lchild;
int Ltag;
int Rtag;
CBNode parent;
public CBNode(E data) {
this.data = data;
this.rchild = null;
this.lchild = null;
this.parent=null;
this.Ltag=0;
this.Rtag=0;
}
public CBNode(){
}
}
class CBTree extends CBNode{
CBNode root;
public CBTree() {
}
public CBNode CreatBTree(CBNode node){
System.out.println("输入一个结点信息:");
String ele=new Scanner(System.in).nextLine();
if(ele.equals("#")){
return null;
}
if(root==null){
node=new CBNode(ele);
root=node;
System.out.println("插入根结点的左子树:");
root.lchild=CreatBTree(root.lchild);
if(root.lchild!=null)
root.lchild.parent=root;
System.out.println("插入根结点的柚子树:");
root.rchild=CreatBTree(root.rchild);
if (root.rchild!=null)
root.rchild.parent=root;
return root;
}else{
node=new CBNode();
node.data=ele;
System.out.println("插入"+node.data+"结点的左子树:");
node.lchild=CreatBTree(node.lchild);
System.out.println("插入"+node.data+"结点的柚子树:");
node.rchild=CreatBTree(node.rchild);
if (node.lchild!=null)
node.lchild.parent=node;
if (node.rchild!=null)
node.rchild.parent=node;
return node;
}
}
//前序线索化 将线索化 放在前面 进入子节点放后面,pre标记 一直跟在线索化后面 要不断更新pre
private CBNode pre;
public void BeThread(CBNode p){
if(p==null)
return;
else {
if(p.lchild==null){
p.Ltag=1;
p.lchild=pre;
}
if(pre!=null&&pre.rchild==null){
pre.rchild=p;
pre.Rtag=1;
}
pre=p;
if(p.Ltag==0){
BeThread(p.lchild);
}
if(p.Rtag==0){
BeThread(p.rchild);
}
}
}
/*遍历输出前序线索二叉树*/
public void Beoutputtree(CBNode tree){
if (tree==null)
return;
else {
while (tree.Ltag==0){//找到最边上一个结点
System.out.println(tree.data);
tree=tree.lchild;
}
//tree=H
while (tree!=null){
System.out.println(tree.data);
if(tree.rchild==null)//最后一个结点右孩子为空时跳出
return;
if (tree.Rtag==1){
tree=tree.rchild;
}else {
tree=tree.lchild;
}
}
}
}
// 中序线索化 将进入左右孩子分支 放在首位 线索化放中间
public void MidThread(CBNode p){
if (p==null)
return;
else {
MidThread(p.lchild);
if (p.lchild==null){
p.Ltag=1;
p.lchild=pre;
}
if(pre!=null&&pre.rchild==null){
pre.Rtag=1;
pre.rchild=p;
}
pre=p;
MidThread(p.rchild);
}
}
public void Mioutputtree(CBNode tree){
if (tree!=null){//先直接遍历到最左边的左子树
while (tree.Ltag==0){
tree=tree.lchild;
}
while (tree!=null){
System.out.println(tree.data);
if(tree.Rtag==1){//如果右子树 是线索,回到那
tree=tree.rchild;
}else{//如果不是线索 那么我们没有必要再走一次 不然进入两个结点的死循环
tree=tree.rchild;
if(tree==null)
return;
while (tree.Ltag==0&&tree!=null){
tree=tree.lchild;
}
}
}
}
}
public void aftThread(CBNode p){
if(p==null){
return;
}else {
if (p.Ltag==0){
aftThread(p.lchild);
}
if(p.Rtag==0){
aftThread(p.rchild);
}
if(p.lchild==null){
p.lchild=pre;
p.Ltag=1;
}
if(pre!=null&&pre.rchild==null){
pre.rchild=p;
pre.Rtag=1;
}
pre=p;
}
}
//后续线索化输出
//后续线索二叉树中,存在 相邻的左右结点 没有直接联系
//通过回溯父结点 来处理
public void aftOutputree(CBNode tree){
if (tree==null){
return;
}
while (tree!=null&&tree.Ltag==0){//先到最左端
tree=tree.lchild;
}
CBNode p=null;//保留遍历过程中上一个结点信息
while (tree!=null){
if(tree.Rtag==1){
System.out.println(tree.data);
p=tree;
tree=tree.rchild;
}else {
if (tree.rchild==p){//说明往右走会重复循环——需要往双亲走
System.out.println(tree.data);
//可能遇到根结点 后续到根节点就返回
if (tree==root)
return;
p=tree;
tree=tree.parent;
}else {//进入双亲结点 且进入右分支 寻找最左结点
tree=tree.rchild;
while (tree!=null&&tree.Ltag==0){
tree=tree.lchild;
}
}
}
}
}
}