线索二叉树的原理以及创建和遍历(c++)

这是一篇非常好的关于线索二叉树的文章,内容详细到位,叙述清晰。作者应该是很认真、细心的人,估计花了不少时间和精力,向作者致敬!

引用地址:http://waret.iteye.com/blog/709779



一、线索二叉树概念

具有 n 个结点的二叉链表中,其二叉链表的 n 个结点中共有 2n 个指针域,在这 2n 个指针域中,真正用于指向后件(左子结点或右子结点)的指针域只有 n-1 个,而另外的 n+1 个指针域都是空的。这样就利用二叉链表中的空指针域,存放指向结点在某种遍历次序下的前趋和后继结点的指针(这种附加的指针称为 " 线索 " ),这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树 (ThreadedBinaryTree) 。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种。

 

二、线索链表的结点结构

线索链表中的结点结构为: 

      ltag 和 rtag 是增加的两个标志域,用来区分结点的左、右指针域是指向其左、右孩子的指针,还是指向其前趋或后继的线索。 

 

三、二叉树的线索化

1 .线索化和线索化实质

将二叉树变为线索二叉树的过程称为线索化 。按某种次序将二叉树线索化的实质是:按该次序遍历二叉树,在遍历过程中用线索取代空指针。

2 .二叉树的中序线索化

( 1 )分析

算法与中序遍历算法类似,只需要将遍历算法中访问结点的操作具体化为建立正在访问的结点与其非空中序前趋结点间线索。

a). 若上次访问到的结点的右指针为空,则将当前访问到的结点序号填入,并置右标志域为 1 ;

b). 若当前访问到的结点的左指针为空,则将上次访问到的及诶单序号填入,并置左标志域为 1

( 2 )将二叉树按中序线索化的算法

Cpp代码  
PROCEDURE INTHREAD(BT,h)  
IF BT != 0 THEN  
    {  
        INTHREAD(L(BT),h)  
        IF (h != 0) and (R(h) = 0) THEN  
            { R(h) = BT; Rflag(h) = 1; }  
        IF L(BT) = 0 THEN  
            { L(BT) = h; Lflag(BT) = 1; }  
        h = BT  
        INTHREAD(R(BT),h)  
    }  
RETURN 

  

( 3 )算法分析

和中序遍历算法一样,递归过程中对每结点仅做一次访问。因此对于 n 个结点的二叉树,算法的时间复杂度亦为 O(n)。

3 .二叉树的前序线索化和后序线索化

前序线索化和后序线索化算法与二叉树的中序线索化类似。

 

四、线索二叉树的运算

1 . 在中序线索二叉树中,查找结点 *p 的中序后继结点

在中序线索二叉树中,查找结点 *p 的中序后继结点分两种情形:

a). 若 *p 的右子树空 ( 即 p->rtag 为 Thread) ,则 p->rchild 为右线索,直接指向 *p 的中序后继。

b). 若 *p 的右子树非空 ( 即 p->rtag 为 Link) ,则 *p 的中序后继必是其右子树中第一个中序遍历到的结点。也就是从*p 的右孩子开始,沿该孩子的左链往下查找,直至找到一个没有左孩子的结点为止,该结点是 *p 的右子树中 “ 最左下 ” 的结点,即 *P 的中序后继结点。

具体算法如下:

Cpp代码  
BinThrNode *Inorderpre(BinThrNode *p)  
{   //在中序线索树中找结点*p的中序前趋,设p非空  
    BinThrNode *q;  
    if (p->ltag==Thread) //*p的左子树为空  
        return p->lchild; //返回左线索所指的中序前趋  
    else{  
        q=p->lchild; //从*p的左孩子开始查找  
        while (q->rtag==Link)  
            q=q->rchild; //右子树非空时,沿右链往下查找  
        return q; //当q的右子树为空时,它就是最右下结点  
    } //end if  
}  


该算法的时间复杂度不超过树的高度 h ,即 O(h) 。

  2 . 在中序线索二叉树中查找结点 *p 的中序前趋结点

中序是一种对称序,故在中序线索二叉树中查找结点 *p 的中序前趋结点与找中序后继结点的方法完全对称。具体情形如下:

a). 若 *p 的左子树为空,则 p->1child 为左线索,直接指向 *p 的中序前趋结点;

b). 若 *p 的左子树非空,则从 *p 的左孩子出发,沿右指针链往下查找,直到找到一个没有右孩子的结点为止。该结点是 *p 的左子树中 “ 最右下 ” 的结点,它是 *p 的左子树中最后一个中序遍历到的结点,即 *p 的中序前趋结点。

具体算法如下:

Cpp代码  
BinThrNode *Inorderpre(BinThrNode *p)  
{   //在中序线索树中找结点*p的中序前趋,设p非空  
    BinThrNode *q;  
    if (p->ltag==Thread) //*p的左子树为空  
        return p->lchild; //返回左线索所指的中序前趋  
    else{  
        q=p->lchild; //从*p的左孩子开始查找  
        while (q->rtag==Link)  
        q=q->rchild; //右子树非空时,沿右链往下查找  
        return q; //当q的右子树为空时,它就是最右下结点  
    } //end if  
}   


      由上述讨论可知:对于非线索二叉树,仅从 *p 出发无法找到其中序前趋 ( 或中序后继 ) ,而必须从根结点开始中序遍历,才能找到 *p 的中序前趋 ( 或中序后继 ) 。线索二叉树中的线索使得查找中序前趋和中序后继变得简单有效。

  3 . 在后序线索二叉树中,查找指定结点 *p 的后序前趋结点

在后序线索二叉树中,查找指定结点 *p 的后序前趋结点的具体规律是:

a). 若 *p 的左子树为空,则 p->lchild 是前趋线索,指示其后序前趋结点。

b). 若 *p 的左子树非空,则 p->lchild 不是前趋线索。由于后序遍历时,根是在遍历其左右子树之后被访问的,故 *p的后序前趋必是两子树中最后一个遍历结点。

当 *p 的右子树非空时, *p 的右孩子必是其后序前趋

当 *p 无右子树时, *p 的后序前趋必是其左孩子

  4 . 在后序线索二叉树中,查找指定结点 *p 的后序后继结点

具体的规律:

a). 若 *p 是根,则 *p 是该二叉树后序遍历过程中最后一个访问到的结点。 *p 的后序后继为空;

b). 若 *p 是其双亲的右孩子,则 *p 的后序后继结点就是其双亲结点

c). 若 *p 是其双亲的左孩子,但 *P 无右兄弟, *p 的后序后继结点是其双亲结点;

d). 若 *p 是其双亲的左孩子,但 *p 有右兄弟,则 *p 的后序后继是其双亲的右子树中第一个后序遍历到的结点,它 是该子树中 “ 最左下的叶结点 ” ;

由上述讨论中可知:在后序线索树中,仅从 *p 出发就能找到其后序前趋结点;要找 *p 的后序后继 结点,仅当 *p 的右子树为空时,才能直接由 *p 的右线索 p->rchild 得到。否则必须知道 *p 的双亲结点才能找到其后序后继。因此,如果线索二叉树中的结点没有指向其双亲结点的指针,就可能要从根开始进行后序遍历才能找到结点 *P 的后序后继。由此,线索对查找指定结点的后序后继并无多大帮助。

  5 . 在前序线索二叉树中,查找指定结点 *p 的前序后继结点

  6 . 在前序线索二叉树中,查找指定结点 *p 的前序前趋结点

在前序线索二叉树中,找某一点 *p 的前序后继也很简单,仅从 *p 出发就可以找到;但找其前序前趋 也必须知道 *p的双亲结点。当树中结点未设双亲指针时,同样要进行从根开始的前序遍历才能找到结点 *p 的前序前趋。

 

五、遍历线索二叉树

遍历某种次序的线索二叉树,只要从该次序下的开始结点开发,反复找到结点在该次序下的后继,直至终端结点。遍历中序线索二叉树算法:

Cpp代码  
PROCEDURE INTHTRAV(BT)  
IF BT = 0 THEN RETURN  
h = BT  
WHILE (Lflag(h) = 0) DO h = L(h)  
OUTPUT V(h)  
WHILE (R(h) != 0 DO  
{  
    IF (Rflag(h) = 1) THEN h = R(h)  
    ELSE  
    {  
        h = R(h)  
        WHILE ((Lflag(h) = 0) and (L(h) != 0)) DO h = L(h)  
    }  
    OUTPUT V(h)  
}  
RETURN <span>  
</span>  


分析:

    a). 中序序列的终端结点的右线索为空,所以 do 语句的终止条件是 p==NULL 。

    b). 该算法的时间复杂性为 O(n) 。因为是非递归算法,常数因子上小于递归的遍历算法。因此,若对一棵二叉树要经常遍历,或查找结点在指定次序下的前趋和后继,则应采用线索链表作为存储结构为宜。

    c). 以上介绍的线索二叉树是一种全线索树(即左右线索均要建立)。许多应用中只要建立左右线索中的一种。

    d). 若在线索链表中增加一个头结点,令头结点的左指针指向根,右指针指向其遍历序列的开始或终端结点会更方便。

 

六、验证前序线索及前序线索遍历和中序线索及中序线索遍历的程序

Cpp代码  
#include <iostream>  
#include <sstream>  
using namespace std;  
  
template<class T>  
struct tbtnode  
{  
    T s_t;  
    bool lflag;  
    bool rflag;  
    tbtnode *lchild;  
    tbtnode *rchild;  
};  
  
template<class T>  
tbtnode<T>* createtbt(tbtnode<T>* tbt, int k, ostream& out, istream& in)  
{  
    tbtnode<T> *p, *t;  
    T tmp;  
    out << "Input key: " << endl;  
    in >> tmp;  
    out << '\t' << tmp << endl;  
    if ('0' != tmp)  
    {  
        p = new tbtnode<T>;  
        p->s_t = tmp;  
        p->lchild = NULL;  
        p->rchild = NULL;  
        p->lflag = 0;  
        p->rflag = 0;  
        if (0 == k) t = p;  
        if (1 == k) tbt->lchild = p;  
        if (2 == k) tbt->rchild = p;  
  
        createtbt(p, 1, out, in);  
        createtbt(p, 2, out, in);  
    }  
  
    return (t);  
}  
  
template<class T>  
void pretraverse(tbtnode<T> *root)  
{  
    if (NULL != root)  
    {  
        cout << root->s_t << " ";  
        pretraverse(root->lchild);  
        pretraverse(root->rchild);  
    }  
}  
  
template<class T>  
void intraverse(tbtnode<T> *root)  
{  
    if (NULL != root)  
    {  
        intraverse(root->lchild);  
        cout << root->s_t << " ";  
        intraverse(root->rchild);  
    }  
}  
  
template<class T>  
void postraverse(tbtnode<T> *root)  
{  
    if (NULL != root)  
    {  
        postraverse(root->lchild);  
        postraverse(root->rchild);  
        cout << root->s_t << " ";  
    }  
}  
  
template<class T>  
void inthread(tbtnode<T> *tbt, tbtnode<T> **h)  
{  
    if (NULL != tbt)  
    {  
        inthread(tbt->lchild, h);  
        if (tbt->lchild == NULL)  
        {  
            tbt->lchild = *h;  
            tbt->lflag = 1;  
        }  
        if ((*h != NULL) && ((*h)->rchild == NULL))  
        {  
            (*h)->rchild = tbt;  
            (*h)->rflag = 1;  
        }  
        *h = tbt;  
        inthread(tbt->rchild, h);  
    }  
}  
  
template<class T>  
void prethread(tbtnode<T> *tbt, tbtnode<T> **h)  
{  
    if (NULL != tbt)  
    {  
        if (tbt->lchild == NULL)  
        {  
            tbt->lchild = *h;  
            tbt->lflag = 1;  
        }  
  
        if ((*h != NULL) && ((*h)->rchild == NULL))  
        {  
            (*h)->rchild = tbt;  
            (*h)->rflag = 1;  
        }  
  
        *h = tbt;  
        //*h = tbt->lchild;  
        if (tbt->lflag == 0)  
            prethread(tbt->lchild, h);  
        if (tbt->rflag == 0)       
            prethread(tbt->rchild, h);  
    }  
}  
  
template<class T>  
void posthread(tbtnode<T> *tbt, tbtnode<T> **h)  
{  
    if (NULL != tbt)  
    {  
        posthread(tbt->lchild, h);  
        posthread(tbt->rchild, h);  
  
        if (tbt->lchild == NULL)  
        {  
            tbt->lchild = *h;  
            tbt->lflag = 1;  
        }  
  
        if ((*h != NULL) && ((*h)->rchild == NULL))  
        {  
            (*h)->rchild = tbt;  
            (*h)->rflag = 1;  
        }  
        *h = tbt;  
    }  
}  
  
template<class T>  
void inthtraverse(tbtnode<T> *tbt)  
{  
    tbtnode<T> *h;  
    if (tbt == NULL) return;  
    h = tbt;  
    while (h->lflag == 0) h = h->lchild;  
    cout << h->s_t << " ";  
    while (h->rchild != NULL)  
    {  
        if (h->rflag == 1)  
            h = h->rchild;  
        else  
        {  
            h = h->rchild;  
            while ((h->lflag == 0) && (h->lchild != NULL))  
                h = h->lchild;  
        }  
        cout << h->s_t << " ";  
    }  
}  
  
template<class T>  
void prethtraverse(tbtnode<T> *tbt)  
{  
    tbtnode<T> *h;  
    if (tbt == NULL) return;  
    h = tbt;  
    cout << tbt->s_t << " ";  
    while (h->rchild != NULL)  
    {  
        if ((h->lflag == 0) && (h->lchild != NULL))  
        {  
            h = h->lchild;  
        }  
        else  
        {  
            h = h->rchild;  
        }  
        cout << h->s_t << " ";  
    }  
}  
  
template<class T>  
void posthtraverse(tbtnode<T> *tbt)  
{  
    tbtnode<T> *h;  
    if (tbt == NULL) return;  
    h = tbt;  
}  
  
int main()  
{  
    typedef tbtnode<char> tbtnode_char;  
    tbtnode_char *root;  
    //istringstream ssin(string("AB0DG000CE0H00F00"));  
    istringstream ssin(string("ABCD00E00FG00H00IJK00L00MN00O00"));  
    root = createtbt(root, 0, cout, ssin);  
    pretraverse(root);  
    cout << endl;  
    intraverse(root);  
    cout << endl;  
    postraverse(root);  
    cout << endl;  
  
    cout << "通过穿线二叉树进行遍历" << endl;  
    tbtnode_char *h = NULL;  
  
    //inthread(root, &h);  
    //inthtraverse(root);  
  
    prethread(root, &h);  
    prethtraverse(root);  
  
    cout << endl;  
  
    return 0;  
}


  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值