• 二叉树的存储结构


    目录

    引言:

    顺序存储结构

    对一般的二叉树

    二叉树的链式存储结构

    创建二叉树 CreateBTNode(*b,*str)

    ● 第七步 : 遍历到 “ ,”

    通过上面的演示  :

            下面我们开始提取规范性的步骤 , 进行程序设计

    所以现在我们进行程序设计:


    引言:

            对于二叉树的存储,我们可以采取顺序存储链式存储结构

    顺序存储结构

    ● 按编号次序存储节点

            • 对树中每个节点进行编号

            • 其编号从小到大的顺序就是节点在连续存储单元的先后次序。


     我们是从编号为1开始,为了保持数组位序和编号保持一致,所以我们从数组下标 1 开始

    我们通过观察, 可以看出二叉树的编号的规律:

    ● 有分支节点的编号,即可求其子女的编号

             • 若编号为 i 的节点的左孩子节点的编号为 2 i ; 右孩子节点的编号为(2 i + 1).

    ●有子女的节点,即可求其双亲的编号(偶数编号/2 , (奇数编号 - 1)/2 )

             •除树根节点外,若一个节点的编号为 i , 则它的双亲节点的编号为⌊ i /2 ⌋

    对一般的二叉树

          

     上面我们介绍的是完全二叉树的顺序存储 , 但是并不是所有的二叉树都是完全二叉树,所以我们就要想办法对一般二叉树进行编号,然后让其编号对应数组的位序


    ● 先用空节点补全成为完全二叉树

    ● 再对节点编号

    ● 最后确定存储

     对空节点补全,只是想象的,形式上的补全,我们补全的目的是能够把编号做下去,对于补全完全二叉树的空缺节点,对我们来说是无关紧要的


    当我们按照完全二叉树的形状进行编号的时候 , 我们再把其存储在数组里面,即可

    对于空缺节点,我们用特殊符号代替即可


    定义存储二叉树节点的数组

    1. || MaxSize 为最大编号加一,数组从0开始,编号从1开始
    2. typedef ElemType SqBTree[MaxSize];
    3. ||树对应的字符串
    4. SqBTree bt = "#ABD#C#E######F";

    给出节点编号,求子女:

    bt[2*i],bt[2*i+1]

    给出节点编号,求其父母编号:
     

    1. bt[⌊i/2⌋]
    2. 或者:
    3. 判断编号为偶数:
    4. bt[i/2]
    5. 编号为奇数:
    6. bt[(i-1)/2]

    二叉树的链式存储结构

    其中,

    data 表示值域,用于存储对应的数据元素(一般是char类型)

    lchild rchild 分别表示左指针域和右指针域,用于分别存储左孩子节点和右孩子节点(即左、右子树的根节点)的存储位置。

    1. typedef struct node
    2. {
    3. ElemType data;
    4. struct node *lchild,*rchile;
    5. }BTNode;

     接下来,根据图示进行构建二叉树的链式存储结构:

    创建二叉树 CreateBTNode(*b,*str)

    根据上面图示的例子:

    我们既然要构造二叉树,输入的是字符串,所有我们把上面的二叉树用字符串来表示

    例:str ------ A BD ,G))C EF ) )

    我们要把此字符串转换成如图所示的链式二叉树:


    下面,我们逐个便利字符串 , 然后利用人脑来构建二叉树,然后从中找到规范性的规律,进而进行程序化设计:


    ●第一步: 遍历到 “A” 。

            • 当我们看到 A , 就断定 A 是此树的根节点


     ● 第二步:遍历到 “( ” 。

            • 当我们看到 ‘( ’就知道,我们该到下一个层次了, 就是A的子树了。

    但是,接下来的子树是A 的左孩子还是右孩子呢?

    如果接下来直接出现一个元素,当然是左孩子, 如果接下来出现一个逗号,逗号后面的元素当然是右孩子


    ● 第三步 : 遍历到“B”

            •  B 前一个元素是 “( ” , 也就意味着,此时出现 B , 那么 B 将作为 A 的左孩子

     


    ● 第四步 : 遍历到“(”

            • 此时遇到 “( ” ,说明套里有套 , B 还有子树,所有应该先处理 B 

    接下来 ,如果出现元素,就是B 的左孩子,如果出现逗号,那接下来 ,再出现元素就是B的右孩子 ,欲知后事如何,请接着遍历 

            • 此时,我们有发现一个问题 , 我们根据图示知道 , 遍历到 “(” , 刚才遇到了 B ,所以接下来处理的是 B 的子树, 但是计算机怎么知道,我们是需要处理元素是 后遇到的 B呢 ? 还是先遇到的 A 呢?

            • 我们人脑当然知道 , 我们先遇到树根 , 然后接着遇到树根的孩子 ,我们要接着处理 孩子的孩子 ,所以遇到“( ”,当然是处理的是 后进来的 B 的子树 

            • 所以我们怎么让计算机 ,懂得 后进来的 , 弹出接着处理呢 ? 

            • 我们此时会想到先进后出 , 后进先出 , 这不就是栈结构吗? 

            • 我们遇到 A , 就把 A 存起来 , 然后接下来的元素,就是A 的子树 ; 我们后来又遇到 B, B后边有“(” , 我们就把 B  压入栈顶 , 然后我们接下来处理的元素 ,就是 栈顶 B  的孩子 , 直到遇到 “)” 我们才能断定此层嵌套结束, 弹出栈顶的一个根节点。


    ● 第五步:遍历到“ D ”

            • 我们刚才已经知道,前一个元素是 “(  ” ,遇到元素的话 , 此元素就是 栈顶元素的左孩子 , 我们接着加入图示:


    ● 第六步 : 遍历到 “ ( ”

            • 我们此时遍历到 “ ( ” , 就应该知道 , 前一个元素有孩子了, 所以我们要把前一个创建的节点 “ D ” 压入栈顶 。

     


    ● 第七步 : 遍历到 “ ,”

            • 很抱歉 ,说明 栈顶元素 D ,其没有左孩子, 就期待是否下一个字符是否是字母,是字符 ,即是其右孩子


    ●第八步: 遍历到 “ G ”

            •  上一步我们遇到 “,”说明如果此时遇到 字母 ,其就是栈顶元素的右孩子 

            • 此时我们思考 ,我们人脑通过判断,通过 上一步遇到 “,” 可以推断出下一步遇到 字母就可以把其称作栈顶元素的右孩子的 。 但是计算机只会一步一步比较 ,它是不会前后兼顾的 

            • 所以 ,当我们在第七步 遇到 “ ,” 的时候 ,我们可以对特定变量进行赋值 ,然后计算机对其赋值进行对比 ,对比成功后,跳转到与数值对应的的符合人脑逻辑的操作 ,从而补足计算机的短板 ,形成严密逻辑 , 进行程序化的设计 

            • 所以 , 我们在遍历到特定字符的时候  ,我们可以对特定变量赋值 ,进而引导计算机进行相关操作


     第九步 :遍历到 “ )” , 

    我们知道 ,此时栈顶元素 “D" 已经没有节点可以构建 ,所以就需要出栈


    第十步: 又一次遍历到 “)” ,我们接着出栈,栈顶元素


    第 十一步 : 遍历到“,” 此时说明 接下来如果遇到字母 ,那么这个字母就是栈顶元素的右孩子 ,但是接下来的都是未知 , 所以 ,我们对 K 进行赋值 为 2 , 当下一个元素是 字母时, 其申请空间 ,然后通过对比到 K = 2 时, 我们就让 K 作为栈顶元素 A  的右孩子 

            • 有些同学说 , 如果不是字母呢 ? 那 K 会不会影响接下来的操作呢? 不会的 , 当遇到 “)” 我们也可以对 K 进行赋值 ,然后操作 , K  的值会根据遇到的字符而改变其值的


    第十二步:

             遍历到 “ C ” ,为 C 创建空间 ,然后此时 K =2 ,所以我们让 C 成为栈顶的节点 A 的右孩子


    第十三步:

            遍历到" ( ",说明我们需要让前一个刚创建的元素入栈 , 此节点有孩子了 , 所以我们把 C 压入栈顶 ,与此同时 , 我们也要让 k = 1 , 表示下一个创建的元素, 变成栈顶元素的左孩子


    第十四步:

            遍历到"E" , 此时 k = 1 , 所以 我们让栈顶元素 C 的左孩子指针指向 E


    第十五步:

            遍历到 " , " , 说明下一个元素如果是字母的话 , 就成为栈顶元素的右孩子 , 所以此时 k 赋值为 2


    第十六步:

            遍历到 " F "  , 并且此时 k = 2 ,所以 F 成为 栈顶元素的右孩子


    第十七步:

            遍历到 " ) " , 说明我们要把栈顶元素出栈了 ,


    第十八步:

            遍历到 " ) " , 说明我们接着要把栈顶元素出栈.

    第十九步:

            遍历到 " \0 " , 说明遍历构建结束

    通过上面的演示  :

            下面我们开始提取规范性的步骤 , 进行程序设计

    我们遍历字符串 , 需要一个字符来承载每次遍历的数据, 设此字符为 ch 

     ● 扫描采用括号表示法表示二叉树的字符串 , 读到的字符为 ch

     ● 使用一个栈St 保存双亲节点 , k  指定其后处理的节点是 双亲节点(保存在栈中的) 左孩子节点 (k = 1) , 还是右孩子节点 ( k = 2 ) .

     ● 分以下几种情况:

            ① 若 ch = " ( " , 则将前面刚创建的节点 ,作为双亲节点进栈 

                 并置 K = 1 , 表示其后创建的节点 , 将作为这个节点的左孩子节点

            ② 若 ch = " , "  表示其后创建的节点 , 作为栈顶节点的右孩子节点

            ③ 若 ch  = " ) " , 表示栈顶节点左右孩子处理完毕 , 退栈

            ④ 其他情况 :

                    当 k = 1 时 , 表示这个节点作为栈顶节点的左孩子节点

                    当 k = 2 时 , 表示这个节点作为栈顶节点的右孩子节点

       如此循环直到 str 处理完毕

    所以现在我们进行程序设计:

    传入要构建的二叉树 , 要构建的二叉树字符串数组

    1. void CreateBTNode(BTNode *&b,char* str)
    2. {

    先构建存放双亲的栈 , 栈顶 top 指向 -1

    1. BTNode *St[MaxSize];
    2. int top = -1;

    定义构建新节点的指针

    BTNode *p = NULL;

    定义遍历二叉树字符串的变量 ch 

    1. char ch;
    2. ||ch 初始指向二叉树字符串数组的第一个元素
    3. int j = 0;
    4. ch = str[j];

    开始构建前 , 把要构建的二叉树先置空 , 避免错乱

    b = NULL;

    定义我们上面所说的变量 k , 指导程序进行相关操作

    int k = 0;

    接下来开始遍历 , 构建二叉树链表:

    1. while(ch !='/0')
    2. {
    3. switch(ch)
    4. {
    5. || 左括号, 把之前创建节点入栈,将 k = 1,标识后面的元素为栈顶节点的左孩子
    6. case '(':
    7. top++;
    8. St[top] = p;
    9. k = 1;
    10. break;
    11. || 遇到右括号,表示栈顶节点左右孩子处理完毕,退栈
    12. case ')':
    13. top--;
    14. break;
    15. || 遇到逗号 , 标识下个字母为栈顶节点的右孩子
    16. case ',':
    17. k = 2;
    18. break;
    19. || 以上都不是 ,说明就是字母,我们就为其创造空间
    20. default:
    21. p = (BTNode*)malloc(sizeof(BTNode));
    22. || 节点数据区赋值为 ch
    23. p->data = ch;
    24. || 新创建的节点左右孩子置空
    25. p->lchild = p->rchild = NULL;
    26. || 此时有一个特殊情况,我们要先确定二叉树的根节点,在其基础上,进行构建
    27. ||当 b 为空时,那第一个创建的节点就是根节点
    28. if(b == NULL)
    29. {
    30. b = p;
    31. }
    32. else
    33. {
    34. || 根据 k 的值 , 进而跳转相应的操作, 为 1 ,则作为栈顶节点左孩子,为2 作为栈顶节点右孩子
    35. switch(k)
    36. {
    37. case 1:
    38. St[top]->lchild = p;
    39. break;
    40. case 2:
    41. St[top]->rchild = p;
    42. break;
    43. }
    44. }
    45. || 遍历下一个字符
    46. j++;
    47. ch = str[j];
    48. }
    49. }

  • 相关阅读:
    Springboot拦截器中注入Bean
    吸烟(抽烟)检测和识别2:Pytorch实现吸烟(抽烟)检测和识别(含吸烟(抽烟)数据集和训练代码)
    第一章.线性空间和线性变换
    丢掉破解版,官方免费了!!!
    python基础项目实战-简单版学生管理系统
    JUC中的原子类
    zotero通过DOI快速导入文献
    城市内涝解决方案:实时监测,提前预警,让城市更安全
    华为全联接大会2023 | 尚宇亮:携手启动O3社区发布
    Vue教学19:Element UI组件深入探索,构建精致的Vue应用界面
  • 原文地址:https://blog.csdn.net/qq_57484399/article/details/127863281