二叉树

119 阅读5分钟

定义

定义:二叉树(binary tree)是指树中节点的度不大于2的有序树,它是一种最简单且最重要的树。二叉树的递归定义为:二叉树是一棵空树,或者是一棵由一个根节点和两棵互不相交的,分别称作根的左子树和右子树组成的非空树;左子树和右子树又同样都是二叉树。

基本概念

1)结点的度:一个结点拥有子树的数目称为结点的度

2)树的度:树中所有结点的度的最大值

3)树的深度:也称为树的高度,树中所有结点的层次最大值称为树的深度

4)叶子结点:也称为终端结点,没有子树的结点或者度为零的结点

5)分支结点:也称为非终端结点,度不为零的结点称为非终端结点

树的分类和树的创建

1. 完全二叉树

特点:叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部,完全二叉树如图:

QQ截图20240326230502.png

2. 满二叉树

2.1 特点:除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树

3. 二叉搜索树

也称二叉查找树,或二叉排序树,简单说就是左子树上的数值小于树根上的值,树根的值小于右子树的值,二叉查找树如图: 二叉搜索树.png

3.1 特点

(1)若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;

(2) 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;

(3) 任意节点的左、右子树也分别为二叉查找树;

(4) 没有键值相等的节点。

4. 平衡二叉树(AVL树)

4.1 特点:平衡二叉树首先必须是二叉查找树,然后每个节点的左子树和右子树的高度差至多为1。如图:

平衡二叉树.png

5. 红黑树

6. B树和B+树

树的遍历

树的遍历可以分为广度遍历和深度遍历,广度遍历可以理解为按层次遍历,深度遍历按照遍历根节点的顺序,可以分为前序遍历,中序遍历,后序遍历。

递归遍历【前序遍历、后序遍历、后序遍历】

class Node {
  constructor(value) {
    this.value = value;
    this.left = null;
    this.right = null;
  }
}

class BinaryTree {
  constructor() {
    this.root = null;
  }

  // 插入节点
  insert(value) {
    const newNode = new Node(value);
    if (this.root === null) {
      this.root = newNode;
    } else {
      this.insertNode(this.root, newNode);
    }
  }

  insertNode(node, newNode) {
    if (newNode.value < node.value) {
      if (node.left === null) {
        node.left = newNode;
      } else {
        this.insertNode(node.left, newNode);
      }
    } else {
      if (node.right === null) {
        node.right = newNode;
      } else {
        this.insertNode(node.right, newNode);
      }
    }
  }

  // 先序遍历
  preorderTraversal(node, type) {
    if (node !== null) {
      console.log(node.value); // 输出当前节点值
      this.preorderTraversal(node.left, 'left'); // 遍历左子树
      this.preorderTraversal(node.right, 'right'); // 遍历右子树
    }
  }

  // 中序遍历
  inorderTraversal(node, type) {
    if (node !== null) {
      this.inorderTraversal(node.left, 'left'); // 遍历左子树
      console.log(node.value); // 输出当前节点值
      this.inorderTraversal(node.right, 'right'); // 遍历右子树
    }
  }

  // 后序遍历
  postorderTraversal(node) {
    if (node !== null) {
      this.postorderTraversal(node.left); // 遍历左子树
      this.postorderTraversal(node.right); // 遍历右子树
      console.log(node.value); // 输出当前节点值
    }
  }
  
  levelOrderTraversal = function() {
    var queue = [];
    var result = [];
    if (this.root !== null) {
        queue.push(this.root);
    }

    while (queue.length > 0) {
    var node = queue.shift();
    result.push(node.value);
    if (node.left !== null) {
    queue.push(node.left);
    }
    if (node.right !== null) {
    queue.push(node.right);
    }
   }
    return result;
  };
}

// 示例使用
const tree = new BinaryTree();
tree.insert(10);
tree.insert(5);
tree.insert(15);
tree.insert(3);
tree.insert(7);
tree.insert(13);
tree.insert(17);

console.log('tree:', tree);
console.log('Preorder traversal:');
tree.preorderTraversal(tree.root);

console.log('Inorder traversal:');
tree.inorderTraversal(tree.root);

console.log('Postorder traversal:');
tree.postorderTraversal(tree.root);

图解

前序遍历

常规操作:先根,再左,再右

fd780fcf40b04b5a9940cf59fe45528b.png 确定了遍历整体结构: cd5a0ebabb8f4a93b2ce89f4a1f1df8a.png

eaac87b60b4b40078e07ef07555cf5a8.png 确定了左子树中的整体结构

继续操作:

在这里插入图片描述

到此左子树中的遍历已经完成
在这里插入图片描述

在这里插入图片描述

确定右子树中的整体结构
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

前序结束
结果为:A B D F E C G I J H K
在这里插入图片描述

简单方法

从根结点出发向左开始绕二叉树一圈,经过的节点顺序即为先序遍历顺序
在这里插入图片描述
结果为:A B D F E C G I J H K

中序遍历

常规操作:先左,再根,再右

在这里插入图片描述

由此可确定根A在中间,下面分别确定左右子树中的顺序
在这里插入图片描述

左子树中:

在这里插入图片描述

由此确定了左子树中的顺序
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

右子树中:
对右子树进行整体划分
在这里插入图片描述
由此可以确定:
在这里插入图片描述
继续:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

中序遍历结束
结果为:F D B E A I G J C H K

简单方法

以该二叉树为例
在这里插入图片描述

结果为:F D B E A I G J C H K

后序遍历

常规操作:先左,再右,再根

在这里插入图片描述

将整体结构化分出来,后序遍历时根节点永远在最后一位

在这里插入图片描述

在这里插入图片描述

继续对左子树的结构进行划分

在这里插入图片描述

在这里插入图片描述

自此,左子树中遍历结束

在这里插入图片描述

在这里插入图片描述

划分右子树结构

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
后序遍历结束

结果为:F D E B I J G K H C A

非递归遍历【前序遍历、后序遍历、后序遍历】

class Node {
  constructor(value) {
    this.value = value;
    this.left = null;
    this.right = null;
  }
}
 
class BinaryTree {
  constructor() {
    this.root = null;
  }
 
  // 添加节点的方法
  insert(value) {
    const newNode = new Node(value);
 
    if (this.root === null) {
      this.root = newNode;
    } else {
      this.insertNode(this.root, newNode);
    }
  }
 
  insertNode(node, newNode) {
    if (newNode.value < node.value) {
      if (node.left === null) {
        node.left = newNode;
      } else {
        this.insertNode(node.left, newNode);
      }
    } else {
      if (node.right === null) {
        node.right = newNode;
      } else {
        this.insertNode(node.right, newNode);
      }
    }
  }
 
  // 非递归前序遍历
  preorderTraversal(callback) {
    const stack = [];
    let current = this.root;
 
    if (current !== null) {
      stack.push(current);
    }
 
    while (stack.length > 0) {
      current = stack.pop();
      callback(current.value);
 
      if (current.right !== null) {
        stack.push(current.right);
      }
 
      if (current.left !== null) {
        stack.push(current.left);
      }
    }
  }
}
 
// 使用示例
const tree = new BinaryTree();
tree.insert(10);
tree.insert(15);
tree.insert(5);
tree.insert(12);
tree.insert(18);
tree.insert(20);
 
tree.preorderTraversal((value) => console.log(value)); // 输出: 10, 5, 12, 15, 18, 20

相关算法延展

最小公共祖先

function lowestCommonAncestor(root, p, q) {
    if (!root || root === p || root === q) return root;
 
    let left = lowestCommonAncestor(root.left, p, q);
    let right = lowestCommonAncestor(root.right, p, q);
    let mid = root;
 
    if (left && right) return mid;  // 如果在左右子树均找到节点,则当前节点为最近公共祖先
    if (left) return left;          // 否则返回找到的一侧的节点
    if (right) return right;         // 否则返回找到的一侧的节点
    return null;                     // 如果都没有找到,返回null
} 

leetcode.cn/problems/lo…