数据结构05:树与二叉树[C++][线索二叉树:先序、后序]

 数据结构05:树与二叉树[C++][线索二叉树:先序、后序]_第1张图片

 图源:文心一言

本篇博文含{先序线索化的代码后序线索化的代码},由于模板字数限制,中序线索化的代码及线索化的原理简介在上一篇博文~

数据结构05:树与二叉树[C++][线索二叉树:中序]_梅头脑_的博客-CSDN博客

  • 第1版:查资料、写BUG、画导图、画配图~
  • 第2版:改掉了后序的bug,这回后序可以跑了~
  • 第3版:改代码、改说明、改配图~
    • 发现线索化代码写在main函数里太奇怪了,所以改到了类里,相关代码与注释进行调整~
    • 对于不够简洁与可能产生歧义的内容作了调整 {甚至重新画了配图}~
    • 增加中序线索化的逆向遍历{在前一篇博文中哦}~ 

参考用书:王道考研《2024年 数据结构考研复习指导》

参考用书配套视频:5.3_4_线索二叉树的概念_哔哩哔哩_bilibili

特别感谢: Chat GPT老师、BING AI老师、文心一言老师~


目录

目录

思维导图

⌨️代码实现

先序线索二叉树

 P0~P3:同中序二叉树

 P5:头结点线索化

 P6:二叉树遍历

 P8:完整代码

 P9:运行结果

后序线索三叉树

后序线索二叉树无法求后序后继

 P0:调用库文件

 P1:定义结点与指针

 P2:封装创建结点

 P3:创建传统二叉树(三叉链表)

 P4:链表线索化

 P5:线索化头结点

 P6:二叉树遍历

 P7:调用函数

 P8:完整代码

 P9:执行结果

结语


思维导图

  • 本篇仅涉及到线索二叉树:先序、后序的代码;
  • 思维导图为整理王道教材第5章 树与二叉树的所有内容,其余学习笔记在以下博客~
    • 数据结构05:树与二叉树[C++][二叉树:先序、中序、后序遍历]
    • 数据结构05:树与二叉树[C++][哈夫曼树HuffmanTree]
    • 数据结构05:树与二叉树[C++][并查集]
    • 数据结构05:树与二叉树[C++][线索二叉树:中序]

⌨️代码实现

先序线索二叉树

 P0~P3:同中序二叉树

此处不再重述调用库、结点定义、增加结点、创建树的功能~

 P4:二叉链表线索化

此处我们仅线索化不含头结点的部分,引用指针p、pre完成线索化~

  • 指针p:从根结点开始,负责指向线性表的前驱结点;
  • 指针pre:从nullptr开始,负责指向线性表的后继结点;
  • 传统先序遍历的顺序是根、左、右~
  • 线索化二叉树,且如果想增加头结点,就需要遍历二叉树找到首尾结点~
  • 判断指针p是否为空,如果为空则退出递归,不为空则继续执行~
    • p指针左子树遍历完成,访问至空指针时,表示该指针可以线索化,p的左子树指针指向前驱结点pre,ltag根据规则置1,完成结点的前驱线索化
    • 前驱结点pre是否存在且右子树为空,将pre的右子树指针指向后继节点p,ltag根据规则置1,完成结点的后继线索化
    • 更新前驱结点pre为当前p指针,为下一次线索化做准备;
    • p指针遍历树的左孩子,访问非空结点时,表示该指针位无需线索化,递归调用本函数;   //步骤同传统二叉树中序遍历
    • p指针遍历树的右孩子,通过非空结点时,表示该指针位无需线索化,ltag置0,递归调用本函数。 //步骤同传统二叉树遍历
    void PreThread(ThreadTree& p, ThreadTree& pre) {
        if (p != NULL) {
            if (p->lchild == NULL) {        // 若左子树为空,完成前驱线索化
                p->lchild = pre;
                p->ltag = 1;
            }
            if (pre != NULL && pre->rchild == NULL) {    //若右子树为空,则完成后继线索化
                pre->rchild = p;
                pre->rtag = 1;
            }
            pre = p;

            if (p->ltag == 0) {              // 若左子树不为空,向左遍历
                PreThread(p->lchild, pre);
            }
            if (p->rtag == 0) {              // 若右子树不为空,向右遍历
                PreThread(p->rchild, pre);
            }
        }
    }

数据结构05:树与二叉树[C++][线索二叉树:先序、后序]_第2张图片

 P5:头结点线索化

备注:此处若无线索化逆向遍历的需求,则头结点不是必须的,反而有头结点增加了代码整体的难度 {头结点和尾结点F的链表相互循环} ~因此小伙伴可以根据个人需要删减本部分代码~

此处我们头结点的处理包含两个内容:

  • 创建头结点时的初始化:左指针指向root结点,右线索指向自己;
  • 完成线索化,头结点的指针与标志域赋值:尾结点的右线索指向头结点、头结点的右线索指向尾结点~

数据结构05:树与二叉树[C++][线索二叉树:先序、后序]_第3张图片

    //头结点初始化
    void InitNode() {
        head = new ThreadNode();
        head->lchild = root;
        head->ltag = 0;
        head->rchild = head;
        head->rtag = 1;
    }

    //处理头结点线索
    void HandleHeadNode(ThreadNode*& head, ThreadNode* lastNode) {
          if (lastNode != nullptr && lastNode->rchild == nullptr) {    //尾结点右线索指向头结点
            lastNode->rchild = head;
            lastNode->rtag = 1;
          }
        //if (head->rchild == nullptr) {    // 头结点右线索指向尾结点
            head->rchild = lastNode;
            head->rtag = 1;
        //}
    }

 P6:二叉树遍历

核心思想:找到遍历起始结点,而后有线索则找线索、没线索则找孩子结点~

  • 若树非空,继续执行以下语句;
  • 将p指向头结点的位置head,其左孩子就是根节点,即为先序遍历的首结点;
    • p指针未循环回头结点时,执行以下语句,
      • 输出p指针指向当前结点的值;
      • 如果p指针指向当前结点的左子树是结点,访问左子树
      • 如果p指针指向当前结点的左子树不是结点,则执行以下语句:
        • 如果p指针指向当前结点的右子树是线索,则循环通过右线索找到后继结点;
        • 如果p指针指向当前结点的右子树是结点,则通过右孩子指针找到后继结点。

 运行起来应该是这样的~

  • P指针的路径为头结点,寻找左孩子结点A,打印结点A
  • 结点A具有左孩子结点B,打印结点B
  • 结点B具有左孩子结点C,打印结点C
  • 结点C的右线索指向结点D,打印结点D
  • 结点D的右线索指向结点F,打印结点F
  • 结点F的线索指向头结点,循环判定失败,退出循环。
    void PreThreadOrder() {
        if (head == NULL) {
            std::cout << "树为空!" << std::endl;
            return;
        }

        std::cout << "线索二叉树先序遍历:";

        ThreadTree p = head->lchild;        // 寻找首结点
        while (p != head) {                 // 若{p不为头结点},继续向后遍历
            std::cout << p->data << " ";    // 打印结点

            if (p->ltag == 0) {             // 若{左子树存在结点}
                p = p->lchild;              // 向左孩子遍历
            } else {                        // 若{左子树不存在结点}
                while (p != head && p->rtag == 1) {    // 若{右子树存在线索}
                    p = p->rchild;                     // 向右线索遍历
                    std::cout << p->data << " ";       // 打印结点
                }
                p = p->rchild;              // {右子树存在孩子},向右孩子遍历
            }

            if (p->rchild == root || p->rchild == head){    // 尾结点与头结点可能会循环遍历,因此增加判定
                break;
            }
        }
        std::cout << std::endl;
    }

注意: 头结点与尾结点F存在链表循环的现象,因此单独增加了判定{if (p->rchild == root || p->rchild == head)},也就是如果没有头结点的话完全可以删掉这行判定~

数据结构05:树与二叉树[C++][线索二叉树:先序、后序]_第4张图片

  P7:调用函数 

作用就是个伪main函数,创建了pre指针,并且执行了上述功能~

不喜欢这么写的话,代码保留到处理头结点{handleheadnode}这一行,把遍历放在真main函数里也是可以的~

​    void ThreadTreeDemo() {
        CreateTree();

        // 进行线索化
        ThreadNode* pre = nullptr;
        InThread(root, pre);

        // 创建头结点并进行线索化
        InitNode();
        HandleHeadNode(head, pre);

        // 后序遍历
        PreThreadOrder();
        std::cout << std::endl;
    }

​

 P8:完整代码

​
#include 
#include 

// 定义结点
typedef struct ThreadNode {
    char data;
    struct ThreadNode* lchild, * rchild;
    int ltag, rtag;
} ThreadNode, * ThreadTree;

// 类:线索化
class ThTree {
// 私有类成员:根结点、头结点
private:
    ThreadNode* root;
    ThreadNode* head;

// 公共类
public:
    // 成员初始化
    ThTree() {
        root = nullptr;
        head = nullptr;
    }
    
    // 创建节点
    ThreadNode* CreateNode(char data) {
        ThreadNode* node = new ThreadNode();
        node->data = data;
        node->lchild = nullptr;
        node->rchild = nullptr;
        node->ltag = 0;
        node->rtag = 0;
        return node;
    }

    // 构建传统二叉树1
    void CreateTree() {
        root = CreateNode('A');
        root->lchild = CreateNode('B');
        root->rchild = CreateNode('F');

        root->lchild->lchild = CreateNode('C');
        root->lchild->rchild = CreateNode('D');
    }

    /* 构建传统二叉树2
    void CreateTree() {
        char rootData;
        std::cout << "请输入根节点的数据: ";
        std::cin >> rootData;

        root = CreateNode(rootData);
        std::queue nodeQueue;
        nodeQueue.push(root);

        while (!nodeQueue.empty()) {
            ThreadNode* currentNode = nodeQueue.front();
            nodeQueue.pop();

            int relation;
            std::cout << "请选择节点 " << currentNode->data << " 的孩子结点个数 (1-双孩子结点, 2-左孩子结点, 3-右孩子结点, 4-空孩子结点): ";
            std::cin >> relation;

            switch (relation) {
                case 1: {
                    char lchildData, rchildData;
                    std::cout << "请输入左孩子结点的数据: ";
                    std::cin >> lchildData;
                    std::cout << "请输入右孩子结点的数据: ";
                    std::cin >> rchildData;

                    ThreadNode* lchildNode = CreateNode(lchildData);
                    currentNode->lchild = lchildNode;
                    nodeQueue.push(lchildNode);

                    ThreadNode* rchildNode = CreateNode(rchildData);
                    currentNode->rchild = rchildNode;
                    nodeQueue.push(rchildNode);
                    break;
                }
                case 2: {
                    char lchildData;
                    std::cout << "请输入左孩子结点的数据: ";
                    std::cin >> lchildData;

                    ThreadNode* lchildNode = CreateNode(lchildData);
                    currentNode->lchild = lchildNode;
                    nodeQueue.push(lchildNode);
                    break;
                }
                case 3: {
                    char rchildData;
                    std::cout << "请输入右孩子结点的数据: ";
                    std::cin >> rchildData;

                    ThreadNode* rchildNode = CreateNode(rchildData);
                    currentNode->rchild = rchildNode;
                    nodeQueue.push(rchildNode);
                    break;
                }
                case 4:
                    // Do nothing for empty child node
                    break;
                default:
                    std::cout << "无效的选择,请重新输入。\n";
                    continue;
            }
        }
    }
    */
    
    // 头结点初始化
    void InitNode() {
        head = new ThreadNode();
        head->lchild = root;
        head->ltag = 0;
        head->rchild = head;
        head->rtag = 1;
    }

    // 头结点线索化
    void HandleHeadNode(ThreadNode*& head, ThreadNode* lastNode) {
    //  if (lastNode != NULL && lastNode->rchild == NULL) {
            lastNode->rchild = head;
            lastNode->rtag = 1;
    //  }
    //  if (head->rchild == NULL) {
            head->rchild = lastNode;
            head->rtag = 1;
    //  }
    }


    // 中序线索化
    void PreThread(ThreadTree& p, ThreadTree& pre) {
        if (p != NULL) {
            if (p->lchild == NULL) {
                p->lchild = pre;
                p->ltag = 1;
            }
            if (pre != NULL && pre->rchild == NULL) {
                pre->rchild = p;
                pre->rtag = 1;
            }
            pre = p;

            if (p->ltag == 0) {
                PreThread(p->lchild, pre);
            }
            if (p->rtag == 0) {
                PreThread(p->rchild, pre);
            }
        }
    }

    // 先序遍历
    void PreThreadOrder() {
        if (head == NULL) {
            std::cout << "树为空!" << std::endl;
            return;
        }

        std::cout << "线索二叉树先序遍历:";

        ThreadTree p = head->lchild;
        while (p != head) {
            std::cout << p->data << " ";

            if (p->ltag == 0) {
                p = p->lchild;
            } else {
                while (p != head && p->rtag == 1) {
                    p = p->rchild;
                    std::cout << p->data << " ";
                }
                p = p->rchild;
            }

            if (p->rchild == root || p->rchild == head){
                break;
            }
        }
        std::cout << std::endl;
    }

    // 执行Demo
    void ThreadTreeDemo() {
        CreateTree();

        ThreadNode* pre = nullptr;
        InThread(root, pre);

        InitNode();
        HandleHeadNode(head, pre);

        PreThreadOrder();
        std::cout << std::endl;

    }
};

int main() {
    ThTree* tree = new ThTree();
    tree->ThreadTreeDemo();

    delete tree;

    return 0;
}

 P9:运行结果

树1运行结果{代码默认已存小树}:

数据结构05:树与二叉树[C++][线索二叉树:先序、后序]_第5张图片

树2运行结果{手动构建小树}:

后序线索三叉树

后序线索二叉树无法求后序后继

根据前述代码及构图,我们知道,先序、中序二叉树可以直接求后序后继:中序二叉树既可以从左向右查询、也可以从右向左查询;先序二叉树可以从上向下查询~

但是后序二叉树这种从下到上就没这么幸运了,我们以图为栗~

  • 看向最右下角的后序线索二叉树,后序遍历一路向左,找到结点C为起点;
  • 结点C是叶子结点,且是左子树,可以通过线索找到结点D;
  • 结点D是叶子结点,且是右子树,可以通过线索找到结点B;
  • 结点B非叶子结点,且是左子树,不能通过线索找到结点F;
  • 结点F非叶子结点,且是右子树,可以通过线索找到结点A;
  • 结点A是根结点,下一个结点是头结点,因此可以结束循环。

出现问题的地方只有B作为非叶子结点,且是左子树时,找不到兄弟结点(如果兄弟结点存在),因此我们引入双亲指针,使结点B通过父节点的孩子结点找到结点F~

话说,万一真考这个也太点背了,不过为了保证博文的完整性还是贴在了这里...

 P0:调用库文件

  • 输入输出流文件iostream:实现输出文字的效果;
  • 辅助队列queue:非必须,仅在有手动创建树的需求时使用,详见下文函数Create Tree;
#include 
#include 

 P1:定义结点与指针

此处增加双亲指针*parent~

typedef struct ThreadNode {
    char data;
    struct ThreadNode* lchild, * rchild, * parent;  // 添加parent指针
    int ltag, rtag;
} ThreadNode, * ThreadTree;

 P2:封装创建结点

  • CreateNode普通结点:赋值data,并将parent指向父结点~
  • CreateRoot根结点:赋值data,但parent指向nullptr,不能作为普通的参数传入,因此写了两个函数~
    ThreadNode* CreateNode(char data,ThreadNode*& parent) {
        ThreadNode* node = new ThreadNode();
        node->data = data;
        node->lchild = nullptr;
        node->rchild = nullptr;
        node->parent = parent;
        node->ltag = 0;
        node->rtag = 0;
        return node;
    }

    ThreadNode* CreateRoot(char data, std::nullptr_t nullp) {
        ThreadNode* newNode = new ThreadNode();
        newNode->data = data;
        newNode->lchild = nullptr;
        newNode->rchild = nullptr;
        newNode->parent = nullptr;
        newNode->ltag = 0;
        newNode->rtag = 0;
        return newNode;
    }

话说,代码有问题询问BING AI老师时,她真的有一点凶;虽然学习的道路有点坎坷,不过最后她还是把我教会了...

BING AI老师真的怀疑我有没有认真听讲... 

 P3:创建传统二叉树(三叉链表)

 创建树这里给出两个选择,二选一即可~

  • 选择一:习惯于C++在线测试代码,或是只想看运行结果的小伙伴,直接粘下面这一段就好了,包含一棵简单小树~在线运行C++(GCC 7.4.0) (json.cn)
    void CreateTree() {
        root = CreateRoot('A',nullptr);
        root->lchild = CreateNode('B', root);
        root->rchild = CreateNode('F', root);

        root->lchild->lchild = CreateNode('C',root->lchild);
        root->lchild->rchild = CreateNode('D',root->lchild);
    }
  • 选择二:需要在命令行自己手动搭建小树的小伙伴,粘下面这一段代码,这段代码使用队列构建树,原理与中序二叉树遍历构建是完全类似的~  
  • 令用户键入的根结点,创建辅助队列中,根节点入队;
  • 辅助队列不为空时,循环执行以下操作:
    • 队首结点出队并记录,这个结点就是当前结点;
    • 二叉树孩子结点的有顺序,不能颠倒,因此采用switch区分4种情况:
      • case-1:具两个孩子结点,令用户键入2个孩子结点,分别初始化,链入当前结点,并加入辅助队列;
      • case-2:仅有左孩子节点,令用户键入1个左孩子结点,初始化,链入当前结点,并加入辅助队列;
      • case-3:仅有右孩子节点,令用户键入1个右孩子结点,初始化,链入当前结点,并加入辅助队列;
      • case-4:没有孩子结点,跳过分支,执行下一个循环
    void CreateTree() {
        char rootData;
        std::cout << "请输入根节点的数据: ";
        std::cin >> rootData;

        root = CreateRoot(rootData, nullptr);
        std::queue nodeQueue;
        nodeQueue.push(root);

        while (!nodeQueue.empty()) {
            ThreadNode* currentNode = nodeQueue.front();
            nodeQueue.pop();

            int relation;
            std::cout << "请选择节点 " << currentNode->data << " 的孩子结点个数 (1-双孩子结点, 2-左孩子结点, 3-右孩子结点, 4-空孩子结点): ";
            std::cin >> relation;

            switch (relation) {
                case 1: {
                    char lchildData, rchildData;
                    std::cout << "请输入左孩子结点的数据: ";
                    std::cin >> lchildData;
                    std::cout << "请输入右孩子结点的数据: ";
                    std::cin >> rchildData;

                    ThreadNode* lchildNode = CreateNode(lchildData,currentNode);
                    currentNode->lchild = lchildNode;
                    nodeQueue.push(lchildNode);  // 将左孩子节点加入队列

                    ThreadNode* rchildNode = CreateNode(rchildData,currentNode);
                    currentNode->rchild = rchildNode;
                    nodeQueue.push(rchildNode);  // 将右孩子节点加入队列
                    break;
                }
                case 2: {
                    char lchildData;
                    std::cout << "请输入左孩子结点的数据: ";
                    std::cin >> lchildData;

                    ThreadNode* lchildNode = CreateNode(lchildData,currentNode);
                    currentNode->lchild = lchildNode;
                    nodeQueue.push(lchildNode);  // 将左孩子节点加入队列
                    break;
                }
                case 3: {
                    char rchildData;
                    std::cout << "请输入右孩子结点的数据: ";
                    std::cin >> rchildData;

                    ThreadNode* rchildNode = CreateNode(rchildData,currentNode);
                    currentNode->rchild = rchildNode;
                    nodeQueue.push(rchildNode);  // 将右孩子节点加入队列
                    break;
                }
                case 4:
                    // Do nothing for empty child node
                    break;
                default:
                    std::cout << "无效的选择,请重新输入。\n";
                    continue;
            }
        }
    }

 P4:链表线索化

此处我们仅线索化不含头结点的部分,引用指针p、pre完成线索化~

  • 指针p:从根结点开始,负责指向线性表的前驱结点;
  • 指针pre:从nullptr开始,负责指向线性表的后继结点;
  • 传统后序遍历的顺序是左子树、右子树、根结点~
  • 原理十分雷同于前、中遍历,算法还是在传统遍历的基础上增加线索化过程,此处不再赘述~
    void PostThread(ThreadTree& p, ThreadTree& pre) {
        if (p != nullptr) {
            PostThread(p->lchild, pre);
            PostThread(p->rchild, pre);

            if (p->lchild == nullptr) {
                p->lchild = pre;
                p->ltag = 1;
            }
            if (pre != nullptr && pre->rchild == nullptr) {
                pre->rchild = p;
                pre->rtag = 1;
            }

            pre = p;
        }
    }

 线索化完成后大概就是下面的样子~

数据结构05:树与二叉树[C++][线索二叉树:先序、后序]_第6张图片

 P5:线索化头结点

备注:此处若无线索化逆向遍历的需求,则头结点不是必须的,反而有头结点增加了代码整体的难度 {头结点和尾结点F的链表相互循环} ~因此小伙伴可以根据个人需要删减本部分代码~

此处我们头结点的处理包含两个内容:

  • 创建头结点时的初始化:左指针指向root结点,右线索指向自己;
  • 完成线索化,头结点的指针与标志域赋值:首结点的左指针指向头结点~

数据结构05:树与二叉树[C++][线索二叉树:先序、后序]_第7张图片

    void HandleHeadNode(ThreadNode*& head, ThreadNode* lastNode) {
        if (lastNode != NULL && lastNode->rchild == NULL) {    // 尾结点右线索指向头结点
            lastNode->rchild = head;
            lastNode->rtag = 1;
        }
    //  if (head->rchild == NULL) {    // 头结点右线索指向尾结点
            head->rchild = lastNode;
            head->rtag = 1;
    //  }
    }

注意:if (lastNode != NULL && lastNode->rchild == NULL)”这句判定不能丢掉,否则根结点A就会将指向右节点F的指针指向头结点~

 P6:二叉树遍历

代码首先从首结点开始,每一轮都会令指针P走向当前结点的父节点,然后遍历父节点的右子树,具体如下~

  • 若树非空,继续执行以下语句;
  • 设定p指针指向头结点的位置,root指针为根结点,初始置空;
  • 遍历p指针的最左侧,即为后序遍历开始的位置;
  • 如果p指针不为空时开始循环;
    • 如果p指针的右线索存在,且右线索不指向rootp指针根据右线索寻找后继结点;
    • 如果p指针的右线索不在:结合前述判定,这是父结点具有右子树的结点;
      • p指针移动到父节点;
      • 如果p指针指向的结点具有右子树,p指针移动右孩子结点的位置;
        • 如果p指针指向的结点具有左子树,执行循环访问右子树最左侧,即该右子树后序遍历开始的位置~
      • 打印p指针指向的结点~
    void PostThreadOrder() {
        if (head == NULL) {
            std::cout << "树为空!" << std::endl;
            return;
        }

        std::cout << "线索二叉树后序遍历:";
        ThreadNode* p = head->lchild;
        while (p->lchild != nullptr && p->ltag == 0) {  // 寻找第一个被线索化的节点(最左边的节点)
            p = p->lchild;
        }

        while (p->rchild != root) {
            std::cout << p->data << " ";  // 输出节点的值

            if (p->rtag == 1 && p->rchild != root) {  // 如果节点的右指针是线索,直接转到后继节点
                p = p->rchild;
            } else {  // 否则,节点的右孩子是已经遍历的孩子结点,因此先退回到该节点的父结点
                if(p != root){
                    p = p->parent;
                }

                if (p->rtag == 0 && p->rchild != root) { // 该结点具有右孩子,则访问右孩子
                    p = p->rchild;
                        while (p->ltag == 0 && p->lchild != nullptr) {  // 找到右子树最左侧的结点
                             p = p->lchild;
                       }
                std::cout << p->data << " ";  // 输出结点的值
                }
            }
        }
        std::cout << p->rchild->data << " ";  // 输出根结点的值
    }

注意:“while (p->rchild != root)”,而不是“while (p != head)”因为在结点A与结点F之间有可能打循环,因此修改了判定~

 根据上图,运行起来应该是这样的~

  • P指针的路径一路向左,结点A、结点B、结点C,结点C设为起始遍历结点;
  • 打印结点C
  • 结点C具有右线索,顺着线索找到结点D,打印结点D
  • 结点D具有右线索,顺着线索找到结点B,打印结点B
  • 结点B没有右线索,需要访问其父节点,如果有父结点有右子树,找到右子树左侧的结点F,打印结点F
  • 结点F具有右线索,顺着线索找到结点A,打印结点A
  • 结点A的线索指向头结点,循环判定失败,退出循环。

 P7:调用函数

作用就是个伪main函数,创建了pre指针,并且执行了上述功能~

不喜欢这么写的话,代码保留到处理头结点{handleheadnode}这一行,把遍历放在真main函数里也是可以的~

​
    void ThreadTreeDemo() {
        CreateTree();

        // 进行线索化
        ThreadNode* pre = nullptr;
        InThread(root, pre);

        // 创建头结点并进行线索化
        InitNode();
        HandleHeadNode(head, pre);

        // 后序遍历
        PostThreadOrder();
        std::cout << std::endl;
    }

​

 P8:完整代码

上次代码的错误是树最后的指针赋值有误,以及判断条件有误,这次手工改掉,应该终于可以跑了,开心~这次把线索化都扔进了类里,应该可以加强一点实用性~

​#include 
#include 

// 定义结点
typedef struct ThreadNode {
    char data;
    struct ThreadNode* lchild, * rchild, * parent;
    int ltag, rtag;
} ThreadNode, * ThreadTree;

// 类:线索化
class ThTree {
// 私有类成员:根结点、头结点
private:
    ThreadNode* root;
    ThreadNode* head;

// 公共类
public:
    // 成员初始化
    ThTree() {
        root = nullptr;
        head = nullptr;
    }
    
    // 创建节点
    ThreadNode* CreateNode(char data,ThreadNode*& parent) {
        ThreadNode* node = new ThreadNode();
        node->data = data;
        node->lchild = nullptr;
        node->rchild = nullptr;
        node->parent = parent;
        node->ltag = 0;
        node->rtag = 0;
        return node;
    }

    ThreadNode* CreateRoot(char data, std::nullptr_t nullp) {
        ThreadNode* newNode = new ThreadNode();
        newNode->data = data;
        newNode->lchild = nullptr;
        newNode->rchild = nullptr;
        newNode->parent = nullptr;
        newNode->ltag = 0;
        newNode->rtag = 0;
        return newNode;
    }

    // 构建传统二叉树1
    void CreateTree() {
        root = CreateRoot('A',nullptr);
        root->lchild = CreateNode('B', root);
        root->rchild = CreateNode('F', root);

        root->lchild->lchild = CreateNode('C',root->lchild);
        root->lchild->rchild = CreateNode('D',root->lchild);
    }

    /* 构建传统二叉树2
    void CreateTree() {
        char rootData;
        std::cout << "请输入根节点的数据: ";
        std::cin >> rootData;

        root = CreateRoot(rootData, nullptr);
        std::queue nodeQueue;
        nodeQueue.push(root);

        while (!nodeQueue.empty()) {
            ThreadNode* currentNode = nodeQueue.front();
            nodeQueue.pop();

            int relation;
            std::cout << "请选择节点 " << currentNode->data << " 的孩子结点个数 (1-双孩子结点, 2-左孩子结点, 3-右孩子结点, 4-空孩子结点): ";
            std::cin >> relation;

            switch (relation) {
                case 1: {
                    char lchildData, rchildData;
                    std::cout << "请输入左孩子结点的数据: ";
                    std::cin >> lchildData;
                    std::cout << "请输入右孩子结点的数据: ";
                    std::cin >> rchildData;

                    ThreadNode* lchildNode = CreateNode(lchildData,currentNode);
                    currentNode->lchild = lchildNode;
                    nodeQueue.push(lchildNode);  // 将左孩子节点加入队列

                    ThreadNode* rchildNode = CreateNode(rchildData,currentNode);
                    currentNode->rchild = rchildNode;
                    nodeQueue.push(rchildNode);  // 将右孩子节点加入队列
                    break;
                }
                case 2: {
                    char lchildData;
                    std::cout << "请输入左孩子结点的数据: ";
                    std::cin >> lchildData;

                    ThreadNode* lchildNode = CreateNode(lchildData,currentNode);
                    currentNode->lchild = lchildNode;
                    nodeQueue.push(lchildNode);  // 将左孩子节点加入队列
                    break;
                }
                case 3: {
                    char rchildData;
                    std::cout << "请输入右孩子结点的数据: ";
                    std::cin >> rchildData;

                    ThreadNode* rchildNode = CreateNode(rchildData,currentNode);
                    currentNode->rchild = rchildNode;
                    nodeQueue.push(rchildNode);  // 将右孩子节点加入队列
                    break;
                }
                case 4:
                    // Do nothing for empty child node
                    break;
                default:
                    std::cout << "无效的选择,请重新输入。\n";
                    continue;
            }
        }
    }
    */

    // 头结点初始化
    void InitNode() {
        head = new ThreadNode();
        head->lchild = root;
        head->ltag = 0;
        head->rchild = head;
        head->rtag = 1;
        head->parent = nullptr;
    }
    
    // 头结点线索化
    void HandleHeadNode(ThreadNode*& head, ThreadNode* lastNode) {
        if (lastNode != NULL && lastNode->rchild == NULL) {
            lastNode->rchild = head;
            lastNode->rtag = 1;
        }
    //  if (head->rchild == NULL) {
            head->rchild = lastNode;
            head->rtag = 1;
    //  }
    }


    // 后序线索化
    void PostThread(ThreadTree& p, ThreadTree& pre) {
        if (p != nullptr) {
            PostThread(p->lchild, pre);
            PostThread(p->rchild, pre);

            if (p->lchild == nullptr) {
                p->lchild = pre;
                p->ltag = 1;
            }
            if (pre != nullptr && pre->rchild == nullptr) {
                pre->rchild = p;
                pre->rtag = 1;
            }

            pre = p;
        }
    }

    // 后序遍历
    void PostThreadOrder() {
        if (head == NULL) {
            std::cout << "树为空!" << std::endl;
            return;
        }

        std::cout << "线索二叉树后序遍历:";
        ThreadNode* p = head->lchild;
        while (p->lchild != nullptr && p->ltag == 0) {
            p = p->lchild;
        }

        while (p->rchild != root) {
            std::cout << p->data << " ";

            if (p->rtag == 1 && p->rchild != root) {
                p = p->rchild;
            } else {
                if(p != root){
                    p = p->parent;
                }

                if (p->rtag == 0 && p->rchild != root) { 
                    p = p->rchild;
                        while (p->ltag == 0 && p->lchild != nullptr) {
                             p = p->lchild;
                       }
                std::cout << p->data << " ";
                }
            }
        }
        std::cout << p->rchild->data << " ";
    }

    // 执行Demo
    void ThreadTreeDemo() {
        CreateTree();

        ThreadNode* pre = nullptr;
        InThread(root, pre);

        InitNode();
        HandleHeadNode(head, pre);

        PostThreadOrder();
        std::cout << std::endl;

    }
};

int main() {
    ThTree* tree = new ThTree();
    tree->ThreadTreeDemo();

    delete tree;

    return 0;
}

​

 P9:执行结果

树1{代码已有小树}:

数据结构05:树与二叉树[C++][线索二叉树:先序、后序]_第8张图片

树2{可自定义小树}:

数据结构05:树与二叉树[C++][线索二叉树:先序、后序]_第9张图片


结语

博文到此结束,写得模糊或者有误之处,欢迎小伙伴留言讨论与批评,督促博主优化内容{例如有错误、难理解、不简洁、缺功能}等~‍️

博文若有帮助,欢迎小伙伴动动可爱的小手默默给个赞支持一下,收到点赞的话,博主肝文的动力++~

你可能感兴趣的:(#,数据结构,数据结构,c++,考研)