线索二叉树

Thread Binary Tree Link

线索二叉树

//线索二叉树    
    //发现:有n个节点的二叉树只是使用了 n - 1个指针域,还有n + 1根指针域没被使用到。
    //中序遍历序列是: DGBHEACF。
    //把指向前驱节点的后继节点的指针称为线索。
    //概念把加上了线索的二叉树称为线索二叉树。
    //对二叉树进行某种序列的遍历使其称为一棵线索二叉树的过程称为二叉树的线索化。
    //线索二叉树分前序,中序,后续,层序。
    //以往的二叉链表,在增加了线索之后称为线索链表

//二叉树的线索化(前提是二叉树已经建立起来了)
    //用中序遍历线索化二叉树
        //a)创建线索二叉树
            //a.1)用任何方法创建二叉树
            //a.2)将该二叉树根据前、中、后续遍历序列创建线索
    //用前序遍历线索化二叉树
    //用后续遍历序列线索二叉树

//线索二叉树的操作
    //1)找线索二叉树的第一个和最后一个节点
    //2)找线索二叉树中某个节点的前驱后后继节点
        //只实现中序遍历的 前驱和后继
        //如果用二叉链表作为二叉树的存储结构,那么:
            //对于中序化的线索二叉树,既可以找到前序节点又可以找到后继节点
            //对于前序化的线索二叉树,可以找到后继节点,但无法找到前驱节点
            //对于后序化的线索二叉树,可以找到前驱节点,但无法找到后继节点
        //如果用三叉链表作为二叉树的存储结构,则找前驱和后继节点都是可以做到
            //三叉链表作为二叉树

    //3)对线索二叉树进行遍历
    //4)对线索二叉树的节点进行查找

节点的定义

template<class T>                 //T代表数据元素的类型
struct BinaryTreeNode
{
    T              data;          //数据域
    BinaryTreeNode* LeftChild;     //左子节点指针
    BinaryTreeNode* RightChild;    //右子节点定义
    int8_t         leftTag;       //左标记 = 0;  表示lfetChild指向的是左子节点, = 1表示leftChild指向的是前驱节点(线索)
    int8_t         rightTag;      //右标记 = 0;  表示RightChild指向的是右子节点, = 1表示RightChild指向的是前驱节点(线索)
};

线索二叉树的定义

//线索二叉树的定义
template<class T>
class ThreadBinaryTree
{
public:
    ThreadBinaryTree();      //构造函数
    ~ThreadBinaryTree();     //析构函数
private:
    void ReleaseNode(BinaryTreeNode<T>* pnode);                          //节点的释放

public:
    void CreateBTreeAccorPT(char* pstr);                                //利用扩展二叉树的前序遍历来创建一颗二叉树

private:
    void CreateBTreeAccorPTRect(BinaryTreeNode<T>*& tnode, char*& pstr);   //参数类型为引用类型,确保递归调用中对参数的改变会影响到调用者

public:
    void CreateThreadInBTreeAccordIO();         //在二叉树中根据中序遍历序列创建线索
    void CreateThreadInBTreeAccordPO();         //在二叉树中根据前序遍历序列创建线索
    void CreateThreadInBTreeAccordPOSTO();      //在二叉树中根据后序遍历序列创建线索

private:
    void CreateThreadInBTreeAccordIO(BinaryTreeNode<T>*& tnode, BinaryTreeNode<T>*& pre);       //参数为引用类型
    void CreateThreadInBTreeAccordPO(BinaryTreeNode<T>*& tnode, BinaryTreeNode<T>*& pre);       //参数为引用类型
    void CreateThreadInBTreeAccordPOSTO(BinaryTreeNode<T>*& tnode, BinaryTreeNode<T>*& pre);    //参数为引用类型

public:
    BinaryTreeNode<T>* GetFirst_IO();        //找线索二叉树(中序线索化)的第一个节点
    BinaryTreeNode<T>* GetLast_IO();         //找线索二叉树(中序线索化)的最后一个节点

private:
    BinaryTreeNode<T>* GetFirst_IO(BinaryTreeNode<T>* root);
    BinaryTreeNode<T>* GetLast_IO(BinaryTreeNode<T>* root);

public:
    BinaryTreeNode<T>* GetNextPoint_IO(BinaryTreeNode<T>* currnode);       //找线索二叉树(中序线索化)中当前节点的后继节点
    BinaryTreeNode<T>* GetPriorPoint_IO(BinaryTreeNode<T>* currnode);      //找线索二叉树(中序线索化)中当前节点的前驱节点
    BinaryTreeNode<T>* GetNextPoint_PO(BinaryTreeNode<T>* currnode);       //找线索二叉树(前序线索化)中当前节点的后继节点
    BinaryTreeNode<T>* GetPriorPoint_POSTO(BinaryTreeNode<T>* currnode);   //找线索二叉树(后序线索化)中当前节点的前驱节点

public:
    void inOrder_Org();      //传统中序遍历来遍历线索二叉树
    void inOrder_IO();       //中序遍历 "按照中序遍历序列线索化的二叉树"   线索二叉树
    void revInOrder_IO();    //逆向中序遍历按照 “中序遍历序列线索化的二叉树” 线索二叉树

private:
    void inOrder_Org(BinaryTreeNode<T>* tNode);
    void inOrder_IO(BinaryTreeNode<T>* tNode);
    void revInOrder_IO(BinaryTreeNode<T>* tNode);

public:
    BinaryTreeNode<T>* SearchElem_IO(const T& e);       //中序遍历序列线索化二叉树查找某个节点(假设二叉树的节点各不相同)
    BinaryTreeNode<T>* SearchElem_PO(const T& e);       //前序遍历序列线索化的二叉树查找某个节点(假设二叉树的节点各不相同)
    BinaryTreeNode<T>* SearchElem_POSTO(const T& e);    //后续遍历序列线索化二叉树查找某个节点(假设二叉树的节点各不相同)

private:
    BinaryTreeNode<T>* SearchElem_IO(BinaryTreeNode<T>* tNode, const T& e);
    BinaryTreeNode<T>* SearchElem_PO(BinaryTreeNode<T>* tNode, const T& e);
    BinaryTreeNode<T>* SearchElem_POSTO(BinaryTreeNode<T>* tNode, const T& e);

private:
    BinaryTreeNode<T>* root;   //树根指针
};

构造与析构

//构造函数
template<class T>
ThreadBinaryTree<T>::ThreadBinaryTree()
{
    root = nullptr;
}

//析构函数
template<class T>
ThreadBinaryTree<T>::~ThreadBinaryTree()
{
    ReleaseNode(root);
}

节点的释放

//释放二叉树节点
template<class T>
void ThreadBinaryTree<T>::ReleaseNode(BinaryTreeNode<T>* pnode)
{
    if (pnode != nullptr)
    {
        if (pnode->leftTag == 0)
        {
            ReleaseNode(pnode->LeftChild);              //只有真正需要delete的节点,才会调用ReleaseNode
        }
        if (pnode->rightTag == 0)
        {
            ReleaseNode(pnode->RightChild);
        }
    }
    delete pnode;
}

扩展前序创建二叉树

//利用扩展二叉树的前序遍历来创建一颗二叉树
template<class T>
void ThreadBinaryTree<T>::CreateBTreeAccorPT(char* pstr)
{
    CreateBTreeAccorPTRect(root, pstr);
}

template<class T>
void ThreadBinaryTree<T>::CreateBTreeAccorPTRect(BinaryTreeNode<T>*& tnode, char*& pstr)
{
    if (*pstr == '#')
    {
        tnode = nullptr;
    }
    else
    {
        tnode = new BinaryTreeNode<T>;      //创建根节点
        tnode->leftTag = tnode->rightTag = 0;   //标志先给0
        tnode->data = *pstr;

        CreateBTreeAccorPTRect(tnode->LeftChild, ++pstr);     //创建左子树
        CreateBTreeAccorPTRect(tnode->RightChild, ++pstr);    //创建右子树
    }
}

线索的创建

  • 中序遍历创建线索

//在二叉树中根据中序遍历序列创建线索
template<class T>
void ThreadBinaryTree<T>::CreateThreadInBTreeAccordIO()
{
    BinaryTreeNode<T>* pre = nullptr;      //记录当前所指向的节点的前驱节点(刚开始的节点没有前驱,所以设置为nullptr
    CreateThreadInBTreeAccordIO(root, pre);

    //处理最后一个节点的右孩子,因为这个右孩子没处理。
    pre->RightChild = nullptr;             //这里之所以直接给nullptr,是因为中序访问顺序是 左根右,所以最后一个节点不可能有右孩子,
                                                //否则最后一个访问的节点就会是它的右孩子

                                           //其实就算不执行这句, pre->rightChild也已经是等于nullptr了。
    pre->rightTag = 1;                     //线索化
}

template<class T>
void ThreadBinaryTree<T>::CreateThreadInBTreeAccordIO(BinaryTreeNode<T>*& tnode, BinaryTreeNode<T>*& pre)
{
    if (tnode == nullptr)
    {
        return;
    }

    //中序遍历序列(左根右),递归循序非常类似于中序遍历
    CreateThreadInBTreeAccordIO(tnode->LeftChild, pre);

    if (tnode->LeftChild == nullptr)          //找空闲的指针域进行线索化
    {
        tnode->leftTag = 1;                 //线索
        tnode->LeftChild = pre;
    }

    //这个前序节点到后继节点肯定是当前这个节点tnode
    if (pre != nullptr && pre->RightChild == nullptr)
    {
        pre->rightTag = 1;                  //线索
        pre->RightChild = tnode;
    }
    pre = tnode;                            //前驱节点指针指向当前节点
    CreateThreadInBTreeAccordIO(tnode->RightChild, pre);    //到这里的时候 pre 即是根节点
}
  • 前序遍历创建线索

//在二叉树中根据前序遍历序列创建线索
template<class T>
void ThreadBinaryTree<T>::CreateThreadInBTreeAccordPO()
{
    BinaryTreeNode<T>* pre = nullptr;      //记录当前所指向的节点的前驱节点(刚开始的节点没有前驱,所以设置为nullptr
    CreateThreadInBTreeAccordPO(root, pre);
    pre->RightChild = nullptr;
    pre->rightTag = 1;                     //线索化
}

template<class T>
void ThreadBinaryTree<T>::CreateThreadInBTreeAccordPO(BinaryTreeNode<T>*& tnode, BinaryTreeNode<T>*& pre)
{
    if (tnode == nullptr)
    {
        return;
    }

    //前序遍历序列(根左右),递归循序非常类似于中序遍历

    if (tnode->LeftChild == nullptr)          //找空闲的指针域进行线索化
    {
        tnode->leftTag = 1;                 //线索
        tnode->LeftChild = pre;
    }

    //这个前序节点到后继节点肯定是当前这个节点tnode
    if (pre != nullptr && pre->RightChild == nullptr)
    {
        pre->rightTag = 1;                  //线索
        pre->RightChild = tnode;
    }
    pre = tnode;                            //前驱节点指针指向当前节点

    if (tnode->leftTag == 0)                 //当leftChild是真正的子节点,而不是线索化后的前驱节点时
        CreateThreadInBTreeAccordPO(tnode->LeftChild, pre);
    if (tnode->rightTag == 0)                //当rightchild是真正的子节点,而不是线索化后的后继节点时
        CreateThreadInBTreeAccordPO(tnode->RightChild, pre);    //到这里的时候 pre 即是根节点
}
  • 后续遍历创建线索

//在二叉树中根据后序遍历序列创建线索
template<class T>
void ThreadBinaryTree<T>::CreateThreadInBTreeAccordPOSTO()
{
    BinaryTreeNode<T>* pre = nullptr;      //记录当前所指向的节点的前驱节点(刚开始的节点没有前驱,所以设置为nullptr
    CreateThreadInBTreeAccordPOSTO(root, pre);

    if (pre->RightChild == nullptr)
    {
        pre->rightTag = 1;
    }
}

template<class T>
void ThreadBinaryTree<T>::CreateThreadInBTreeAccordPOSTO(BinaryTreeNode<T>*& tnode, BinaryTreeNode<T>*& pre)
{
    if (tnode == nullptr)
    {
        return;
    }

    CreateThreadInBTreeAccordPOSTO(tnode->LeftChild, pre);
    CreateThreadInBTreeAccordPOSTO(tnode->RightChild, pre);    //到这里的时候 pre 即是根节点
    //后序遍历序列(左右根),递归循序非常类似于中序遍历
    if (tnode->LeftChild == nullptr)          //找空闲的指针域进行线索化
    {
        tnode->leftTag = 1;                 //线索
        tnode->LeftChild = pre;
    }

    //这个前序节点到后继节点肯定是当前这个节点tnode
    if (pre != nullptr && pre->RightChild == nullptr)
    {
        pre->rightTag = 1;                  //线索
        pre->RightChild = tnode;
    }
    pre = tnode;                            //前驱节点指针指向当前节点
}

中序线索化的操作

  • 找中序线索后的第一个节点

template<class T>
BinaryTreeNode<T>* ThreadBinaryTree<T>::GetFirst_IO()        //找线索二叉树(中序线索化)的第一个节点
{
    return GetFirst_IO(root);
}

template<class T>
BinaryTreeNode<T>* ThreadBinaryTree<T>::GetFirst_IO(BinaryTreeNode<T>* root)
{
    //中序遍历 “左跟右顺序”,一直找真正的左孩子
    if (root == nullptr)
        return nullptr;
    BinaryTreeNode<T>* tmpnode = root;
    while (tmpnode->leftTag == 0)         //指向真正的左子节点
    {
        tmpnode = tmpnode->LeftChild;
    }
    return tmpnode;
}
  • 找中序线索后的最后一个节点 

template<class T>
BinaryTreeNode<T>* ThreadBinaryTree<T>::GetLast_IO()         //找线索二叉树(中序线索化)的最后一个节点
{
    return GetLast_IO(root);
}

template<class T>
BinaryTreeNode<T>* ThreadBinaryTree<T>::GetLast_IO(BinaryTreeNode<T>* root)
{
    //中序遍历是 “左根右顺序”,一直找真正的右孩子即可
    if (root == nullptr)
        return nullptr;
    BinaryTreeNode<T>* tmpnode = root;
    while (tmpnode->rightTag == 0)         //指向真正的左子节点
    {
        tmpnode = tmpnode->RightChild;
    }
    return tmpnode;
}

线索化后的前驱及后继的查找

  • 中序线索找当前节点前驱

template<class T>
BinaryTreeNode<T>* ThreadBinaryTree<T>::GetPriorPoint_IO(BinaryTreeNode<T>* currnode)     //找线索二叉树(中序线索化)中当前节点的前驱节点
{
    if (currnode == nullptr)
        return nullptr;
    if (currnode->leftTag == 1)         //leftchild指向的是前驱节点
        return currnode->LeftChild;
    //如果该节点的leftChild指向的是真正的右孩子节点,那么该怎么样获得该节点的前驱节点呢?
    return GetLast_IO(currnode->LeftChild);     //在左子树中查找最后一个要访问的节点
}
  • 中序线索找当前节点后继

template<class T>
BinaryTreeNode<T>* ThreadBinaryTree<T>::GetNextPoint_IO(BinaryTreeNode<T>* currnode)    //找线索二叉树(中序线索化)中当前节点的后继节点
{
    if (currnode == nullptr)
        return nullptr;
    if (currnode->rightTag == 1)         //rightChild指向的是后继节点
        return currnode->RightChild;

    //如果该节点的rightChild指向的是真正的右孩子节点,那么该怎么样获得该节点的后继节点呢?
    //考虑到中序遍历的顺序是 “左根右”,那么当前节点(看成根)的右子树的第一个节点就是:
    return GetFirst_IO(currnode->RightChild);           //在右子树中查找第一个要访问的节点  
}
  • 前序线索找当前节点后继

template<class T>
BinaryTreeNode<T>* ThreadBinaryTree<T>::GetNextPoint_PO(BinaryTreeNode<T>* currnode)       //找线索二叉树(前序线索化)中当前节点的后继节点
{
    //根左右
    if (currnode == nullptr)
        return nullptr;
    if (currnode->rightTag == 1)
        return currnode->RightChild;

    //该节点有右孩子才能走到这里
    //那么:盖如该节点有左孩子,则该节点的后继节点必然是左孩子的第一个节点,根据根左右顺序,就是该左孩子节点
    if (currnode->leftTag == 0)      //有左孩子
        return currnode->LeftChild;
    //没有左孩子,而且前面已经确定了有右孩子,则根据根左右顺序
    return currnode->RightChild;
}
  • 后序线索找当前节点前驱

template<class T>
BinaryTreeNode<T>* ThreadBinaryTree<T>::GetPriorPoint_POSTO(BinaryTreeNode<T>* currnode)    //找线索二叉树(后序线索化)中当前节点的前驱节点
{
    if (currnode == nullptr)
    {
        return nullptr;
    }
    if (currnode->leftTag == 1)
    {
        return currnode->LeftChild;
    }

    //有左孩子,但是否有右孩子并不确定
    if (currnode->rightTag == 0)
    {
        //有右孩子,则是右子树中按照后序遍历 得到的最后一个节点,该节点就是这个右孩子
        return currnode->RightChild;
    }

    //没有右孩子,那么按照左根右的顺序,则左子树中按照后序遍历得到的最后一个节点,该节点就是这个左孩子
    return currnode->LeftChild;
}

中序遍历操作

  • 传统中序遍历

template<class T>
void ThreadBinaryTree<T>::inOrder_Org()      //传统中序遍历来遍历线索二叉树
{
    inOrder_Org(root);
}

template<class T>
void ThreadBinaryTree<T>::inOrder_Org(BinaryTreeNode<T>* tNode)
{
    if (tNode != nullptr)            //若二叉树非空
    {
        //左根右
        if (tNode->leftTag == 0)     //是真正的左孩子
            inOrder_Org(tNode->LeftChild);

        cout << (char)tNode->data << " ";   //输出节点数据域的值

        if (tNode->rightTag == 0)    //是真正的右孩子
        {
            inOrder_Org(tNode->RightChild);         //递归方式中序遍历右子树 
        }
    }
}
  • 按线索中序遍历

template<class T>
void ThreadBinaryTree<T>::inOrder_IO()       //中序遍历 "按照中序遍历序列线索化的二叉树"   线索二叉树
{
    inOrder_IO(root);
}

template<class T>
void ThreadBinaryTree<T>::inOrder_IO(BinaryTreeNode<T>* tNode)
{
    BinaryTreeNode<T>* tmpNode = GetFirst_IO(tNode);
    while (tmpNode != nullptr)     //从第一个节点开始一直找后继即可
    {
        cout << (char)tmpNode->data << " ";      //输出节点数据域的值
        tmpNode = GetNextPoint_IO(tmpNode);
    }
}
  • 逆向线索中序遍历

template<class T>
void ThreadBinaryTree<T>::revInOrder_IO()     //逆向中序遍历按照 “中序遍历序列线索化的二叉树” 线索二叉树
{
    revInOrder_IO(root);
}

template<class T>
void ThreadBinaryTree<T>::revInOrder_IO(BinaryTreeNode<T>* tNode)
{
    BinaryTreeNode<T>* tmpNode = GetLast_IO(tNode);
    while (tmpNode != nullptr)           //从第一个节点开始一直找后继即可
    {
        cout << (char)tmpNode->data << " ";     //输出节点的数据域的值
        tmpNode = GetPriorPoint_IO(tmpNode);    //找前驱节点
    }
}

线索化后的查找某节点

  • 中序线索化后查找某节点

template<class T>
BinaryTreeNode<T>* ThreadBinaryTree<T>::SearchElem_IO(const T& e)    //中序遍历序列线索化二叉树查找某个节点(假设二叉树的节点各不相同)
{
    return SearchElem_IO(root, e);
}

template<class T>
BinaryTreeNode<T>* ThreadBinaryTree<T>::SearchElem_IO(BinaryTreeNode<T>* tNode, const T& e)
{
    if (tNode == nullptr)
    {
        return nullptr;
    }
    if (tNode->data == e)     //从根开始找
        return tNode;

    //这里的代码取自于 中序遍历按照 "中序遍历序列线索化二叉树"线索二叉树inOrder_IO()的代码
    BinaryTreeNode<T>* tmpNode = GetFirst_IO(tNode);
    while (tmpNode != nullptr)           //从第一个节点开始一直找后继即可
    {
        if (tmpNode->data == e)
        {
            return tmpNode;
        }
        tmpNode = GetNextPoint_IO(tmpNode);
    }
    return nullptr;
}
  • 前序线索化后查找某节点

template<class T>
BinaryTreeNode<T>* ThreadBinaryTree<T>::SearchElem_PO(const T& e)     //前序遍历序列线索化的二叉树查找某个节点(假设二叉树的节点各不相同)
{
    return SearchElem_PO(root, e);
}

template<class T>
BinaryTreeNode<T>* ThreadBinaryTree<T>::SearchElem_PO(BinaryTreeNode<T>* tNode, const T& e)
{
    if (tNode == nullptr)
    {
        return nullptr;
    }
    BinaryTreeNode<T>* tmpNode = root;      //根就是第一个节点
    while (tmpNode != nullptr)               //从第一个节点开始一直找后继即可                              
    {
        if (tmpNode->data == e)
        {
            return tmpNode;
        }
        tmpNode = GetNextPoint_PO(tmpNode);
    }
    return nullptr;
}
  • 后续线索化后查找某节点

template<class T>
BinaryTreeNode<T>* ThreadBinaryTree<T>::SearchElem_POSTO(const T& e)        //后续遍历序列线索化二叉树查找某个节点(假设二叉树的节点各不相同)
{
    return SearchElem_POSTO(root, e);
}

template<class T>
BinaryTreeNode<T>* ThreadBinaryTree<T>::SearchElem_POSTO(BinaryTreeNode<T>* tNode, const T& e)
{
    if (tNode == nullptr)
    {
        return nullptr;
    }
    BinaryTreeNode<T>* tmpNode = root;          //跟就是最后一个节点
    while (tmpNode != nullptr)                   //从最后一个节点开始一直找前驱即可
    {
        if (tmpNode->data == e)
        {
            return tmpNode;
        }
        tmpNode = GetPriorPoint_POSTO(tmpNode);
    }
    return nullptr;
}

测试用例

int main()
{
    ThreadBinaryTree<int> ThreadTree;
    ThreadTree.CreateBTreeAccorPT((char*)"ABD#G##EH###C#F##");   //利用扩展二叉树的前序遍历序列创建二叉树

    //对二叉树进行线索化(根据中序遍历序列创建线索)
    ThreadTree.CreateThreadInBTreeAccordIO();

    //对二叉树进行线索化(根据前序遍历序列创建线索)
    //ThreadTree.CreateThreadInBTreeAccordPO();

    //对二叉树进行线索化(根据后序遍历序列创建线索)
    //ThreadTree.CreateThreadInBTreeAccordPOSTO();

#if 1
    ThreadTree.inOrder_Org();           //传统中序递归遍历
    cout << endl;

    ThreadTree.inOrder_IO();            //线索中序遍历
    cout << endl;

    ThreadTree.revInOrder_IO();         //线索中序逆向遍历
    cout << endl;
    int val = 'B';
    BinaryTreeNode<int>* p = ThreadTree.SearchElem_IO(val);
    if (p != nullptr)
    {
        cout << "找到了值为 " << (char)val << " 的节点" << endl;
        //顺便找一下后继和前驱节点
        BinaryTreeNode<int>* nx = ThreadTree.GetNextPoint_IO(p);
        if (nx != nullptr)
        {
            cout << "找到B后面的那个节点的值为: " << (char)nx->data << endl;
        }

        BinaryTreeNode<int>* pr = ThreadTree.GetPriorPoint_IO(p);
        if (pr != nullptr)
        {
            cout << "找到B前面那个节点的值为: " << (char)pr->data << endl;
        }
    }
    else
    {
        cout << "没找到值为 " << (char)val << " 的节点" << endl;
    }
#endif

#if 0    //若用前序线索化查找,线索化也得是前序的
    int var = 'B';
    BinaryTreeNode<int>* pp = ThreadTree.SearchElem_PO(var);
    if (pp != nullptr)
    {
        cout << "找到了值为 " << (char)var << " 的节点" << endl;
    }
#endif
    return 0;
}

注意

//每种线索化,都对应该线索化的操作,比如采用中序线索化,就只能使用中序线索的各种操作
//中序线索后,则不能使用前序线索或者后续线索的操作

  

 

  

 

posted @ 2022-08-03 10:44  huahuati  阅读(49)  评论(0)    收藏  举报