二叉树遍历--coding

【经典面试题二】二叉树的递归与非递归遍历(前序、中序、后序) - SHERO_Vae - 博客园

浅谈数据结构-二叉树 - bldong - 博客园

二叉树的性质:什么是二叉树(包含满二叉树和完全二叉树)

二叉树遍历:从树的根节点出发,按照某种次序依次访问二叉树中所有的结点,使得每个结点被访问仅且一次。

这里有两个关键词:访问次序。

  三种遍历方式  访问节点的顺序是一致的,不同之处在于,有的遍历流程把访问到的节点暂存起来,达成某种条件后再将节点输出。约定, 根节点V, 其左孩子为L, 右孩子为R, 那么遍历顺序可以记为:

前序遍历:到达一个节点后,即刻输出该节点的值,并继续遍历其左右子树。

中序遍历:到达一个节点后,将其暂存,遍历完左子树后,再输出该节点的值,然后遍历右子树

后序遍历:到达一个节点后,将其暂存,遍历完左右子树后,再输出该节点的值

1、前序遍历

基本思想:先访问根结点,再先序遍历左子树,最后再先序遍历右子树即 根—左—右

前序递归遍历的代码实现,如下所示

//前序递归遍历
void PreOrderTraverse(BiTree t)
{
  //注意跳出条件
    if(t != NULL)
    {
       //注意访问语句顺序
        printf("%c ", t->data);
        PreOrderTraverse(t->lchild);
        PreOrderTraverse(t->rchild);
    }
}

1)前序非递归遍历:

对于任一结点p:

        a. 访问结点p,并将结点p入栈;

        b. 判断结点p的左孩子是否为空,若为空,则取栈顶结点并进行出栈操作,并将栈顶结点的右孩子置为当前的结点p,循环至a;若不为空,则将p的左孩子置为当前结点p;

        c. 直到p为空,并且栈为空,则遍历结束。

//前序非递归遍历
void preOrder2(BinTree *root)     //非递归前序遍历
{
    stack<BinTree*> s;
    BinTree *p=root;
    while(p!=NULL||!s.empty())
    {
	    // 现将左子树输出并且压入栈,直到左子树为null
        while(p!=NULL)           //若当前的节点非空
        {
            cout<<p->data<<" ";  //则输出该节点的值
            s.push(p);           //该节点压入栈中   
            p=p->lchild;         // 我们继续向左子树前进
        }
		
		// 取栈顶结点并进行出栈操作,并将栈顶结点的右孩子置为当前的结点p
        if(!s.empty())
        {
            p=s.top();
            s.pop();
            p=p->rchild;
        }
    }
}

2、中序遍历

基本思想:先中序遍历左子树,然后再访问根结点,最后再中序遍历右子树即 左—根—右

中序遍历迭代代码

//中序递归遍历
void InOrderTraverse(BiTree t)
{
    if(t != NULL)
    {
        InOrderTraverse(t->lchild);
        printf("%c ", t->data);
        InOrderTraverse(t->rchild);
    }
}

2)中序非递归遍历

    根据中序遍历的顺序,对于任一结点,优先访问其左孩子,而左孩子结点又可以看做一个根结点,然后继续访问其左孩子结点,直到遇到左孩子结点为空的结点才停止访问,然后按相同的规则访问其右子树。其处理过程如下:

       对于任一结点:

       a. 若其左孩子不为空,则将p入栈,并将p的左孩子设置为当前的p,然后对当前结点再进行相同的操作;

       b. 若其左孩子为空,则取栈顶元素并进行出栈操作,访问该栈顶结点,然后将当前的p置为栈顶结点的右孩子;

       c. 直到p为空并且栈为空,则遍历结束。

与先序遍历类似,唯一区别是到达当前节点时  并不直接输出该节点。

void inOrder2(BinTree *root)      //非递归中序遍历
{
    stack<BinTree*> s;
    BinTree *p=root;
    while(p!=NULL||!s.empty())
    {
        while(p!=NULL)
        {
            s.push(p);
            p=p->lchild;
        }
        if(!s.empty())
        {
            p=s.top();
            cout<<p->data<<" ";
            s.pop();
            p=p->rchild;
        }
    }
}

3、后序遍历

基本思想:先后序遍历左子树,然后再后序遍历右子树,最后再访问根结点即左—右—根。

后序递归遍历代码实现,如下所示。

//后序递归遍历
void PostOrderTraverse(BiTree t)
{
    if(t != NULL)
    {
        PostOrderTraverse(t->lchild);
        PostOrderTraverse(t->rchild);
        printf("%c ", t->data);
    }
}

  后序遍历的非递归实现是三种遍历方式中最难的一种。因为在后序遍历中,要保证左孩子和右孩子都已被访问,并且左孩子在右孩子之前访问才能访问根结点,这就为流程控制带来了难题。下面介绍一种思路。

 思路1:要保证根结点在左孩子和右孩子访问之后才能访问,因此对于任一结点p,先将其入栈。若p不存在左孩子和右孩子,则可以直接访问它,或者p存在左孩子或右孩子,但是其左孩子和右孩子都已经被访问过了,则同样可以直接访问该结点。若非上述两种情况,则将p的右孩子和左孩子依次入栈,这样就保证了每次取栈顶元素的时候,左孩子在右孩子之前别访问,左孩子和右孩子都在根结点前面被访问。

void postOrder3(BinTree *root)     //非递归后序遍历
{
    stack<BinTree*> s;
    BinTree *cur;                      //当前结点
    BinTree *pre=NULL;                 //前一次访问的结点
    s.push(root);
    while(!s.empty())
    {
        cur=s.top();
        if((cur->lchild==NULL&&cur->rchild==NULL)||
           (pre!=NULL&&(pre==cur->lchild||pre==cur->rchild)))
        {
            cout<<cur->data<<" ";  //如果当前结点没有孩子结点或者孩子节点都已被访问过
              s.pop();
            pre=cur;
        }
        else
        {
            if(cur->rchild!=NULL)
                s.push(cur->rchild);
            if(cur->lchild!=NULL)
                s.push(cur->lchild);
        }
    }
}

思路2::对于任一结点P,将其入栈,然后沿其左子树一直往下搜索,直到搜索到没有左孩子的结点,此时该结点出现在栈顶,但是此时不能将其出栈并访问, 因此其右孩子还为被访问。所以接下来按照相同的规则对其右子树进行相同的处理,当访问完其右孩子时,该结点又出现在栈顶,此时可以将其出栈并访问。这样就 保证了正确的访问顺序。可以看出,在这个过程中,每个结点都两次出现在栈顶,只有在第二次出现在栈顶时,才能访问它。因此需要多设置一个变量标识该结点是 否是第一次出现在栈顶。

void postOrder2(BinTree *root)    //非递归后序遍历
{
    stack<BTNode*> s;
    BinTree *p=root;
    BTNode *temp;
    while(p!=NULL||!s.empty())
    {
        while(p!=NULL)              //沿左子树一直往下搜索,直至出现没有左子树的结点
        {
            BTNode *btn=(BTNode *)malloc(sizeof(BTNode));
            btn->btnode=p;
            btn->isFirst=true;
            s.push(btn);
            p=p->lchild;
        }
        if(!s.empty())
        {
            temp=s.top();
            s.pop();
            if(temp->isFirst==true)     //表示是第一次出现在栈顶
             {
                temp->isFirst=false;
                s.push(temp);
                p=temp->btnode->rchild;
            }
            else                        //第二次出现在栈顶
             {
                cout<<temp->btnode->data<<" ";
                p=NULL;
            }
        }
    }
}

4、二叉树最大路径和

LeetCode:力扣

5、二叉树的最大和最小深度

力扣

力扣

6、二叉树最长路径

力扣

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值