408数据结构学习笔记——二叉树的遍历和线索二叉树(上)

简介: 408数据结构学习笔记——二叉树的遍历和线索二叉树

1.二叉树的先中后序遍历

1.1.先中后序遍历的基本概念

  1. 先序遍历:根→左→右:ABDECFG
  2. 中序遍历:左→根→右:DBEAFCG
  3. 后序遍历:左→右→根:DEBFGCA
  4. 可以先按遍历的顺序写出每次递归的子树的根左右结点,然后依次按结点添加下一次递归的根左右结点,直到访问全部结点(逐层展开)

      1.先序遍历:根左右→根(根左右)右→ 根(根左右)(根左右)

       2.中序遍历:左根右→(左根右)根→(左根右)根(左根右)

      3.后序遍历:左右根→(左右根)右根→(左右根)(左右根)根

23445be388c74e599b43c7ed6e286c9b.png

1.2.先中后序遍历的代码实现

1.2.1.递归

typedef struct BiTNode{
    elemType value;
    struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;
//先序遍历
void preOrder(BiTree T){
    if(T != NULL){
        visit(T);    //访问根节点
        preOrder(T->lchild);    //递归遍历左子树
        preOrder(T->rchild);    //递归遍历右子树
    }
}
//中序遍历
void inOrder(BiTree T){
    if(T != NULL){
        inOrder(T->lchild);    //递归遍历左子树
        visit(T);    //访问根节点
        inOrder(T->rchild);    //递归遍历右子树
    }
}
//后序遍历
void postOrder(BiTree T){
    if(T != NULL){
        postOrder(T->lchild);    //递归遍历左子树
        postOrder(T->rchild);    //递归遍历右子树
        visit(T);    //访问根节点
    }
}

b0ebad6591cd44a3b623aeb34a47c73d.png

  1. #126为第126行代码,#127为第127行代码
  2. preOrder(A)→访问A→preOrder(A)入栈#126,进入preOrder(B)
  3. 访问B→preOrder(B)入栈#126,进入preOrder(D)
  4. 访问D→左子树为空→preOrder(D)入栈#127,进入preOrder(G)
  5. 访问G→左子树空→右子树空→返回preOrder(D)#127→返回preOrder(B)#126→preOrder(B)#127入栈,进入preOrder(E)
  6. 访问E→左子树为空→右子树为空→返回preOrder(B)#127→返回preOrder(A)#126→preOrder(A)#127入栈,进入preOrder(C)
  7. 访问C→preOrder(C)#126入栈,进入preOrder(F)
  8. 访问F→左子树为空→右子树为空→返回preOrder(C)#126→右子树为空→返回preOrder(A)#127→结束遍历

1.2.2.非递归

后序遍历非递归:栈中剩余元素都是栈顶元素的双亲结点,此特性可求根到结点的路径和两个节点的最近公共结点

#define MAXSIZE 100
typedef struct BiTNode{
    struct BiTNode *lchild, *rchild;
    Elemtype value;
}BiTNode, *BiTree;
typedef struct Stack{
    BiTNode data[MAXSIZE];
    int top;
}Stack;
//先序遍历
void PreOrder(BiTree T){
    Stack S;
    InitStack(S);
    BiTNode *p = T;
    //当p不存在并且栈空时结束循环
    while(p || !(IsEmpty(S)){
        if (p) {
            //先序遍历先访问p再入队
            visit(p);
            push(S, p);
            p = p->lchild;
        }
        else{
            pop(S, p);
            p = p->rchild);
        }
    }//while
}
//中序遍历
void InOrder(BiTree T){
    Stack S;
    InitStack(S);
    BiTNode *p = T;
    while(p || !(IsEmpty(S)){
        if (p){
            push(S, p);
            p = p->lchild;
        }
        else{
            //中序遍历出栈后访问p
            pop(S, p);
            visit(p);
            p = p->rchild;
        }
    }//while
}
//后序遍历
void PostOrder(BiTree T){
    Stack S;
    InitStack(S);
    BiTNode *p = T, *r = NULL;
    while(p || !(IsEmpty(S)){
        //p存在,则入栈p,进入p的左子树
        if (p){
            push(S, p);
            p = p->lchild;
        }
        //p不存在
        else{
            //查看栈顶元素
            GetTop(S, p);
            //栈顶元素的右孩子未访问过,则将右孩子入栈,并且进入其左子树
            if (p->rchild && p->rchild != r) {
                p = p->rchild;
                push(S, p);
                p = p->lchild;
            }
            //栈顶元素的右孩子访问过,则出栈并访问栈顶元素,并用r进行标记
            //访问后p置空再次进入else循环
            else {
                pop(S, p);
                visit(p);
                r = p;
                p = NULL;
            }
        }//else
    }//while
}

2.二叉树的层次遍历

2.1.算法思想

  1. 初始化一个队列
  2. 将根节点入队
  3. 若队不空,则队头结点出队,访问该节点,并且将其左右孩子入队
  4. 重复3直至队列为空

2.2.代码实现

typedef struct BiTNode{
    elemType value;
    struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;
typedef struct linkNode{
    //存指针
    BiTNode *data;
    struct linkNode *next;
}linkNode;
typedeft struct linkQueue{
    linkNode *front, *rear;
}linkQueue;
//层次遍历
void levelOrder(BiTree T){
    linkQueue Q;
    initQueue(Q);    //初始化队列
    BiTree p;
    enQueue(Q, T);    //T入队
    while(!isEmpty(Q)){
        deQueue(Q, p);    //p出队
        visit(p);    //访问p
        if (p->lchild) enQueue(Q, p->lchild);    //p的左孩子入队
        if (p->rchild) enQueue(Q, p->rchild);    //p的右孩子入队
    }
}

3.由遍历序列构造二叉树

中序+前序/后序/层序可以确定一个二叉树

f2e020eb6a50434597ac873a7e5d017d.png

3.1.前序+中序确定一个二叉树

  1. 前序序列:D A E F B C H G I
  2. 中序序列:E A F D H C B G I
  3. 前序序列第一个访问的是根节点→D是根节点→左(EAF)根(D)右(HCBGI)
  4. 前序序列中EAF第一个访问的是根节点→A是EAF根节点→左(E)根(A)右(F)
  5. 前序序列中HCBGI第一个访问的是根节点→B是HCBGI根节点→左(HC)根(B)右(GI)
  6. 前序序列中HC第一个访问的是根节点→C是HC的根节点→左(H)根(C)
  7. 前序序列中GI第一个访问的是根节点→G是GI的根节点→根(G)右(I)

3.2.后序+中序确定一个二叉树

  1. 后序序列:E F A H C I G B D
  2. 中序序列:E A F D H C B G I
  3. 后序序列中最后访问的是根节点→D是根节点→左(EAF)根(D)右(HCBGI)
  4. 后序序列中EAF最后访问的是根节点→A是EAF根节点→左(E)根(A)右(F)
  5. 后序序列中HCBGI最后访问的是根节点→B是HCBGI根节点→左(HC)根(B)右(GI)

  6.后序序列中HC最后访问的是根节点→C是HC根节点→左(H)根(C)

  7.后序序列中GI最后访问的是根节点→G是GI根节点→根(I)右(G)

3.3.层序+中序确定一个二叉树

  1. 层次序列:D A B E F C G H I
  2. 中序序列:E A F D H C B G I
  3. 层次序列中第一个访问的是根节点→D是根节点→左(EAF)根(D)右(HCBGI)
  4. 层次序列中EAF第一个访问的是根节点→A是EAF根节点→左(E)根(A)右(F)
  5. 层次序列中HCBGI第一个访问的是根节点→B是HCBGI根节点→左(HC)根(B)右(GI)
  6. 层次序列中HC第一个访问的是根节点→C是HC的根节点→左(H)根(C)
  7. 层次序列中GI第一个访问的是根节点→G是GI的根节点→根(G)右(I)

4.线索二叉树

4.1.线索二叉树的概念

二叉树使用链式存储时会有n + 1个空指针未使用,可以将其指向该结点的前驱和后继,使得二叉树具有线性特性,从而方便遍历

规定线索二叉树中,相比传统二叉树,多两个标志位——ltag和rtag。tag用来标记指针指向的是左右孩子还是前驱后继

7b9ea4a9781445e2b72de89b0b25e100.png

typedef struct threadNode{
    elemType data;
    struct threadNode *lchild, *rchild;
    int ltag, rtag;
}threadNode, *threarTree;

38ed893b09574cf2977a97aedb09ed17.png

4.2.中序线索化

typedef struct threadNode{
    elemType data;
    struct threadNode *lchild, *rchild;
    int ltag, rtag;
}threadNode, *threadTree;
//全局变量pre,指向当前当前结点的前驱结点
threadNode *pre = NULL;
void inThread(threadTree &T){
    if (T){
        inThread(T->lchild);
        visit(T);
        inThread(T->rchild);
    }
}
void visit(threadNode *q){
    //如果p节点左孩子不存在,则左孩子指向该节点的前驱结点
    if (!q->lchild) {
        q->lchild = pre;
        q->ltag = 1;
    }
    //如果pre结点存在并且它的右子树为空
    if (pre && !pre.rchild){
        pre->rchild = q;
        pre->rtag = 1;
    }
    pre = q;
}
void createInThread(threadTree T){
    pre = NULL;
    if(T) {
        inThread(T);
        //如果最后一个结点的右孩子为空,则改标记位
        if (pre->rchild == NULL) pre->rtag = 1;
    }
}

c1f4fc466d29406ebbfb8bb42eb90d28.png

4.3.先序线索化

需要注意,如果不对ltag进行判断,则会进入死循环

typedef struct threadNode{
    struct threadNode *lchild, *rchild;
    elemType data;
    int ltag, rtag;
}threadNode, *threadTree;
threadNode pre = NULL;
void preThread(threadTree T){
    visit(T);
    //防止进入死循环
    if (ltag == 0) preThread(T->lchild);
    preThread(T->rchild);
}
void visit(threadTree *p){
    if (p->lchild == NULL) {
        p->lchild = pre;
        p->ltag = 1;
    }
    if (pre && pre->rchild == NULL) {
        pre->rchild = p;
        pre->rtag = 1;
    }
    pre = p;
}
void createPreThread(threadTree T){
    pre = NULL;
    if (T){
        preThread(T);
        if (pre->rchild  == NULL) pre->rtag = 1;
    }
}

4.4.后序线索化

typedef struct threadNode{
    struct threadNode *lchild, *rchild;
    elemType data;
    int ltag, rtag;
}threadNode, *threadTree;
threadNode *pre = NULL;
void postThread(threadTree T){
    postThread(T->lchild);
    postThread(T->rchild);
    visit(T);
}
void visit(threadNode *p){
    if (p->lchild == NULL){
        p->lchild = pre;
        p->ltag = 1;
    }
    if (pre && pre->rchild == NULL){
        pre->rchild = p;
        pre->rtag = 1;
    }
    pre = p;
}
void createPostThread(threadTree T){
    pre = NULL;
    if (T) {
        postThread(T);
        if (pre->rchild == NULL) pre->rtag = 1;
    }
}

4.5.找前驱/后继

4.5.1.中序线索树找后继

  1. p->rtag = 1,next = p->rchild
  2. p->rtag = 0,next = 右子树的最左节点
//找到中序遍历的第一个节点
threadNode *firstNode(threadNode *p){
    //找到最左下的结点,并非一定是叶子结点
    //有可能是左空右不空
    while(p->ltag == 0) p = p->lchild;
    return p;
}
//找当前节点的后继结点
threadNode *nextNode(threadNode *p){
    //rtag = 0说明有右子树,继续寻找右子树中的最左节点
    if (p->rtag == 0) return firstNode(p->rchild);
    //rtag = 1说明右子树为指向的是后继结点
    else return p->rchild;
}
//中序遍历
void InOrder(ThreadNode *T){
    for (threadNode *p = firstNode(T); p != NULL; p = nextNode(p)) visit(p);
}

4.5.2.中序线索树找前驱

  1. p->ltag = 1,pre = p->lchild
  2. p->ltag = 0,pre = 左子树的最右结点
//找到最后一个结点
ThreadNode *LastNode(ThreadNode *p){
    while(p->rtag == 0) p = p->rchild;
    return p;
}
//找前驱结点
ThreadNode *PreNode(ThreadNode *p){
    if (p->rtag == 0) return LastNode(p->lchild);
    else return p->rchild;
}
//中序遍历
void InOrder(ThreadTree T){
    for (ThreadNode *p = LastNode(T); p != NULL; p = preNode(p)) visit(p);
}

4.5.3.先序搜索树找后继

  1. p->ltag = 1,next = p->rchild
  2. p->ltag = 0
  1. 左子树不空,next = 左孩子
  2. 左子树空,右子树不空,next = 右孩子

4.5.4.先序搜索树找前驱

  1. p->ltag = 1,pre = p->lchild
  2. p->ltag = 0
  1. 如果没有parent指针,则无法找到前驱
  2. 有parent指针:      

          1.p是左孩子:pre = p->parent

          2.p是右孩子,但左兄弟为空:pre = p->parent

          3.p是右孩子,且有左兄弟:pre = 左兄弟子树的最后一个遍历的结点

          4.p是根节点:pre = NULL

4.5.5.后序搜索树找后继

  1. p->rtag = 1,next = p->rchild
  2. p->rtag = 0
  1. 如果没有parent指针,则无法找到后继
  2. 有parent指针:

                1.p是右孩子:next = p->parent

               2.p是左孩子,且没有右兄弟:next = p->parent

              3.p是左孩子,且有右兄弟:next = 右兄弟子树中第一个被后序遍历的结点

               4.p是根节点,p没有后继

4.5.6.后序搜索树找前驱

  1. p->ltag = 1,pre = p->lchild
  2. p->ltag = 0,p必有左子树
  1. p有右子树,pre = p->rchild
  2. p没有右子树,pre = p->lchild




相关文章
|
12天前
|
Java C++
【C++数据结构——树】二叉树的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现二叉树的基本运算。​ 相关知识 创建二叉树 销毁二叉树 查找结点 求二叉树的高度 输出二叉树 //二叉树节点结构体定义 structTreeNode{ intval; TreeNode*left; TreeNode*right; TreeNode(intx):val(x),left(NULL),right(NULL){} }; 创建二叉树 //创建二叉树函数(简单示例,手动构建) TreeNode*create
37 12
|
12天前
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
37 10
|
13天前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
37 2
|
27天前
|
数据库
数据结构中二叉树,哈希表,顺序表,链表的比较补充
二叉搜索树,哈希表,顺序表,链表的特点的比较
数据结构中二叉树,哈希表,顺序表,链表的比较补充
|
13天前
|
数据采集 存储 算法
【C++数据结构——图】图的遍历(头歌教学实验平台习题) 【合集】
本文介绍了图的遍历算法,包括深度优先遍历(DFS)和广度优先遍历(BFS)。深度优先遍历通过递归方式从起始节点深入探索图,适用于寻找路径、拓扑排序等场景;广度优先遍历则按层次逐层访问节点,适合无权图最短路径和网络爬虫等应用。文中提供了C++代码示例,演示了如何实现这两种遍历方法,并附有测试用例及结果,帮助读者理解和实践图的遍历算法。
26 0
|
2月前
|
机器学习/深度学习 存储 算法
数据结构实验之二叉树实验基础
本实验旨在掌握二叉树的基本特性和遍历算法,包括先序、中序、后序的递归与非递归遍历方法。通过编程实践,加深对二叉树结构的理解,学习如何计算二叉树的深度、叶子节点数等属性。实验内容涉及创建二叉树、实现各种遍历算法及求解特定节点数量。
112 4
|
2月前
|
C语言
【数据结构】二叉树(c语言)(附源码)
本文介绍了如何使用链式结构实现二叉树的基本功能,包括前序、中序、后序和层序遍历,统计节点个数和树的高度,查找节点,判断是否为完全二叉树,以及销毁二叉树。通过手动创建一棵二叉树,详细讲解了每个功能的实现方法和代码示例,帮助读者深入理解递归和数据结构的应用。
174 8
|
2月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
286 9
|
2月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
44 1
|
12天前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
128 75

热门文章

最新文章