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

news2024/11/16 17:49:36

 

 图源:文心一言

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

数据结构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);
            }
        }
    }

 🔯P5:头结点线索化

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

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

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

    //头结点初始化
    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)},也就是如果没有头结点的话完全可以删掉这行判定~

  🔯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 <iostream>
#include <queue>

// 定义结点
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<ThreadNode*> 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运行结果{代码默认已存小树}:

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

🧵后序线索三叉树

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

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

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

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

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

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

 🔯P0:调用库文件

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

 🔯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<ThreadNode*> 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;
        }
    }

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

 🔯P5:线索化头结点

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

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

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

    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 <iostream>
#include <queue>

// 定义结点
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<ThreadNode*> 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{代码已有小树}:

树2{可自定义小树}:


🔚结语

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/757330.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux 系统编程-开发环境(一)

目录 1 shell 1.1 shell 家族 1.2 bash 1.3 命令和路径补齐 1.4 历史记录 1.5 主键盘快捷键 1.6 演示 2 目录和文件 2.1 类Unix系统目录结构 2.2 用户目录 2.2.1 相对路径和绝对路径 2.3 ls 2.4 cd 2.5 which 2.6 pwd 2.7 mkdir 2.8 rmdir 2.9 touch 2.10…

在AndroidStudio中开发系统APP

1.AndroidStudio项目中调用系统API AndroidStudio项目中调用系统API&#xff08;比如调用 UnsupportedAppUsage 的方法&#xff09;&#xff0c;需要引入系统framework.jar包。 第一步如下图&#xff0c;fremework.jar 放在app/systemjar/目录下 第二步&#xff0c;在app下的…

Win10点击任务栏搜索、日历无响应

现象描述 点击Win10任务搜索栏和日历均无响应 解决方法 1、无响应应该是程序发生了异常&#xff0c;通过Windows日志产看器发现是KERNELBASE.dll模块发生了0x88985004异常。 2&#xff0c;查看错误代码含义 3&#xff0c;在微软社区查看此类问题&#xff0c;重点关注与字…

RocketMQ快速使用基础

1. RocketMQ 介绍 RocketMQ作为一款纯java、分布式、队列模型的开源消息中间件&#xff0c;支持事务消息、顺序消息、批量消息、定时消息、消息回溯等 前身是MetaQ&#xff0c;是阿里研发的一个队列模型的消息中间件&#xff0c;后开源给apache基金会成为了apache的顶级开源项目…

基于SaaS模式的Java基层卫生健康云HIS系统源码【运维管理+运营管理+综合监管】

云HIS综合管理平台 一、模板管理 模板分为两种&#xff1a;病历模板和报表模板。模板管理是运营管理的核心组成部分&#xff0c;是基层卫生健康云中各医疗机构定制电子病历和报表的地方&#xff0c;各医疗机构可根据自身特点特色定制电子病历和报表&#xff0c;制作的电子病历…

【Kubernetes运维篇】RBAC之创建集群用户管理K8S

文章目录 一、创建zhangsan集群用户赋予uat名称空间管理员权限二、创建lisi集群用户赋予查看所有名称Pod权限 需求&#xff1a;公司新入职两位运维同事&#xff0c;分别是zhangsan、lisi&#xff0c;刚入职肯定不能给K8S管理员权限&#xff0c;所以需要创建两个系统账号&#x…

【电路原理学习笔记】第3章:欧姆定律:3.2 电流的计算

第3章&#xff1a;欧姆定律 3.2 电流的计算 电流相关欧姆定律公式&#xff1a; I V R I\frac{V}{R} IRV​ 【例3-3】图3-6所示电路中有多少安培的电流&#xff1f; 【解】 I V R 100 V 220 Ω 0.455 A I\frac{V}{R}\frac{100\rm V}{220\rm Ω}0.455\rm A IRV​220Ω100V…

常用API学习02(Java)

Object 在java中,Object是类层次中的根类&#xff0c;即Object是所有类的父类-任何类都是 object的子类。任何一个类的对象都可以使用object来声明。 类<?> getClass() 返回此object的运行 int hashCode() 返回对象的哈希码 protected Object clone() 创…

Day53| 1143.最长公共子序列、1035.不相交的线 、 53. 最大子序和 动态规划

1143.最长公共子序列 1.题目&#xff1a; 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在不改变字符的相对顺…

SEED实验复现

SEED 项目由雪城大学教授杜文亮于 2002 年启动雪城大学。它由美国总共1万美元资助 美国国家科学基金会。现在&#xff0c;SEED 实验室正在被超过 全球数千个研究所。SEED 代表 &#xff08;SEcurity EDucaton&#xff09;。 https://github.com/seed-labs/seed-labs 该项目使用…

深度学习实战42-基于大模型开发MathGPT的原理介绍,让数学问题智能解答变为可能

大家好,我是微学AI,今天给大家介绍一下深度学习实战42-基于大模型开发MathGPT的原理介绍,让数学问题智能解答变为可能。在去年ChatGPT的发布后,各种国内外的大语言模型层出不穷涌现,但是大家都知道现在的模型的诟病的数学能力不足,就算是简单的数学题都可能算错,今天我就…

spring boot面向切面编程aop

一、什么是AOP AOP&#xff0c;Aspect Oriented Programming&#xff0c;面向切面编程 举个例子来理解 如果我们的业务需要额外做三件事情&#xff0c;判断是否已经登录&#xff0c;记录日志&#xff0c;统计业务执行时长 传统的做法是这样子的&#xff1a; 而apo的实现是这…

vue2的数据数据双向绑定

遍历data里面的属性&#xff0c;通过Object.defineProperty的set方法监听属性值的变化&#xff0c;最后通知视图更新 先问自己两个问题&#xff1a; 1.app.message修改数据的时候&#xff0c;Vue内部是如何监听message数据发生改变的 使用Object.defineProperty ->监听对…

概率论的学习和整理17:EXCEL里直接对应的分布公式计算概率(未完成)

1EXCEL计算这些特殊分布的方差 1.1 用原始的概率&#xff0c;期望和方差的方法 虽然计算概率&#xff0c;需要用对应分布的公式P(xn) 想了解的随机变量是总次数n&#xff0c;需要对应几何分布&#xff0c;负二项分布P(xk) 想了解的随机变量是成功次数k&#xff0c;需要对应超几…

算法竞赛,机器学习,深度学习ai学习方向如何规划,搭建环境等答疑

目录 1了解人工智能的背景知识 2 补充数学或编程知识 3 熟悉机器学习工具库 4 系统的学习人工智能 5 动手去做一些AI应用 1了解人工智能的背景知识 本文可以让你避免踩坑走弯路&#xff0c;一些虽然存在但是在研究或者工业上不常用的知识&#xff0c;为自己腾出更多的时间…

Ubuntu软件包安装失败:代码 bionic 和 focal的区别

问题 我在Ubuntu上使用apt安装软件时总是报一些错误&#xff0c;不是版本不对&#xff0c;就是依赖关系不对。尝试了各种方法&#xff0c;突然想到是不是软件源有问题。 查看/etc/apt/sources.list文件&#xff0c;发现使用了阿里云的软件源&#xff1a; deb http://mirrors…

FreeRTOS实时操作系统(十六)内存管理

系列文章 FreeRTOS实时操作系统&#xff08;一&#xff09;RTOS的基本概念 FreeRTOS实时操作系统&#xff08;二&#xff09;任务创建与任务删除&#xff08;HAL库&#xff09; FreeRTOS实时操作系统&#xff08;三&#xff09;任务挂起与恢复 FreeRTOS实时操作系统&#x…

Android调用google原生裁剪,兼容三方相册裁剪功能

Android调用google原生裁剪&#xff0c;兼容三方相册裁剪功能 效果图实现功能编写CropImage类继承 ActivityResultContract调用 效果图 实现功能 本篇文章裁剪功能实现兼容Android6&#xff0c;解决部分google手机&#xff08;有部分Android10的Google手机无法使用google自带裁…

“遇见0和1”小程序正式开源

开源地址 https://gitee.com/lingstudy/meet0and1-applets-share 纯云开发&#xff1a;微信小程序 —“遇见0和1”开源发布 关于小程序 小程序前端使用 ColorUI 组件库&#xff0c;并且参考了大佬“爱敲代码的猫” 的开源项目 WeHalo 的页面设计&#xff0c;后端使用小程序纯云…

stm32读写nand flash

文章目录 1.简介2.频率设置3.FSMC参数设置4.修改宏定义 NAND_DEVICE5.程序测试5.1.简单测试5.2.擦除、写入、读取测试 注意 1.简介 目前我在使用stm32f407ZGT6来读写三星的nand flash【K9F1G08U0E】。 板子我是在这里买的 【STM32F407ZGT6最小系统板/核心板/转接板/开发板/加1…