每日一知识:详解二叉树的遍历(递归+迭代),javascript的写法

一.封装二叉树及插入操作

树:
在这里插入图片描述
树中每个元素都叫做节点,位于树顶部的节点叫作根节点,一个节点可以有祖先节点和子节点,根节点没有父节点。

二叉树:
字面意思:两个分叉的树。二叉树中的节点最多只能有两个子节点,上面的树其实就是二叉树

二叉搜索树(BST):
BinarySearchTree简称BST。二叉搜索树是二叉树的子集,不同的是,节点的左测节点的值比父节点的值大,节点的右侧节点的值比父节点小,上面那个图也是一个二叉搜索树。
BST的查找和插入的效率很高,类似于二分查找的思想,可以快速的定位元素

封装一个二叉搜索树:

// 树的结点
class Node {
    constructor(key) {
        this.key = key;
        this.left = null;
        this.right = null;
    }
}
// BST
class BinarySearchTree {
    constructor() {
        this.root = null;
    }
    //insert(key):向树中插入一个新的键
    insert(key) {
        //1.根据key创建Node节点
        const newNode = new Node(key);
        //2.如果原来的树是一颗空树
        if (this.root === null) {
            this.root = newNode;
        } else {
            //递归插入
            this.insertNode(this.root, newNode);
        }
        return this.root
    }
    
    // 插入的递归函数
    insertNode(root, node) {
        //判断新节点和当前节点的大小
        if (node.key < root.key) {
            if (root.left === null) {
                root.left = node
            } else {
           		// 插入节点比根节点小,就继续比较根节点的左节点
                this.insertNode(root.left, node)
            }
        } else {
            if (root.right === null) {
                root.right = node
            } else {
                this.insertNode(root.right, node)
            }
        }
    }

测试代码:

        const bst = new BinarySearchTree();
        //插入数据
        bst.insert(4)
        bst.insert(2)
        bst.insert(3)
        bst.insert(1)
        bst.insert(6)
        bst.insert(5)
        bst.insert(7)
        console.log(bst);

插入测试
至此,就封装好了一个简单的二叉搜索树,经测试,没有问题

二.递归遍历

接下来就进行遍历操作,首先封装递归的写法:

  • 先序遍历:根左右
    先序遍历简单地说就是,传入一个节点,如果节点为空就直接返回,不为空,就递归遍历它的子节点,直到遍历完
    先序遍历
    //先序遍历
    preOrderTraverse() {
    	// 传入根节点
    	// 简单地说就是,传入一个节点,如果节点为空就直接返回,不会空,就递归遍历它的子节点,直到遍历完
        this.preOrderTraverseNode(this.root)
    }
    //先序遍历的递归函数
    preOrderTraverseNode(node) {
    	// 递归终止的条件
        if (node === null) return
        console.log(node.key)
        // 处理子节点
        this.preOrderTraverseNode(node.left)
        this.preOrderTraverseNode(node.right)
    }
  • 中序遍历:左根右,中序遍历的顺序就是升序的顺序
    中序遍历,不同的是,先处理左节点,遍历完左节点,再处理根节点,右节点
    中序遍历
  //中序遍历
    inOrderTraverse() {
    	//传入根节点
        this.inOrderTraverseNode(this.root)
    }
	
    inOrderTraverseNode(node) {
        if (node === null) return
		//递归操作, 首先处理左节点,再处理根节点,再处理右节点
        this.inOrderTraverseNode(node.left)
        console.log(node.key)
        this.inOrderTraverseNode(node.right)
    }

  • 后序遍历:左右根
    后序遍历,先处理左节点,再处理右节点,最后处理根节点
    后序遍历
    //后序遍历
    postOrderTraverse() {
        this.postOrderTraverseNode(this.root)
    }

    postOrderTraverseNode(node) {
        if (node === null) return
		//递归操作, 首先处理左节点,再处理右节点,再处理根节点
        this.postOrderTraverseNode(node.left)
        this.postOrderTraverseNode(node.right)
        console.log(node.key)
    }

测试一下递归遍历:

        const bst = new BinarySearchTree();
        //插入数据
        bst.insert(4)
        bst.insert(2)
        bst.insert(3)
        bst.insert(1)
        bst.insert(6)
        bst.insert(5)
        bst.insert(7)

        //先序遍历
        bst.preOrderTraverse() // 4 2 1 3 6 5 7
        //中序遍历
        bst.inOrderTraverse()   // 1 2 3 4 5 6
        //后序遍历
        bst.postOrderTraverse()  // 1 3 2 5 7 6 4

三.迭代法

搞懂了基本的遍历,我们不难发现,二叉树的遍历采用的是深度优先的遍历,
因此迭代法的思路也是:深度优先遍历,比如先序遍历,需要先遍历完左子树,再遍历右子树。
迭代法需要借助一个辅助栈(其实递归跟栈联系的很紧密),有一个遍历一颗二叉树,并且是先左子树,再右子树的通用模板,如下:

	let Traversal = function(root) {
		//辅助栈
	    const stack = [];
	    //循环遍历二叉树
	    while (root || stack.length){
	      //非空节点入栈,深度优先遍历左子树
	      while(root){
	        stack.push(root);
	        root = root.left;
	      }
	      // 出现空节点说明遍历到最深处,出栈操作,遍历右子树
	      root = stack.pop();
	      root = root.right;
	    }
	    return res;
	};

在这里插入图片描述
结合图片发现这个遍历产生的整体压栈的顺序为:

  • 4,2,1入栈
  • 1出栈
  • 2出栈
  • 3入栈
  • 3出栈
  • 4出栈
  • 6入栈
  • 5入栈
  • 5出栈
  • 6出栈
  • 7入栈
  • 7出栈

上述入栈顺序排列:4、2、1、3、6、5、7 ,就是先序排列的顺序!
上述出栈顺序排列:1、2、3、4、5、6、7,就是中序排列的顺序!

具体的代码如下:

 // 先序遍历(迭代法)
    preTraverse(root) {
    	// 结果集
        const res = []
        // 辅助栈
        const stack = []
        // 循环遍历二叉树
        while (root || stack.length) {
        	// 非空节点入栈,深度优先遍历左子树
            while (root) {
            	// res 记录入栈顺序
                res.push(root.key)
                stack.push(root)
                root = root.left
            }
            // 出现空节点说明遍历到最深处,出栈操作,遍历右子树
            root = stack.pop()
            root = root.right
        }
        // 入栈顺序就是先序遍历的顺序
        return res
    }
    
    //中序遍历(迭代法)
    inTraverse(root) {
    	// 结果集
        const res = []
        // 辅助栈
        const stack = []
        // 循环遍历二叉树
        while (root || stack.length) {
            // 非空节点入栈,深度优先遍历左子树
            while (root) {
                stack.push(root)
                root = root.left
            }
            root = stack.pop()
            // res 记录出栈顺序
            res.push(root.key)
            // 出现空节点说明遍历到最深处,出栈操作,遍历右子树
            root = root.right
        }
		// 出栈顺序就是中序遍历的顺序
        return res
    }

测试代码:

        const bst = new BinarySearchTree();
        //插入数据
        bst.insert(4)
        bst.insert(2)
        bst.insert(3)
        bst.insert(1)
        bst.insert(6)
        bst.insert(5)
        bst.insert(7)
        
        //测试迭代遍历
        // 先序遍历
        bst.preTraverse()  // 4 2 1 3 6 5 7
        // 中序遍历
        bst.inTraverse()   // 1 2 3 4 5 6

后序遍历有点不太一样,但是思想是一样的,反着来,就可以了。传入根节点我们需要先遍历右子树,再遍历左子树。
在这里插入图片描述
入栈顺序为:

  • 4入栈
  • 6入栈
  • 7入栈
  • 5入栈
  • 2入栈
  • 3入栈
  • 1入栈
    将入栈顺序倒着排列,就是后序遍历的结果了:1、3、2、5、7、6、4
    //后序遍历 (迭代法)
    postTraverse(root) {
    	// 结果集
        const res = []
        // 辅助栈
        const stack = []
        // 循环遍历二叉树
        while (root || stack.length) {
            while (root) {
                stack.push(root)
                // 用unshift 在插入的时候直接插入到数组前面。当然也可以最后再reverse
                res = res.unshift(root.key)
                // 先遍历右子树
                root = root.right
            }
            // 出现空节点说明遍历到最深处,出栈操作,遍历左子树
            root = stack.pop()
            root = root.left
        }
        return res
    }

后序遍历测试代码:

        const bst = new BinarySearchTree();
        //插入数据
        bst.insert(4)
        bst.insert(2)
        bst.insert(3)
        bst.insert(1)
        bst.insert(6)
        bst.insert(5)
        bst.insert(7)
        
        // 后序遍历
        console.log(bst.postTraverse(bst.root));  // [1, 3, 2, 5, 7, 6, 4]

四.题目实战

leetCode第144题:先序遍历
leetCode第94题:中序遍历
leetCode第145题:后序遍历

四.全篇完整代码:

     // 树的结点
      class Node {
          constructor(key) {
              this.key = key;
              this.left = null;
              this.right = null;
          }
      }

      class BinarySearchTree {
          constructor() {
              //初始化实例对象时是空树
              this.root = null;
          }

          //insert(key):向树中插入一个新的键
          insert(key) {
              //1.根据key创建Node节点
              const newNode = new Node(key);
              //2.如果原来的树是一颗空树,当前节点赋给根节点
              if (this.root === null) {
                  //创建根节点
                  this.root = newNode;
              } else {
                  //递归插入
                  this.insertNode(this.root, newNode);
              }
              return this.root
          }
          // 插入的递归函数
          insertNode(root, node) {
              //判断新节点和当前节点的大小
              if (node.key < root.key) {
                  if (root.left === null) {
                      root.left = node
                  } else {
                      // 插入节点比根节点小,就继续比较根节点的左节点
                      this.insertNode(root.left, node)
                  }
              } else {
                  if (root.right === null) {
                      root.right = node
                  } else {
                      this.insertNode(root.right, node)
                  }
              }
          }

          //先序遍历
          preOrderTraverse() {
              // 传入根节点
              // 简单地说就是,传入一个节点,如果节点为空就直接返回,不会空,就递归遍历它的子节点,直到遍历完
              this.preOrderTraverseNode(this.root)
          }
          //先序遍历的递归函数
          preOrderTraverseNode(node) {
              // 递归终止的条件
              if (node === null) return
              console.log(node.key)
              // 处理子节点
              this.preOrderTraverseNode(node.left)
              this.preOrderTraverseNode(node.right)
          }




          //中序遍历
          inOrderTraverse() {
              //传入根节点
              this.inOrderTraverseNode(this.root)
          }
          //中序遍历的递归函数
          inOrderTraverseNode(node) {
              if (node === null) return
              //递归操作, 首先处理左节点,再处理根节点,再处理右节点
              this.inOrderTraverseNode(node.left)
              console.log(node.key)
              this.inOrderTraverseNode(node.right)
          }



          //后序遍历
          postOrderTraverse() {
              this.postOrderTraverseNode(this.root)
          }
          //后序遍历的递归函数
          postOrderTraverseNode(node) {
              if (node === null) return
              //递归操作, 首先处理左节点,再处理右节点,再处理根节点
              this.postOrderTraverseNode(node.left)
              this.postOrderTraverseNode(node.right)
              console.log(node.key)
          }


          // 先序遍历(迭代法)
          preTraverse(root) {
              // 结果集
              const res = []
              // 辅助栈
              const stack = []
              // 循环遍历二叉树
              while (root || stack.length) {
                  // 非空节点入栈,深度优先遍历左子树
                  while (root) {
                      // res 记录入栈顺序
                      res.push(root.key)
                      stack.push(root)
                      root = root.left
                  }
                  // 出现空节点说明遍历到最深处,出栈操作,遍历右子树
                  root = stack.pop()
                  root = root.right
              }
              // 入栈顺序就是先序遍历的顺序
              return res
          }

          //中序遍历(迭代法)
          inTraverse(root) {
              // 结果集
              const res = []
              // 辅助栈
              const stack = []
              // 循环遍历二叉树
              while (root || stack.length) {
                  // 非空节点入栈,深度优先遍历左子树
                  while (root) {
                      stack.push(root)
                      root = root.left
                  }
                  root = stack.pop()
                  // res 记录出栈顺序
                  res.push(root.key)
                  // 出现空节点说明遍历到最深处,出栈操作,遍历右子树
                  root = root.right
              }
              // 出栈顺序就是中序遍历的顺序
              return res
          }

          //后序遍历 (迭代法)
          postTraverse(root) {
              // 结果集
              const res = []
              // 辅助栈
              const stack = []
              // 循环遍历二叉树
              while (root || stack.length) {
                  while (root) {
                      stack.push(root)
                      // 用unshift 在插入的时候直接插入到数组前面。当然也可以最后再reverse
                      res.unshift(root.key)
                      // 先遍历右子树
                      root = root.right
                  }
                  // 出现空节点说明遍历到最深处,出栈操作,遍历左子树
                  root = stack.pop()
                  root = root.left
              }
              return res
          }
      }

      const bst = new BinarySearchTree();
      //插入数据
      bst.insert(4)
      bst.insert(2)
      bst.insert(3)
      bst.insert(1)
      bst.insert(6)
      bst.insert(5)
      bst.insert(7)
      console.log(bst);

      // 测试递归遍历
      // 先序遍历
      // bst.preOrderTraverse()  // 4 2 1 3 6 5 7
      // 中序遍历
      // bst.inOrderTraverse()   // 1 2 3 4 5 6
      // 后序遍历
      // bst.postOrderTraverse()  // 1 3 2 5 7 6 4

      
      // 测试迭代遍历
      // 先序遍历
      console.log(bst.preTraverse(bst.root));  // [4, 2, 1, 3, 6, 5, 7]
      // 中序遍历
      console.log(bst.inTraverse(bst.root));  // [1, 2, 3, 4, 5, 6, 7]
      // 后序遍历
      console.log(bst.postTraverse(bst.root));  // [1, 3, 2, 5, 7, 6, 4]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值