二叉树学习(四):线索二叉树

来源:互联网 发布:虚拟机ubuntu无法联网 编辑:程序博客网 时间:2024/06/03 21:03

线索二叉树原理

在一棵二叉树中,有n个结点的二叉链表,每个结点有指向左孩子、右孩子的指针域,所以n个结点有2n个指针域。其中,有2n-(n-1)=n+1个空指针域,这在空间存储上是严重的浪费。
这里写图片描述
并且,二叉树在做查找操作的时候,我们只知道,每个结点的左孩子和右孩子,并不知道结点的前驱结点和后继结点是谁,每做查找就需要遍历整个二叉链表。在时间上也是巨大开销。
我们考虑将空指针域利用起来,每个结点的左空指针域(lChild)用来存放该结点的前驱结点,将该结点的右空指针域(rChild)用来存放该结点的后继结点。那么如何区分结点的左指针域到底是指向左孩子还是指向前驱结点呢?我们使用增加一位标志位(ltag)的方式来解决,如果该标志位值为1,则表示是指向前驱结点,如果该标志位是0,则表示指向的是左孩子。
结点的结构如下图:
这里写图片描述
ltag和rtag只占一个bit,故只需增加很少的存储空间。

线索二叉树结构

在线索二叉树中,结点可结构定义如下:

public class ClueNode<T>{    private T data;    public ClueNode<T> lChild;    public ClueNode<T> rChild;    public boolean ltag;    public boolean rtag;}

线索化的实质是将二叉链表中的空指针改为指向前驱或者后继结点的线索。由于前驱和后继只有在遍历的时候才能知道,所以线索的过程就是遍历的过程

二叉树线索化

建立线索二叉树,就是在遍历的时候将空的左右指针域指向当前结点的前驱或者后继结点。设指针pre始终指向刚刚访问过的结点,即指向前一个访问的结点。例如p指向当前结点,pre则指向p的前驱。
在对二叉树加线索时,必须先分配一个头结点,建立头结点和二叉树的根节点的指向关系,对二叉树线索完成以后,还需要建立最后一个结点和头结点之间的线索。

启动二叉树线索化:

public boolean startInThreading(){    if (head==null) return false;    //设置head结点为头结点,其左子结点指向根结点    head.ltag = false;    head.rChild = head;//右指针回指    if (head.lChild == null){        head.lChild = head;//若二叉树为空,左指针回指    } else {        pre = head;        inThreading(head);//设置默认的前驱结点        pre.rChild = head;//按***中序遍历***进行中序线索化        pre.rtag = true;//最后一个结点线索花    }    return true;}

通过中序遍历完成二叉树线索化

public void inThreading(ClueNode<T> node){    if (node==null)         return;    inThreading(node.lChild);//左子树线索化    if (node.lChild == null){//设置前驱线索        node.ltag = true;        node.lChild = pre;    }    if (pre.rChild == null) {//设置后继线索        pre.rtag = true;        pre.rChild = node;    }    pre = node;//设置当前结点为前驱结点    inThreading(node.rChild);//右子树线索化}

其中,完成前驱只需要判断当前结点的的左指针域是否为空,为空就把pre赋值给当前结点左指针域;完成后继,由于操作当前结点的时候,后继结点还没有访问到,所以完成后继的方式是,操作当前结点时,完成前驱结点的后继,当前结点的后继,留给操作下一个结点的时候,去完成后继。例如,操作当前结点p的时候,判断前驱结点pre的右指针域是否为空,如果为空,即进行pre.rChild = p;的操作,将当前结点p赋值给pre右指针域,即完成后继操作。

线索二叉树差找

线索化之后的二叉树在查找操作时,是非常快的,因为此时的结点保存有前驱后继的信息。以中序线索二叉树上寻找结点前驱后继为例:

在中序线索二叉树上查找任意结点的中序前驱结点

对于中序线索二叉树上的任意结点,寻找其中序的前驱结点,有以下两种情况:
1、如果该结点的左标志为1,那么该结点的左指针域所指向的结点就是该结点的前驱结点;
2、如果该结点的左标志为0,表明该结点有左孩子,根据中序遍历的定义,它的前驱结点是以该结点的左孩子为根结点的子树的最右结点,故只需沿着该结点的左子树的右指针域向下查找,当查找到某结点的右标志rtag为1时,该结点就是其前驱结点。

在中序线索二叉树上寻找中序前驱结点算法:

public ClueNode<T> searchPreNode(ClueNode<T> node){    ClueNode<T> q = node.lChild; //当前结点的左指针域赋值给q    if (!node.ltag) {  //左标志为0时,即结点node有左孩子        while (!q.rtag){ //左孩子的右标志为0,就继续往下寻找,直到标志为1            q = q.rChild;        }    }    return q;}

在中序线索二叉树上查找任意结点的中序后继结点

对于中序线索二叉树上的任意结点,寻找其中序的后继结点,有以下两种情况:
1、如果该结点的右标志为1,那么该结点的右指针域所指向的结点就是该结点的后继结点;
2、如果该结点的右标志为0,表明该结点有右孩子,根据中序遍历的定义,它的后继结点是以该结点的右孩子为根结点的子树的最左结点,故只需沿着该结点的右子树的左指针域向下查找,当查找到某结点的左标志rtag为1时,该结点就是其后继结点。

在中序线索二叉树上寻找中序后继结点算法:

public ClueNode<T> searchPostNode(ClueNode<T> node){    ClueNode<T> q = node.rChild;//当前结点node的右指针域赋值给q    if (!node.rtag) { //如果node结点的右标志为0,即node结点有右孩子        while (!q.ltag){ //如果右孩子的左标志为0,继续往下查找,直到左标志为1            q = q.lChild;        }    }    return q;}
0 0