二叉树的线索化

在上篇博文中我们说到有关二叉树的遍历问题,将树中的所有结点按照某种次序排列在一个线性有序的序列中,然后从某个结点出发就会可以轻易找到在某种次序下的前驱和后继。

然而当我们希望快速找到某一结点的前驱后继,同时又不希望对二叉树进行遍历时,这时就需要一个东西去把每个结点的前驱后继记录保存起来。为了做到这一点,数据结构引出一个概念叫线索。就好比在某种次序下用一根线将这棵树的前驱后继保存起来,使得一个前驱指针指向前驱结点,一个后继指针指向后继结点。

这里写图片描述
我们可以用一个结构体来对保存这些结点,在这里用枚举对前驱和后继进行标记。

enum Pointer
{
    THREAD,     //线索化
    LINK        //连接
};

struct BinaryTreeNodeThd
{
    T _data;
    BinaryTreeNodeThd<T>* _left;
    BinaryTreeNodeThd<T>* _right;
    Pointer _leftTag;
    Pointer _rightTag;
}

当我们做完这些的时候就可以考虑我们怎么去实现线索化。

1.中序线索二叉树

在中序线索化中,如果_leftTag==LINK,那表示在_left中存储的是左孩子结点的指针;如果_leftTag==THREAD,即_left中存储的是指向前驱的指针。_rightTag也同理。

这里写图片描述

首先判断当前结点,如果当前结点是根结点,那么就递归找到它的最后一个叶子结点,即图中3这个结点。现在对3进行前驱线索化,使得_leftTag等于THREAD。紧接着对它进行后继线索,因为3没有左孩子和右孩子,所以接下来我们需要对4进行线索化。

在这里我们需要借用一个pre的指针,让它在遍历的过程中总是指向在中序遍历下的前驱结点,即在中序遍历中刚刚访问过的结点。同理,4 的前驱结点为2,后继结点为1……一定要注意:当我们遇到空指针时就根据情况填入前驱或后继线索。

void _InOrderThreading(Node* cur, Node*& prev)   //中序线索化
    {
        if (cur == NULL) return;
        _InOrderThreading(cur->_left,prev);   //递归,左孩子线索化

        if (cur->_left == NULL)     //建立当前结点的前驱线索
        {

            cur->_leftTag = THREAD;
            cur->_left = prev;
        }

        if (prev && prev->_right == NULL)    //建立当前结点的后继线索
        {
            prev->_right = cur;
            prev->_rightTag = THREAD;
        }

        prev = cur;      //前驱跟上,当前结点向前遍历
        _InOrderThreading(cur->_right, prev);   //递归,右孩子线索化
    }

2.前序线索二叉树

前序线索在思想上和中序线索是一样的,区别仅是加入前驱和后继的时间不同。线索表示如下图。

这里写图片描述

首先先考虑根,然后在对它的左孩子和右孩子进行处理。当一个结点是叶子结点时,它需要把前驱链接到它的父结点上,用pre保存上一个访问的结点(在这里也就是父结点),然后再次访问它父结点的右孩子……依此类推,对于每个结点都做这样的处理,把大问题不断变成子问题,然后对其进行处理。代码如下。

void _PrevOrderThreading(Node* root, Node*& prev)
    {
        if (root == NULL) return;
        if (root->_left == NULL)
        {
            root->_left = prev;
            root->_leftTag = THREAD;
        }

        if (prev && prev->_right == NULL)
        {
            prev->_right = root;
            prev->_rightTag = THREAD;
        }

        prev = root;

        if (root->_leftTag == LINK)
            _PrevOrderThreading(root->_left, prev);
        if (root->_rightTag == LINK)
            _PrevOrderThreading(root->_right, prev);
    }

对二叉树的线索化,思路都是从大问题不断细化缩小实现,一定要明白它的线索是怎么个线索法:有孩子结点就存孩子结点的指针,没有孩子结点就指向前驱或后继。

最后附上源代码。

#include<iostream>
using namespace std;

enum Pointer
{
    THREAD,
    LINK
};

template<class T>
struct BinaryTreeNodeThd
{
    T _data;
    BinaryTreeNodeThd<T>* _left;
    BinaryTreeNodeThd<T>* _right;
    Pointer _leftTag;
    Pointer _rightTag;


    BinaryTreeNodeThd(const T& x)
        :_left(NULL)
        , _right(NULL)
        , _leftTag(LINK)
        , _rightTag(LINK)
        , _data(x)
    {}
};

template<class T>
class BinaryTreeThd
{
    typedef BinaryTreeNodeThd<T> Node;

public:
    BinaryTreeThd(T* a, size_t n, const T& invalid)
    {
        size_t index = 0;
        _root = _CreatTree(a, n, invalid, index);
    }


    //leftTag==0  left中存放的是指向左孩子结点的指针
    //leftTag==1  left中存放的是该结点中序下的前驱结点指针
    void InOrderThreading()  //中序线索化
    {
        Node* prev = NULL;
        _InOrderThreading(_root, prev);
    }

    void InOrderThd()     //中序线索化遍历
    {
        Node* cur = _root;
        while (cur)
        {
            while (cur->_leftTag == LINK)
            {
                cur = cur->_left;
            }
            cout << cur->_data << " ";

            while (cur->_rightTag == THREAD)
            {
                cur = cur->_right;
                cout << cur->_data << " ";
            }

            cur = cur->_right;
        }
        cout << endl;
    }

    void PrevOrderThreading()
    {
        Node* prev = NULL;
        _PrevOrderThreading(_root, prev);
    }

    void PrevOrderThd()
    {
        Node* cur = _root;
        while(cur)
        {
            while (cur->_leftTag == LINK)
            {
                cout << cur->_data << " ";
                cur = cur->_left;
            }

            cout << cur->_data << " ";
            cur = cur->_right;
        }
        cout << endl;
    }


protected:
    Node* _CreatTree(T* a, size_t n, const T& invalid, size_t& index)
    {
        Node* root = NULL;
        if (index < n && a[index] != invalid)
        {
            root = new Node(a[index]);
            root->_left = _CreatTree(a, n, invalid, ++index);
            root->_right = _CreatTree(a, n, invalid, ++index);
        }
        return root;
    }

    void _InOrderThreading(Node* cur, Node*& prev)
    {
        if (cur == NULL) return;
        _InOrderThreading(cur->_left,prev);   //递归,左孩子线索化

        if (cur->_left == NULL)     //建立当前结点的前驱线索
        {

            cur->_leftTag = THREAD;
            cur->_left = prev;
        }

        if (prev && prev->_right == NULL)    //建立当前结点的后继线索
        {
            prev->_right = cur;
            prev->_rightTag = THREAD;
        }

        prev = cur;      //前驱跟上,当前结点向前遍历
        _InOrderThreading(cur->_right, prev);   //递归,右孩子线索化
    }

    void _PrevOrderThreading(Node* root, Node*& prev)
    {
        if (root == NULL) return;
        if (root->_left == NULL)
        {
            root->_left = prev;
            root->_leftTag = THREAD;
        }

        if (prev && prev->_right == NULL)
        {
            prev->_right = root;
            prev->_rightTag = THREAD;
        }

        prev = root;

        if (root->_leftTag == LINK)
            _PrevOrderThreading(root->_left, prev);
        if (root->_rightTag == LINK)
            _PrevOrderThreading(root->_right, prev);
    }


protected:
    Node* _root;
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值