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




相关文章
|
21天前
|
算法
【算法与数据结构】二叉树(前中后)序遍历2
【算法与数据结构】二叉树(前中后)序遍历
|
6天前
二叉树和数据结构
二叉树和数据结构
15 0
|
7天前
|
算法 DataX
二叉树(中)+Leetcode每日一题——“数据结构与算法”“剑指Offer55-I. 二叉树的深度”“100.相同的树”“965.单值二叉树”
二叉树(中)+Leetcode每日一题——“数据结构与算法”“剑指Offer55-I. 二叉树的深度”“100.相同的树”“965.单值二叉树”
|
17天前
|
算法 索引
【算法与数据结构】深入二叉树实现超详解(全源码优化)
【算法与数据结构】深入二叉树实现超详解(全源码优化)
|
17天前
|
存储 算法
【算法与数据结构】深入解析二叉树(二)之堆结构实现
【算法与数据结构】深入解析二叉树(二)之堆结构实现
|
28天前
|
存储 算法 程序员
【数据结构】【版本2.0】【树形深渊】——二叉树入侵
【数据结构】【版本2.0】【树形深渊】——二叉树入侵
|
29天前
|
算法 C++ 开发者
【C/C++ 数据结构 】二叉树基本性质:具有n个结点的完全二叉树的深度为[log2n]+1或者[log2(n+1)]...
【C/C++ 数据结构 】二叉树基本性质:具有n个结点的完全二叉树的深度为[log2n]+1或者[log2(n+1)]...
11 0
|
29天前
|
存储 算法 C语言
【C/C++ 数据结构 树】探索C/C++中的二叉树:从理论到实践
【C/C++ 数据结构 树】探索C/C++中的二叉树:从理论到实践
60 0
|
30天前
|
存储 算法 Python
数据结构与算法——二叉树介绍(附代码)
数据结构与算法——二叉树介绍(附代码)
22 3
|
存储 算法
【链式二叉树】数据结构链式二叉树的(万字详解)
【链式二叉树】数据结构链式二叉树的(万字详解)