《算法笔记》9.2 二叉树的遍历

9.2 二叉树的遍历

二叉树的遍历是指通过一定顺序访问二叉树的所有节点。遍历方法一般有4种:先序遍历,中序遍历,后序遍历,层序遍历。其中,前三种一般使用DFS实现,而层序遍历一般使用BFS使用。
先序遍历顺序为:根节点->左子树->右子树
中序遍历顺序为:左子树->根节点->右子树
后序遍历顺序为:左子树->右子树->根节点

9.2.1 先序遍历

先序遍历顺序为:根节点->左子树->右子树

void preOrder( Node* root ){
    if( root == NULL ){
        return ;
    }
    
    cout << root->data <<endl;
    preOrder( root->lchild ) ;
    preOrder( root->rchild ) ;
}

先序遍历序列的性质:
对一颗二叉树的先序遍历序列,序列的第一个一定是根节点。

9.2.2 中序遍历

中序遍历顺序为:左子树->根节点->右子树

void preOrder( Node* root ){
    if( root == NULL ){
        return ;
    }
    
    preOrder( root->lchild ) ;
    cout << root->data <<endl;
    preOrder( root->rchild ) ;
}

中序遍历序列性质:
由于中序遍历总是把根节点放在左右子树中间,因此只要知道根节点,就可以通过根节点在中序遍历序列中的位置区分出左子树和右子树。

9.2.3 后序遍历

后序遍历顺序为:左子树->右子树->根节点

void preOrder( Node* root ){
    if( root == NULL ){
        return ;
    }
    
    preOrder( root->lchild ) ;
    preOrder( root->rchild ) ;
    cout << root->data <<endl;
}

后续遍历序列性质:
后序遍历总是把根节点放在最后访问,这和先序遍历恰好相反,因此对后序遍历序列来说,序列的最后一个一定是根节点。
总的来说,无论先序遍历序列还是后序遍历序列,都必须知道中序遍历序列才能唯一地确定一棵树。

9.2.4 层序遍历

层序遍历是指按层次的顺序从根节点向下逐层进行遍历。
image.png

//层序遍历
void LayerOrder( Node* root ){
    queue<Node*> q ;    //这里队列里是存地址
    q.push(root) ;      //根节点入队
    
    while( !q.empty() ){
        Node* top = q.front() ;     //取出队首元素
        q.pop() ;        //出队
        cout << top->data <<endl ;  //访问队首元素
        
        if( top->lchild != NULL ){
            q.push( top->lchild ) ;
        }
        
        if( top->rchild != NULL ){
            q.push( top->rchild ) ;
        }
    }

}

可以发现,这里使用的队列中的元素是node型而不是node型。这是因为队列中保存的只是原始元素的副本,因此如果队列中直接存放node型,当需要修改队首元素时,就会无法对原元素进行修改,故让队列中存放node型变量的地址,也就是node型变量。这就就可以通过访问地址去修改原元素。

另外还需要指出,很多题目中要求计算出每个节点所处的层次,这时就需要在二叉树节点的定义中添加一个记录层次layer的变量:

struct Node{
    int data ;     //数据域
    int layer ;     //层次
    Node* lchild ;  //左指针域
    Node* rchild ;  //右指针域
};

需要在根节点入队前就先令根节点的layer为1来表示根节点在第一层(视题目而定),之后再top->lchild和top->rchild入队前,把他们的层号都记为当前节点top的层号加1,即

//层序遍历
void LayerOrder( Node* root ){
    queue<Node*> q ;    //这里队列里是存地址
    root->layer = 1 ;   //根节点的层号为1
    q.push(root) ;      //根节点入队
    
    while( !q.empty() ){
        Node* top = q.front() ;     //取出队首元素
        q.pop() ;        //出队
        cout << top->data <<endl ;  //访问队首元素
        
        if( top->lchild != NULL ){
            top->lchild->layer = top->layer + 1 ;
            q.push( top->lchild ) ;
        }
        
        if( top->rchild != NULL ){
            top->rchild->layer = top->layer + 1 ;
            q.push( top->rchild ) ;
        }
    }

}

给定一棵二叉树的先序遍历序列和中序遍历序列,重建这颗二叉树。

假设已知先序序列为pre1、pre2…、preN,中序序列为ini,in2…inN,如图所示。那么由先序序列的性质可知,先序序列的第一个元素pre,是当前二叉树的根结点。再由中序序列的性质可知,当前二叉树的根结点将中序序列划分为左子树和右子树。因此,要做的就是在中序序列中找到某个结点ink,使得ink= pre1,这样就在中序序列中找到了根结点。易知左子树的结点个数numLeft=k-1,于是,左子树的先序序列区间就是[2,k],左子树的中序序列区间是[1,k-1];右子树的先序序列区间是[k+1,n],右子树的中序序列区间是[k+1,n],接着只需要往左子树和右子树进行递归构建二叉树即可。
image.png

事实上,如果递归过程中当前先序序列的区间为[preL,preR],中序序列的区间为[inL,inR],那么左子树的结点个数为numLeft=k-inL。这样左子树的先序序列区间就是[preL+1,preL + numLeft],左子树的中序序列区间是[inL,k-1];右子树的先序序列区间是[preL + numLeft + 1,preR],右子树的中序序列区间是[k+ 1,inR],如图所示。

image.png

那么,如果一直这样递归下去,什么时候是尽头呢?这个问题的答案是显然的,因为只要先序序列的长度小于等于0时,当前二叉树就不存在了,于是就能以这个条件作为递归边界。

//知道先序序列和中序序列,重建二叉树
//当先先序序列区间为[preL , preR] , 中序序列区间为[inL , inR] 
Node* create( int preL , int preR , int inL , int inR ){
    if( preL > preR ){  
        return ;    //先序序列长度小于等于0,直接返回
    }
    
    Node* root = new Node ;     //新建一个新的节点,用来存放当前二叉树的根节点
    root->data = pre[preL] ;    //新节点的数据域为根节点的值
    int k ; 
    for( int k = inL ; k<= inR ; k++ ){
        if( in[k] == pre[preL] ){
            break ;
        }
    }
    
    int numLeft = k - inL ; //左子树的个数
    
    //左子树的先序区间为[preL+1 ,preL+numLeft] , 中序区间为[inL , k-1]
    //返回左子树的根节点地址
    root->lchild = create( preL+1 , preL + numLeft , inL , k-1 ) ;

    //右子树的先序区间为[preL+numLeft+1 ,preR] , 中序区间为[k+1 , inR]
    //返回右子树的根节点地址
    root->rchild = create( preL+numLeft+1 , preR , k+1 , inR ) ;
    
    return root ;
}

中序序列可以与先序序列,后序序列,层序序列中的任意一个来构建唯一的二叉树,而后三者两两搭配或是三个一起上都无法构建唯一的二叉树。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值