【PHP解法==LeetCode94.144.145】二叉树的前中后序递归与非递归PHP解法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010365335/article/details/86776170

1.递归

(1)前序遍历

LeetCode 144题.二叉树的前序遍历
前序遍历:打印节点->左结点->右结点

class Solution {
    public $res = [];    //结果数组
    function preorderTraversal($root) {
        if($root){
        	$this->res[] = $root->val;				//打印节点
            $this->preorderTraversal($root->left);  //遍历左节点
            $this->preorderTraversal($root->right); //遍历右节点
        }
        return $this->res;
    }
}

(2)中序遍历

LeetCode 94题.二叉树的中序遍历
中序遍历:左结点->打印节点->右结点

class Solution {
    public $res = [];    //结果数组
    function inorderTraversal($root) {
        if($root){
            $this->inorderTraversal($root->left);  //遍历左节点
            $this->res[] = $root->val;			   //打印节点
            $this->inorderTraversal($root->right); //遍历右节点
        }
        return $this->res;
    }
}

(3)后序遍历

LeetCode 145题.二叉树的后序遍历
后序遍历:左结点->右结点->打印节点

class Solution {
    public $res = [];    //结果数组
    function postorderTraversal($root) {
        if($root){
            $this->postorderTraversal($root->left);   //遍历左节点
            $this->postorderTraversal($root->right);  //遍历右节点
            $this->res[] = $root->val;				  //打印节点
        }
        return $this->res;
    }
}

2.非递归(根据节点的访问顺序)

(1)前序遍历

根据前序遍历访问的顺序,优先访问根结点,然后再分别访问左孩子和右孩子。即对于任一结点,其可看做是根结点,因此可以直接访问,访问完之后,若其左孩子不为空,按相同规则访问它的左子树;当访问其左子树时,再访问它的右子树。
其处理过程如下:
对于任一结点Node:
1)访问结点Node,将该节点的值压入结果数组中,并将结点Node入栈;
2)遍历左子树,直至左孩子为空,取栈顶结点,并将栈顶结点的右孩子置为当前的结点Node,循环至1);
3)直到Node为NULL并且栈为空,则遍历结束。

class Solution {
    function preorderTraversal($root) {
        $res = [];		  //结果数组
        $stack = [];      //栈
        $node = $root;
        while($node || !empty($stack)){
            while($node){
                $res[] = $node->val;       //前序遍历:先访问,保存节点的值
                $stack[] = $node;		   //将节点压入栈中
                $node = $node->left;	   //遍历左子树
            }
            if(!empty($stack)){            //左子树遍历完毕
                $node = array_pop($stack); //节点出栈
                $node = $node->right;      //访问右结点
            }
        }
        return $res;
    }
}

(2)中序遍历

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

1)遍历左子树,将node入栈,直至左子树为空
2)若其左孩子为空,则取栈顶元素并进行出栈操作,访问该栈顶结点,将该节点的值压入结果数组中,然后将当前的Node置为栈顶结点的右孩子;
3)直到Node为NULL并且栈为空则遍历结束。

class Solution {
    function inorderTraversal($root) {
        $res = [];		  //结果数组
        $stack = [];      //栈
        $node = $root;
        while($node || !empty($stack)){
            while($node){
                $stack[] = $node;          //将节点压入栈中
                $node = $node->left;       //中序遍历:先遍历左子树
            }
            if(!empty($stack)){            //左子树遍历完毕
                $node = array_pop($stack); //节点出栈
                $res[] = $node->val;       //结果数组保存节点的值
                $node = $node->right;      //访问右结点
            }
        }
        return $res;
    }
}

(3)后序遍历

在后序遍历中,要保证左孩子和右孩子都已被访问并且左孩子在右孩子前访问才能访问根结点。
其处理过程如下:
对于任一结点Node:

1)将其入栈,遍历左子树,直到搜索到没有左孩子的结点,此时该结点出现在栈顶,但是此时不能将其出栈并访问, 因此其右孩子还未被访问
2)所以接下来按照相同的规则对其右子树进行相同的处理,判断其右子树是否为空或者右子树是否是最近访问的节点,否则将Node置为其右结点
3)当其没有右子树时,或者节点的右子树节点是最近访问过的节点(表示右子树已经访问过了),则将节点的值压入在结果数组中

class Solution {
    function postorderTraversal($root) {
        $res = [];		  //结果数组
        $stack = [];      //栈
        $recent = null;   //保存最近的节点
        $node = $root;
        while ($node || !empty($stack)) {
            while ($node) {
                $stack[] = $node;          //将节点压入栈中
                $node = $node->left;       //后序遍历:先遍历左子树
            }
            if(!empty($stack)){            //左子树遍历完毕
                $node = array_pop($stack); //节点出栈
                if($node->right && $node->right != $recent){
                //当$node->right == $recent时,意味着$node节点的右子树已经遍历完毕,可以将该节点弹出了
                    $stack[] = $node;
                    $node = $node->right;
                }else{
                    $res[] = $node->val;   //结果数组保存节点的值
                    $recent = $node;       //保存最近访问的节点
                    //将$node变量设置为null,程序下次循环将会直接进行出栈流程
                    //而不是将$node节点设为空节点
                    $node = null;          
                }
            }
        }
        return $res;
    }
}

3.非递归(利用堆栈模拟递归过程)

用栈模拟系统栈对指令的分析,该解法很方便对前中后序进行分析,简单修改代码即可实现前中后序的非递归解法,尤其是后序遍历的非递归算法。
原理:(以前序遍历为例)
前序遍历的顺序:先打印,再访问左孩子,再访问右孩子
因为栈是后进先出的,所以在推入栈的过程中,就应该反过来
先推入访问右孩子,再推入访问左孩子,最后打印节点的信息
因此可以用栈模拟系统栈中对这些指令的分析

(1)前序遍历

解法
1)初始化堆栈,结果数组
2)在循环中,每一次取出栈顶的元素,再去分析栈顶元素
3)判断元素中下标为1的布尔值,为true则将节点的值压入结果数组中,为false则进行后续将左右孩子压入栈中
4)若右孩子存在,则将右孩子初始化压入栈中
5)若左孩子存在,则将左孩子初始化压入栈中
6)将该节点的下标为1的布尔值该为true,既下次再推出该元素时直接打印

class Solution {
    function preorderTraversal($root) {
        $res = [];                  //结果数组
        $stack = [];				//堆栈[节点(对象),是否存入结果数组(bool)]
        $stack[] = [$root,false];	//初始化堆栈,保存根节点
        while (!empty($stack)) {
            $node = array_pop($stack);             //弹出栈顶元素
            $p = $node[0];                         //0下标为节点对象
            if($node[1]){                          //1下标为是否存入结果数组,进行判断
                $res[] = $p->val;                  //将节点的值存入结果数组
            }else{
                if($p->right)                      //访问右子树
                    $stack[] = [$p->right,false];  //压入栈中,初始为不打印
                if($p->left)                       //访问左子树
                    $stack[] = [$p->left,false];   //压入栈中,初始为不打印
                $stack[] = [$p,true];			   //将该节点设置为可打印
            }
        }
        return $res;
    }
}

(2)中序遍历

解法:与前序的区别,步骤456 =改为=》 465
1)初始化堆栈,结果数组
2)在循环中,每一次取出栈顶的元素,再去分析栈顶元素
3)判断元素中下标为1的布尔值,为true则将节点的值压入结果数组中,为false则进行后续将左右孩子压入栈中
4)若右孩子存在,则将右孩子初始化压入栈中
6)将该节点的下标为1的布尔值该为true,既下次再推出该元素时直接打印
5)若左孩子存在,则将左孩子初始化压入栈中

class Solution {
    function inorderTraversal($root) {
        $res = [];                  //结果数组
        $stack = [];				//堆栈[节点(对象),是否存入结果数组(bool)]
        $stack[] = [$root,false];	//初始化堆栈,保存根节点
        while (!empty($stack)) {
            $node = array_pop($stack);             //弹出栈顶元素
            $p = $node[0];                         //0下标为节点对象
            if($node[1]){                          //1下标为是否存入结果数组,进行判断
                $res[] = $p->val;                  //将节点的值存入结果数组
            }else{
                if($p->right)                      //访问右子树
                    $stack[] = [$p->right,false];  //压入栈中,初始为不打印
                $stack[] = [$p,true];			   //将该节点设置为可打印
                if($p->left)                       //访问左子树
                    $stack[] = [$p->left,false];   //压入栈中,初始为不打印
            }
        }
        return $res;
    }
}

(3)后序遍历

解法:与前序的区别,步骤456 =改为=》 645
1)初始化堆栈,结果数组
2)在循环中,每一次取出栈顶的元素,再去分析栈顶元素
3)判断元素中下标为1的布尔值,为true则将节点的值压入结果数组中,为false则进行后续将左右孩子压入栈中
6)将该节点的下标为1的布尔值该为true,既下次再推出该元素时直接打印
4)若右孩子存在,则将右孩子初始化压入栈中
5)若左孩子存在,则将左孩子初始化压入栈中

class Solution {
    function postorderTraversal($root) {
        $res = [];                  //结果数组
        $stack = [];				//堆栈[节点(对象),是否存入结果数组(bool)]
        $stack[] = [$root,false];	//初始化堆栈,保存根节点
        while (!empty($stack)) {
            $node = array_pop($stack);             //弹出栈顶元素
            $p = $node[0];                         //0下标为节点对象
            if($node[1]){                          //1下标为是否存入结果数组,进行判断
                $res[] = $p->val;                  //将节点的值存入结果数组
            }else{
             	$stack[] = [$p,true];			   //将该节点设置为可打印
                if($p->right)                      //访问右子树
                    $stack[] = [$p->right,false];  //压入栈中,初始为不打印
                if($p->left)                       //访问左子树
                    $stack[] = [$p->left,false];   //压入栈中,初始为不打印
            }
        }
        return $res;
    }
}

猜你喜欢

转载自blog.csdn.net/u010365335/article/details/86776170