3

**二叉树是一种数据结构。其特点是:

1.由一系列节点组成,具有层级结构。每个节点的特性包含有节点值、关系指针。节点之间存在对应关系。

2.树中存在一个没有父节点的节点,叫做根节点。树的末尾存在一系列没有子节点的节点,称为叶子节点。其他可以叫做中间节点。

3.树的根节点位于第一层,层级数越大,节点位置越深,层级数也叫做树高。

**排序二叉树为二叉树的一种类型,其特点是:

1.节点分为左右子树。

2.在不为空的情况下,左子树子节点的值都小于父节点的值。

3.在不为空的情况下,右子树子节点的值都大于父节点的值。

4.每个节点的左右子树都按照上述规则排序。
image.png

(一)生成二叉树

// 将值生成节点,节点包括:节点值、左指针、右指针
class Node {
  constructor(key) {
    this.key = key;
    this.left = null;
    this.right = null;
  }
}
// 二叉树
class BinaryTree {
  constructor() {
    this.root = null; // 根节点
  }
  // 插入,插入的是一个节点,所以应该先把值生成节点(包括节点值,左指针,右指针)
  insert(key) {
    const newNode = new Node(key);
    // 如果根节点为空,则新节点作为根节点,否则在根节点下进行插入
    if (this.root === null) {
      this.root = newNode;
    }
    this.insertNode(this.root, newNode);
  }

  // 有根节点的情况下插入值
  insertNode(root, newNode) {
    if (newNode.key < root.key) {
      // 进入左子树
      if (root.left === null) {
        // 左子树为空
        root.left = newNode;
      } else {
        // 左子树已存在
        this.insertNode(root.left, newNode);
      }
    } else if (newNode.key > root.key) {
      // 进入右子树
      if (root.right === null) {
        // 右子树为空
        root.right = newNode;
      } else {
        // 右子树已存在
        this.insertNode(root.right, newNode);
      }
    }
  }
}
const binaryTree = new BinaryTree();
var keys = [19, 8, 15, 24, 45, 12, 5];
keys.forEach((key) => binaryTree.insert(key));
console.log(binaryTree);

image.png

(二)二叉树的遍历

1. 中序遍历

(1)递归方法

// 中序遍历:递归方法
var inorderTraversal = function (root) {
  var result = [];
  pushRoot(root, result);
  return result;
};
function pushRoot(root, result) {
  if (root !== null) {
    // 左
    if (root.left !== null) {
      pushRoot(root.left, result);
    }
    // 中
    result.push(root.key);
    // 右
    if (root.right !== null) {
      pushRoot(root.right, result);
    }
  }
}

// 注意这里传入的是binaryTree.root,而不是binaryTree
inorderTraversal(binaryTree.root); // [5, 8, 12, 15, 19, 24, 45]

(2)非递归方法(栈)

var inorderTraversal = function (root) {
  let result = [];
  let stack = [];
  let node = root;
  while (node !== null || stack.length !== 0) {
    if (node !== null) {
      stack.push(node);
      node = node.left; // 遍历左,存入栈中
    } else {
      debugger;
      // node===null时说明左边没有了,那么栈顶就是最左边的(最小的)
      node = stack.pop();
      result.push(node.key);
      node = node.right; //看右边有没有
    }
  }
  console.log(result); // [5, 8, 12, 15, 19, 24, 45]

  return result;
};

inorderTraversal(binaryTree.root);

2.前序遍历

(1)递归方法

// 前序遍历 递归
var preOrderTraversal = function (root) {
  var res = [];
  preOrder(root, res);
  return res;
};
function preOrder(root, res) {
  if (root !== null) {
    // 中
    res.push(root.key);
    // 左
    preOrder(root.left, res);
    // 右
    preOrder(root.right, res);
  }
}
console.log(preOrderTraversal(binaryTree.root)); // [19, 8, 5, 15, 12, 24, 45]

(2)非递归方法(栈)

var preOrderTraversal = function (root) {
  var res = [];
  var stack = [];
  var node = root;
  while (node !== null || stack.length !== 0) {
    if (node !== null) {
      res.push(node.key);
      stack.push(node);
      node = node.left;
    } else {
      node = stack.pop();
      node = node.right;
    }
  }
  return res
};
console.log(preOrderTraversal(binaryTree.root)) // [19, 8, 5, 15, 12, 24, 45]

3.后序遍历

(1)递归方法

var afterOrderTraversal = function (root) {
  var res = [];
  afterOrder(root, res);
  return res;
};
function afterOrder(root, res) {
  if (root !== null) {
    // 左
    afterOrder(root.left, res);
    // 右
    afterOrder(root.right, res);
    // 中
    res.push(root.key);
  }
}
console.log(afterOrderTraversal(binaryTree.root)); // [5, 12, 15, 8, 45, 24, 19]

(2)非递归方法

首先要搞清楚先序、中序、后序的非递归算法共同之处:用栈来保存先前走过的路径,以便可以在访问完子树后,可以利用栈中的信息,回退到当前节点的双亲节点,进行下一步操作。

后序遍历的非递归算法是三种顺序中最复杂的,原因在于,后序遍历是先访问左、右子树,再访问根节点,而在非递归算法中,利用栈回退到时,并不知道是从左子树回退到根节点,还是从右子树回退到根节点,如果从左子树回退到根节点,此时就应该去访问右子树,而如果从右子树回退到根节点,此时就应该访问根节点。所以相比前序和后序,必须得在压栈时添加信息,以便在退栈时可以知道是从左子树返回,还是从右子树返回进而决定下一步的操作。


MandyShen
166 声望21 粉丝