二叉树结构,三指针描述一棵树

树结构初识,三指针描述一棵树

1. 三指针树

树 子树 节点

​ 父节点 孩子节点 兄弟节点
在这里插入图片描述

2. 三指针描述一棵树

  • 2.1 三指针树节点类型

    链表构成的树结构 由三个指针来描述

    每个节点类型都具有父亲节点,兄弟节点与孩子节点来表示他们在树中的位置

    template <class T>
    struct Node
    {
    T data;
    Node<T>* pParent; //父亲
    Node<T>* pBrother; //兄弟
    Node<T>* pChild; //孩子
    };
  • 树类

    ​ 对于一个类,他具有类的结构,构造函数析构函数,当然这对于树来说无关紧要,类具有私有成员 pRoot来描述树的根节点

  • 2.2 查找树节点:

    • 在插入树的时候,必须先要找到你所要插入的节点应该位于树的什么位置,首先要根据pFindData来定位你想要把InsertData插入到树的什么位置?

      例如,这是一个三指针树结构:

      查找示例:

      • 插入节点 2,规定2是1的孩子节点 : 首先要找到节点1 ,根据1 来确定2是1的孩子节点

      • 再来插入节点3 ,规定3是2的兄弟节点: 所以我们就要找到节点2,根据2来确定3是2的兄弟节点

      在这里插入图片描述

      • 再来插入节点 4,规定4是3的兄弟节点: 同理,找到3来确定4是3的兄弟

      在这里插入图片描述

      • 再来插入节点5,规定5是2的孩子节点: 同理,根据2来确定2是5的孩子

      在这里插入图片描述

      • 再来插入一个6,但是规定6是0的兄弟节点,但是0不在树中,查找不到,则规定6自动成为根节点的兄弟节点

      在这里插入图片描述

      • 再来插入一个7,但是规定7是0的孩子节点,但是0不在树中,查找不到,则规定7自动成为根节点的最小孩子节点

        在这里插入图片描述

      • 同理,可知,当我们想要插入一个节点时,必须要规定它跟哪一个节点是什么关系(三指针的树结构,其他树不讨论)

      • 当我们想要让一个节点成为一个节点的兄弟,则查找到某一行(横着的)的节点

      • 当我们想要让一个节点成为一个节点的孩子,则查找到某一列(竖着的)的节点

      • 没有查找到的,自动成为根节点的孩子或者兄弟

    • 查找代码如下:

      //查找树节点
      Node<T>* Find_Node(const T& Finddata);
      template<class T>
      inline Node<T>* New_Tree<T>::Find_Node(const T& Finddata)
      {
      if (pRoot == nullptr)
      {
      return nullptr;
      }
      Node<T>* pCur = pRoot;
      Node<T>* pTemp = nullptr;
      //从根节点开始查找
      while (pCur)
      {
      pTemp = pCur;
      while (pTemp)
      {
      //在某个地方找到了FindData的数据,则返回这个节点位置,
      //例如上图,插入2找1,插入3找2,插入4找3,插入5找2
      if (pTemp->data == Finddata)
      {
      return pTemp;
      }
      //把每个节点的兄弟作为列
      pTemp = pTemp->pBrother;
      }
      //把每个节点的孩子作为行
      pCur = pCur->pChild;
      }
      //没找到则返回空
      return nullptr;
      }
  • 2.3 插入树节点

    1. 创建树的节点

      //创建节点
      Node<T>* Create_Node(const T& data);

      数据等于data,三个指针初始化为nullptr

      template<class T>
      inline Node<T>* New_Tree<T>::Create_Node(const T& data)
      {
      Node<T>* pNew = new Node<T>;
      if (pNew == nullptr)
      {
      return nullptr;
      }
      //快捷初始化为空
      memset(pNew, 0, sizeof(Node<T>));
      pNew->data = data;
      return pNew;
      }
    2. 树的插入节点

      InsertData表示要插入的数据

      FindData表示要插入的节点想要成为 FindData节点的孩子或者兄弟

      isBrother表示要插入的节点的类型,属于兄弟还是孩子,设置兄弟节点为默认节点,函数的缺省。则自动成为兄弟节点

      //插入树节点
      void push(const T& Insertdata, const T& Finddata, bool isBrother)

      2.3.1 树的插入位置选择:(伪代码)

      • 根节点 :(即插入的第一个节点)

      • 要插入一个兄弟节点:pFind查找要插入的前一个位置

        • pFind有效

          • 有原始的兄弟节点,成为最小兄弟节点
          • 没有原始兄弟,成为第一个兄弟
        • pFind无效(nullptr)例如上述 FindData=0

          • 没找到pFind,自动成为根节点的最小兄弟
      • 要插入一个孩子节点:pFind查找要插入的前一个位置

        • pFind有效
          • 有原始的孩子节点,成为最小孩子节点
          • 没有原始孩子,成为第一个孩子
        • pFind无效(nullptr)例如上述 FindData=0
          • 没找到pFind,自动成为根节点的最小孩子
      • 注意: 插入兄弟节点时,不一定非要选择相邻的位置,比如,你要在插入4当作3的兄弟(如上图),那么FindData不一定为3,也可以选择2,只要是同为兄弟节点,在同一行,都可以正确的插入,原因是都是插入到这一行的最小兄弟,所以肯定要有一个临时变量控制移动到最小兄弟处,然后再pTemp的兄弟指向pNew

  • 插入为孩子节点时,也有一个临时变量,终究是移动到最小孩子处,所以FindData只要是同为孩子节点,即这一列的元素即可,pTemp移动到最小孩子,pTemp的孩子指向pNew

  • 注意插入完成后,新插入的节点的父亲的关系的更新,新插入兄弟节点,则其父亲指向亲儿子的父亲节点,例如3,4的父亲和2一样为1;新插入孩子节点,则其父亲为其上一个节点,2的父亲为1,5的父亲为2;根节点的和其兄弟没有父亲nullptr

    2.3.2 插入树代码示例

    template<class T>
    inline void New_Tree<T>::push(const T& Insertdata, const T& Finddata, bool isbrother)
    {
    Node<T>* pNew = Create_Node(Insertdata);
    if (pNew == nullptr)
    {
    cout << "节点创建失败!\n";
    return;
    }
    //如果树为空
    if (pRoot == nullptr)
    {
    pRoot = pNew;
    return;
    }
    //查找要插入的节点在什么位置
    Node<T>* pFind = Find_Node(Finddata);
    Node<T>* pTemp = nullptr;
    //要插入兄弟节点
    if (isbrother)
    {
    //如果有原始兄弟节点
    if (pFind)
    {
    //1. 找到pFind的最小兄弟节点
    pTemp = pFind;
    while (pTemp->pBrother)
    {
    pTemp = pTemp->pBrother;
    }
    //2. pNew插入到最小兄弟位置
    pTemp->pBrother = pNew;
    //3. pNew的父节点更新
    pNew->pParent = pTemp->pParent;
    }
    else
    {
    //如果没有兄弟节点 直接成为根节点的兄弟
    pTemp = pRoot;
    while (pTemp->pBrother)
    {
    pTemp = pTemp->pBrother;
    }
    pTemp->pBrother = pNew;
    }
    }
    else //要插入孩子节点
    {
    //原始有孩子节点
    if (pFind)
    {
    //1. 成为pFind的最小孩子节点
    pTemp = pFind;
    while (pTemp->pChild)
    {
    pTemp = pTemp->pChild;
    }
    pTemp->pChild = pNew;
    pNew->pParent = pTemp;
    }
    else
    {
    //没有孩子节点,成为根节点的最小孩子
    pTemp = pRoot;
    while (pTemp->pChild)
    {
    pTemp = pTemp->pChild;
    }
    pTemp->pChild = pNew;
    }
    }
    }
  • 2.4 树的删除节点

    • 2.4.1 删除情况列举

      1. 删除带兄弟的根节点 ,如删除1 ,pDel指向1节点,则让其亲兄弟节点变为根节点亲兄弟节点的孩子节点指向pDel的亲孩子节点,同时要注意pDel的亲儿子的兄弟节点的父亲也要及时更新为新的根节点(20 节点)

      在这里插入图片描述

      1. 删除不带兄弟节点的根节点,删除根节点1,让其亲儿子的节点成为根节点包括其自身及兄弟节点的父亲指向nullptr
        在这里插入图片描述

      2. 删除兄弟节点: 删除亲儿子类型的兄弟节点,如删除3,则需要找到其父亲节点,并且判断其父亲节点的亲儿子节点不是pDel,否则可能会出现删除5的情况(如d情况)。让其父亲节点的亲儿子到达pDel的前一个节点(因为他们都是同一行的兄弟),跳跃删除
        在这里插入图片描述

      3. 删除兄弟节点: 删除根节点的兄弟节点,删除20,找到pDel的前一个节点,跳跃删除,同时注意pDel有没有孩子节点,其孩子节点成为其下一个兄弟的亲儿子节点,(不允许其孩子节点又有兄弟节点的情况出现)
        在这里插入图片描述

      4. 删除亲儿子类型节点(带兄弟),找到其父节点(注意区别情况c ),父节点的孩子节点成为pDel的第一个兄弟节点,pDel的孩子节点成为pDel的亲兄弟的孩子,同时pDel的孩子的兄弟节点父亲关系转变(pDel第一个兄弟)(如果有兄弟的话)
        在这里插入图片描述

      5. 删除亲儿子类型节点(不带兄弟),注意区别与上面的情况,没有兄弟,则直接更新孩子,同时还要注意孩子有没有兄弟,有的话父亲关系转变
        在这里插入图片描述

    • 2.4.2 删除代码

    //删除树节点
    void pop(const T& Deldata);
    template<class T>
    inline void New_Tree<T>::pop(const T& Deldata)
    {
    Node<T>* pDel = Find_Node(Deldata);
    if (pDel == nullptr)
    {
    cout << "未找到此节点,删除失败\n";
    return;
    }
    Node<T>* pTemp = nullptr;
    //删除根节点
    if (pDel == pRoot)
    {
    //根节点有兄弟节点 情况 1
    if (pDel->pBrother)
    {
    pTemp = pDel->pChild;
    //1. 根的第一个兄弟成为新的根
    pRoot = pDel->pBrother;
    //2. pDel的儿子们成为新的根的儿子
    pRoot->pChild = pTemp;
    //3. pDel的儿子们的pParent指向新的父亲
    while (pTemp)
    {
    pTemp->pParent = pRoot;
    pTemp = pTemp->pBrother;
    }
    }
    else //根节点没有兄弟节点 情况 2
    {
    //1. 直接删除pRoot 并让它的孩子们指向空
    pRoot = pDel->pChild;
    pTemp = pDel->pChild;
    while (pTemp)
    {
    pTemp->pParent = nullptr;
    pTemp = pTemp->pBrother;
    }
    }
    delete pDel;
    return;
    }
    /*
    删除非根节点
    */
    //横着有兄弟
    //1. 要删除的是根节点的兄弟 情况 4
    Node<T>* pFather = pDel->pParent;
    if (pDel->pParent == nullptr)
    {
    pTemp = pRoot;
    while (pTemp->pBrother != pDel)
    {
    pTemp = pTemp->pBrother;
    }
    pTemp->pBrother = pDel->pBrother;
    //节点有孩子
    if (pDel->pChild)
    {
    pTemp = pRoot;
    //pDel的孩子成为pDel的最小兄弟的孩子
    while (pTemp->pBrother)
    {
    pTemp = pTemp->pBrother;
    } //移动到最小兄弟
    pTemp->pChild = pDel->pChild;
    pDel->pChild->pParent = pTemp;
    }
    delete pDel;
    return;
    }
    //2. 要删除的是叶子节点的兄弟 情况 3
    if (pFather->pChild != pDel)
    {
    pTemp = pFather->pChild;
    while (pTemp->pBrother != pDel)
    {
    pTemp = pTemp->pBrother;
    }
    pTemp->pBrother = pDel->pBrother;
    delete pDel;
    return;
    }
    /*
    竖着的情况
    */
    //竖着有兄弟 情况 5
    if (pDel->pBrother)
    {
    //1. pDel的第一个兄弟成为pFather的亲儿子
    pFather->pChild = pDel->pBrother;
    //2. pDel的儿子们成为pDel的亲兄弟的儿子
    pDel->pBrother->pChild = pDel->pChild;
    pTemp = pDel->pChild;
    //3. pDel的儿子们与pDel的亲兄弟确认父子关系
    while (pTemp)
    {
    pTemp->pParent = pDel->pBrother;
    pTemp = pTemp->pBrother;
    }
    }
    //竖着没兄弟 情况 6
    else
    {
    //1. pDel的亲儿子成为pFather的亲儿子
    pFather->pChild = pDel->pChild;
    //2. pDel的亲儿子如果有兄弟们,则成为pFather的孩子
    pTemp = pDel->pChild;
    while (pTemp)
    {
    pTemp->pParent = pFather;
    pTemp = pTemp->pBrother;
    }
    }
    delete pDel;
    return;
    }

3. 测试

  • 树的遍历就不写了, 可以通过加断点调试来观察树的结构
#include "Tree.h"
#include "My_Tree.h"
int main()
{
#if 1
New_Tree<int> a;
a.push(1, 0);
a.push(2, 1, false);
a.push(3, 2);
a.push(33, 3,false);
a.push(4, 3);
a.push(5, 2, false);
a.push(6, 5);
a.push(7, 5,false);
a.push(8, 7);
a.push(9, 7);
a.push(10, 7);
a.push(11, 7,false);
a.push(12, 5, false);
a.push(13, 12);
a.push(14, 13);
a.pop(1);
a.pop(3);
a.pop(8);
a.pop(12);
#endif
return 0;
}

在这里插入图片描述

三指针树是树结构的基础,尽管三指针没什么用,但是用它来锻炼思维还是不错的,方便以后的二叉树的学习

源代码下载: ~~

这里是本篇文章的源代码下载连接,大家也可以多多关注我的guthub,记录我的编程学习,共同进步!

posted @   hugeYlh  阅读(26)  评论(0编辑  收藏  举报  
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
点击右上角即可分享
微信分享提示