5.5遍历二叉树和线索二叉树

发布时间 2023-07-28 11:24:14作者: 杭冷卉

5.5遍历二叉树和线索二叉树

遍历二叉树的非递归算法


中序遍历的非递归算法

​ 二叉树中序遍历的非递归算法的关键:在中序遍历过某节点的整个左子树后,如何找到该节点的根以及右子树。

基本思想:

  1. 建立一个栈
  2. 根结点进栈,遍历左子树
  3. 根结点出栈,输出根结点,遍历右子树。
Status InOrderTraverse(BiTree T)
{
    BiTree p; InitStack(S); p = T;
    while(p || !StackEmpty(S)){
        if (p) {Push(S,p); p = p->lchild;}
        else {Pop(S,q); printf("%c",q->data);
             p = q->rchild;}
    }
    return OK;
}

二叉树的层次遍历


image-20230728092329417

层次遍历结果:abfcdgeh

​ 对于一颗二叉树,从根结点开始,按从上到下、从左到右的顺序访问每一个结点。

​ 每个结点仅仅访问一次。

算法设计思路: 使用一个队列

  1. 将根结点进队;
  2. 队不空时循环:从队列中出列一个结点*p,访问它;
    1. 若它有左孩子结点,将左孩子结点进队;
    2. 若它有右孩子结点,将右孩子结点进队。

使用队列类型定义如下:

typedef struct{
    BTNode data[MaxSize];	//存放队中元素
    int font, rear;		//对头和队尾指针
}SqQueue;		//顺序循环队列类型

二叉树层次遍历算法:

void LevelOrder(BTNode *b){
    BTNode *p; SqQueue *qu;
    InitQueue(qu);		//初始化队列
    enQueue(qu, b);		//根结点指针进入队列
    while(!QueueEmpty(qu)) {		//队不为空,则循环
        deQueue(qu, p);		//出队结点p
        printf("%c", p->data);		//访问结点p
        if(p->lchild != NULL) enQueue(qu, p->lchild);	//有左孩子时将其进队
        if(p->rchild != NULL) enQueue(qu, p->rchild);	//有右孩子时将其进队
    }
}

二叉树遍历算法的应用——二叉树的建立


按先序遍历序列建立二叉树的二叉链表

例:已知先序序列为:ABCDEGF

  1. 从键盘输入二叉树的结点信息,建立二叉树的存储结构;
  2. 在建立二叉树的过程中按照二叉树先序方式建立;

为了确保生成树唯一,补充空结点(#)!!!

对如图所示二叉树,按下列顺序读入字符:ABC##DE#G##F###

image-20230728094809953

Status CreateBiTree(BiTree &T){
    scanf(&ch);	//cin>>ch;
    if(ch == "#")	T = NULL;
    else{
        if(!(T = (BiTree)*malloc(sizeof(BiTNode))))
            exit(OVERFLOW);//T = new BiTNode;
        T->data = ch;
        CreateBiTree(T->lchild);	//构造左子树
        CreateBiTree(T->rchild);	//构造右子树
    }
    return OK;
}//CreateBiTree

二叉树遍历算法的应用——复制二叉树


  • 如果是空树,递归结束;
  • 否则,申请新结点空间,复制根结点
    • 递归复制左子树
    • 递归复制右子树
int Copy(BiTree T,BiTree &NewT){
    if(T == NULL)	//如果是空树,返回0
    {
        NewT = NULL;	return 0;
    }
    else{
        NewT = new BiNode;		NewT->data = T->data;
        Copy(T->lChild, NewT->lchild);
        Copy(T->rChild, NewT->rchild);
    }
}

二叉树遍历算法的应用——计算二叉树深度


  • 如果是空树,则深度为0;

  • 否则,递归计算左子树的深度记为m,递归计算右子树的深度记为n,二叉树的深度则为m与n的较大者加1.

int Depth(BiTree T){
    if(T == NULL) return 0;	//如果是空树返回0
    else{
        m = Depth(T->lchild);
        n = Depth(T->rchild);
        if(m > n) return (m+1);
        else return (n+1);
    }
}

二叉树遍历算法的应用——计算二叉树结点总数


  • 如果是空树,则结点个数为0;

  • 否则,结点个数为左子树的结点个数+右子树的结点个数再+1.

int Node(BiTree T){
    if(T == NULL)
        rerurn 0;
    else
        return NodeCount(T->lchild) + NodeCount(T->rchild) + 1;
}

二叉树遍历算法的应用——计算二叉树叶子结点数


  • 如果是空树,则叶子结点个数为0;
  • 否则,为左子树的叶子结点个数+右子树的叶子结点个数。
int LeadCount(BiTree T){
    if(T == NULL)	//如果是空树返回0
        return 0;
    if(T->lchild == NULL && T->rchild == NULL)
        return 1;	//如果是叶子结点返回1
    else
        return LeafCount(T->lchild) + LeafCount(T->rchild);
}

线索二叉树


问题:为什么要研究线索二叉树?

​ 当二叉链表作为二叉树的存储结构时,可以很方便地找到某个结点的左右孩子;但一般情况下,无法直接找到该结点在某种遍历序列中的前驱和后继结点。

提出问题:如何寻找特定遍历序列中二叉树结点的前驱和后继???

解决方法:

  1. 通过遍历寻找——费时间
  2. 再增设前驱、后继指针域——增加了存储负担
  3. 利用二叉链表中的空指针域

​ 如果某个结点的左孩子为空,则将空的左孩子指针域改为指向其前驱;如果某结点的右孩子为空,则将空的右孩子指针域改为指向其后继——这种改变指向的指针称为 “线索”

​ 加上了线索的二叉树为 线索二叉树(Threaded Binary Tree)

​ 对二叉树按某种遍历次序时期变为线索二叉树的过程叫 线索化

​ 为区分lchild和rchild指针到底是指向孩子的指针,还是指向前驱或者后继的指针,对二叉链表中每个结点增设两个标志域ltag和rtag,并约定:

ltag = 0 lchild指向该结点的左孩子
ltag = 1 lchild指向该结点的前驱
rtag = 0 lchild指向该结点的右孩子
rtag = 1 lchild指向该结点的后驱

这样,结点的结构为:

lchild itag data rtag rchild
typedef struct BiThrNode{
    int data;
    int ltag, rtag;
    struct BiThrNode *lchild, rchild;
}BiThrNode, *BiThrTree;

先序线索二叉树 中序线索二叉树 后序线索二叉树

为避免悬空态,增设一个头结点

增设了一个头结点:ltag=0,lchild指向根节点,rtag=1,rchild指向遍历序列中最后一个结点,遍历序列中第一个结点的lc域和最后一个结点的rc域都指向头结点

image-20230728105844187