浅谈二叉树算法

107 阅读6分钟

四种遍历算法

遍历算法几乎是二叉树所有算法的基础,而我们已经知道了二叉树的遍历方法有先序遍历、中序遍历、后序遍历,层序遍历;下面我们用代码实现一下四种遍历方法。
先定义一下二叉树的结构体:

//
typedef struct BiNode{ 
    TElemType data;
    struct BiNode *lchild,*rchild; 
}BiNode,*BiTree;

因为二叉树使用递归定义的,所以使用递归可以很轻易的写出遍历算法
先序遍历:

//先序遍历二叉树
void TraverseBiTree(BiTree T)
{
    if (T == NULL)
        return ;
    visit(T);//访问当前根节点
    TraverseBiTree(T->lChild);//递归遍历左子树
    TraverseBiTree(T->rChlid);//递归遍历右子树
}

2021-11-21 19-32-09 的屏幕截图.png

中序遍历也是可以写出的:

//中序遍历二叉树
void InOrderBiTree(BiTree T)
{
    if (T == NULL)
        return ;
    InOrderBiTree(T->lChild);
    visite(T);    
    InOrderBiTree(T->rChlid);
}

同样写出后序遍历算法:

//中序遍历二叉树
void InOrderBiTree(BiTree T)
{
    if (T == NULL)
        return ;
    InOrderBiTree(T->lChild);
    InOrderBiTree(T->rChlid);
    visite(T);    
}

三种遍历算法非常相似,只有访问根节点的代码位置不同,而遍历算法的时间复杂度是O(n)[每个结点只访问一次],空间复杂度也是O(n)[栈占用的最大辅助空间]

除了以上三种算法外,还有一种层次遍历法,顾名思义,就是二叉树从根节点开始按从上到下,从左到右的顺序访问每个结点,每个结点只被访问一次。
使用队列来完成这个算法:

  • 将根节点入队
  • 对不空时循环:从队列中出列一个结点p,访问它;
    1. 若它有左孩子结点,将左孩子入队
    2. 若它有左孩子结点,将左孩子入队 先定义一下使用的队列:
typedef struct{
    BTnode data[MaxSize];//存放队中元素
    int front,rear;//队头指针,队尾指针
    }SqQueue

再来实现层序遍历:

void levelOrder(TreeNode *T){
        BTNode *p;
        SqQueue *qu;
        QueueInit(qu);//初始化队列
        Queueadd(qu,T);//根节点入队
        while(!QueueEmpty(qu)){//队不为空,循环
            QueueDelet(qu,p)//出队结点p,p指向当前出队的结点
            visi(p);//访问结点p
            if(p->lchild != NULL) Queueadd(qu,p->lchild);//根节点入队
            if(p->rchild != NULL) Queueadd(qu,p->lchild);//根节点入队
        }
    }

二叉树的建立

在已知某种遍历顺序时候,我们可以通过遍历顺序创建二叉树;
首先是先序创建,获得了二叉树的结点信息,建立了二叉树的存储结构,在建立二叉树的过程中按照先序方式建立(只知道先序序列是不够的,还要知道空结点的位置才能得到唯一的二叉树,这里用'#'代表空结点):

//先序创建二叉树
bool DLRCreatBiTree(BiTree &T){
    cin >> ch;
    if(ch == "#") T = NULL;
    else{
        if(!T = (BiTNode*)malloc(sizeof(BiTNode)));//T = new BiNode;
            exit(-1);
        T->data = ch;//申请空间储存数据——>生成根节点
        DLRCreatBiTree(T->lchild);//构建左子树
        DLRCreatBiTree(T->rchild);//构建右子树
    }
    return true;
}

中序创建二叉树和后序创建爱你二叉树与先序创建也一般无二;
中序创建:

//中序创建二叉树
bool LDRCreatBiTree(BiTree &T){
    cin >> ch;
    if(ch == "#") T = NULL;
    else{
        LDRCreatBiTree(T->lchild);//构建左子树
        if(!T = (BiTNode*)malloc(sizeof(BiTNode)));//T = new BiNode;
            exit(-1);
        T->data = ch;//申请空间储存数据——>生成根节点
        LDRCreatBiTree(T->rchild);//构建右子树
    }
    return true;
}

后序创建:

bool LRDCreatBiTree(BiTree &T){
    cin >> ch;
    if(ch == "#") T = NULL;
    else{
        LRDCreatBiTree(T->lchild);//构建左子树
        LRDCreatBiTree(T->rchild);//构建右子树
        if(!T = (BiTNode*)malloc(sizeof(BiTNode)));//T = new BiNode;
            exit(-1);
        T->data = ch;//申请空间储存数据——>生成根节点
    }
    return true;
}

复制二叉树

复制算法也是使用递归来完成:

  • 如果是空树,递归结束
  • 否则申请新的结点空间,复制结点
    1. 递归复制左子树
    2. 递归复制右子树

当然是可以用多种遍历循序来完成复制的:
先序复制:

int DLRCopy(BiTree T,BiTree &newT){
    if(T == NULL){
        newT = NULL;
        return 0;
    }
    else {
        newT = (BiTNode*)malloc(sizeof(BiTNode));//newT = new BiTNode;
        newT->data = T->data;
        DLRcopy(T->lchild,newT->lchild);
        DLRcopy(T->rchild,newT->rchild);
    }
}

中序复制:

int LDRCopy(BiTree T,BiTree &newT){
    if(T == NULL){
        newT = NULL;
        return 0;
    }
    else {
        LDRcopy(T->lchild,newT->lchild);
        newT = (BiTNode*)malloc(sizeof(BiTNode));//newT = new BiTNode;
        newT->data = T->data;
        LDRcopy(T->rchild,newT->rchild);
    }
}

后序复制:

int LRDCopy(BiTree T,BiTree &newT){
    if(T == NULL){
        newT = NULL;
        return 0;
    }
    else {
        LRDcopy(T->lchild,newT->lchild);
        LRDcopy(T->rchild,newT->rchild);
        newT = (BiTNode*)malloc(sizeof(BiTNode));//newT = new BiTNode;
        newT->data = T->data;
    }
}

求二叉树的深度

求取二叉树的深度:

  • 如果是空树,则深度为0
  • 否则,递归计算左子树的深度为m,递归计算右子树的深度为n,二叉树的深度为m与n的较大者加1
int Depth(BiTree T){
    if*T == NULL) return 0;
    else {
        int m = Depth(T->lchild);
        int n = Depth(T->rchild);
        if(m > n) return(m + 1);
        else retrun (n + 1);
    }
}

计算二叉树结点总数

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

计算叶子结点的个数

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

线索二叉树(Threaded Binary Tree)

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

  • 通过遍历寻找——浪费时间
  • 再增设前驱,后继指针域——浪费空间
  • 利用二叉链表的空指针域 线索华——利用二叉链表的空指针域:
    如果某个结点的左孩子为空,则将空的左孩子的指针域改为指向其前驱;如果某个结点的右孩子为空,则将空的右孩子的指针域改为指向其后继;[这种改变指向的指针称为“”线索“”]

为了区分lchildhe==和rchild,对二叉链表中每个结点增设两个标志域ltag,rtag;并约定:

  • ltage = 0 lchild 指向该结点的左孩子
  • ltage = 1 lchild 指向该结点的前驱
  • rtage = 0 rchild 指向该结点的右孩子
  • rtage = 1 rchild 指向该结点的后继 增设一个头结点:ltag = 0,lchild之下部分根节点;rtag = 1rchild指向遍历序列中的最后一个结点(遍历序列中的第一个结点lc域和最后一个结点的rc域都指向头结点)

定义存储结构:

typedef steuct BiTHrNode{
    int data;
    bool ltag,rtag;
   steuct BiTHrNode *lchild,*rchild;
}BiThrNode,*BiThrTree;