js二叉树

77 阅读6分钟

二叉树是计算机科学中的一种重要数据结构,广泛用于实现各种算法和数据处理任务。以下是关于二叉树的定义、原理、实现和应用的详细介绍:

1. 二叉树的定义

二叉树是一种树形结构,其中每个节点最多有两个子节点,分别称为左子节点和右子节点。二叉树的基本元素是节点,节点包含以下三个属性:

  • 值(value) :节点存储的数据。
  • 左子节点(left) :指向左子节点的引用。
  • 右子节点(right) :指向右子节点的引用。

一个二叉树要么为空(即不包含任何节点),要么有一个根节点,根节点的左子树和右子树也是二叉树。

2. 二叉树的原理

二叉树的核心原理是分治思想,即将一个大问题分解成若干个小问题递归求解。通过递归访问左子树和右子树,可以高效地进行查找、插入、删除等操作。

二叉树可以有很多种类,常见的有以下几种:

  • 满二叉树:每个节点要么是叶子节点,要么有两个子节点。
  • 完全二叉树:树的所有层都是满的,只有最底层可以不满,但底层节点都靠左排列。
  • 平衡二叉树:一种二叉树,其中任何节点的两个子树的高度差不超过1。
  • 二叉搜索树(BST) :一种特殊的二叉树,其中左子树的值都小于根节点,右子树的值都大于根节点。

3. 二叉树的实现

在 JavaScript 中,可以使用类来实现一个二叉树的结构:

二叉树节点的定义

class TreeNode {
  constructor(value) {
    this.value = value;    // 节点的值
    this.left = null;      // 左子节点
    this.right = null;     // 右子节点
  }
}

二叉树的实现

class BinaryTree {
  constructor() {
    this.root = null;      // 树的根节点
  }

  // 插入一个新值到二叉树中
  insert(value) {
    const newNode = new TreeNode(value);
    
    if (!this.root) {
      this.root = newNode;
      return;
    }

    let currentNode = this.root;
    
    while (true) {
      if (value < currentNode.value) {
        if (!currentNode.left) {
          currentNode.left = newNode;
          return;
        }
        currentNode = currentNode.left;
      } else {
        if (!currentNode.right) {
          currentNode.right = newNode;
          return;
        }
        currentNode = currentNode.right;
      }
    }
  }
 

  // 二叉树的前序遍历
  preOrderTraversal(node = this.root, result = []) {
    if (!node) return result;
    result.push(node.value);
    this.preOrderTraversal(node.left, result);
    this.preOrderTraversal(node.right, result);
    return result;
  }

  // 二叉树的中序遍历
  inOrderTraversal(node = this.root, result = []) {
    if (!node) return result;
    this.inOrderTraversal(node.left, result);
    result.push(node.value);
    this.inOrderTraversal(node.right, result);
    return result;
  }

  // 二叉树的后序遍历
  postOrderTraversal(node = this.root, result = []) {
    if (!node) return result;
    this.postOrderTraversal(node.left, result);
    this.postOrderTraversal(node.right, result);
    result.push(node.value);
    return result;
  }
}

使用示例

const tree = new BinaryTree();
tree.insert(10);
tree.insert(5);
tree.insert(20);
tree.insert(3);
tree.insert(7);

console.log('前序遍历: ', tree.preOrderTraversal());  // [10, 5, 3, 7, 20]
console.log('中序遍历: ', tree.inOrderTraversal());   // [3, 5, 7, 10, 20]
console.log('后序遍历: ', tree.postOrderTraversal()); // [3, 7, 5, 20, 10]

在二叉树的操作中,查找最小子节点查找最大子节点以及删除节点都是非常常见的操作。

1. 查找最小子节点

在二叉搜索树(BST)中,最小的节点总是位于最左边的子树上。因此,查找最小节点的过程就是不断沿着左子树遍历,直到没有左子节点为止。

findMin(node = this.root) {
  if (!node) return null;
  
  let currentNode = node;
  while (currentNode.left) {
    currentNode = currentNode.left;
  }
  
  return currentNode;
}

2. 查找最大子节点

与查找最小子节点类似,最大的节点总是位于最右边的子树上。因此,查找最大节点的过程就是不断沿着右子树遍历,直到没有右子节点为止。

javascript
findMax(node = this.root) {
  if (!node) return null;
  
  let currentNode = node;
  while (currentNode.right) {
    currentNode = currentNode.right;
  }
  
  return currentNode;
}

javascript

3. 删除节点

删除节点是二叉搜索树中相对复杂的操作,通常需要考虑三种情况:

  • 情况 1:要删除的节点是叶子节点,直接删除即可。
  • 情况 2:要删除的节点有一个子节点,删除该节点后,将子节点直接接到该节点的父节点上。
  • 情况 3:要删除的节点有两个子节点,需要找到该节点的后继节点(右子树中最小的节点),用后继节点替换该节点的值,然后递归删除后继节点。

下面是删除节点的实现:

delete(value, node = this.root) {
  if (!node) return null;

  if (value < node.value) {
    // 去左子树查找
    node.left = this.delete(value, node.left);
  } else if (value > node.value) {
    // 去右子树查找
    node.right = this.delete(value, node.right);
  } else {
    // 找到要删除的节点
    if (!node.left && !node.right) {
      // 情况1:节点是叶子节点
      return null;
    } else if (!node.left) {
      // 情况2:节点只有右子树
      return node.right;
    } else if (!node.right) {
      // 情况2:节点只有左子树
      return node.left;
    } else {
      // 情况3:节点有两个子节点
      const minRight = this.findMin(node.right);
      node.value = minRight.value; // 用后继节点替换当前节点的值
      node.right = this.delete(minRight.value, node.right); // 删除后继节点
    }
  }
  
  return node;
}

综合示例

将这些新功能整合进 BinaryTree 类中后,你可以这样使用:

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

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

  // 插入节点
  insert(value) {
    const newNode = new TreeNode(value);
    if (!this.root) {
      this.root = newNode;
      return;
    }
    let currentNode = this.root;
    while (true) {
      if (value < currentNode.value) {
        if (!currentNode.left) {
          currentNode.left = newNode;
          return;
        }
        currentNode = currentNode.left;
      } else {
        if (!currentNode.right) {
          currentNode.right = newNode;
          return;
        }
        currentNode = currentNode.right;
      }
    }
  }

  // 查找最小节点
  findMin(node = this.root) {
    if (!node) return null;
    let currentNode = node;
    while (currentNode.left) {
      currentNode = currentNode.left;
    }
    return currentNode;
  }

  // 查找最大节点
  findMax(node = this.root) {
    if (!node) return null;
    let currentNode = node;
    while (currentNode.right) {
      currentNode = currentNode.right;
    }
    return currentNode;
  }

  // 删除节点
  delete(value, node = this.root) {
    if (!node) return null;

    if (value < node.value) {
      node.left = this.delete(value, node.left);
    } else if (value > node.value) {
      node.right = this.delete(value, node.right);
    } else {
      if (!node.left && !node.right) {
        return null;
      } else if (!node.left) {
        return node.right;
      } else if (!node.right) {
        return node.left;
      } else {
        const minRight = this.findMin(node.right);
        node.value = minRight.value;
        node.right = this.delete(minRight.value, node.right);
      }
    }

    return node;
  }

  // 前序遍历
  preOrderTraversal(node = this.root, result = []) {
    if (!node) return result;
    result.push(node.value);
    this.preOrderTraversal(node.left, result);
    this.preOrderTraversal(node.right, result);
    return result;
  }

  // 中序遍历
  inOrderTraversal(node = this.root, result = []) {
    if (!node) return result;
    this.inOrderTraversal(node.left, result);
    result.push(node.value);
    this.inOrderTraversal(node.right, result);
    return result;
  }

  // 后序遍历
  postOrderTraversal(node = this.root, result = []) {
    if (!node) return result;
    this.postOrderTraversal(node.left, result);
    this.postOrderTraversal(node.right, result);
    result.push(node.value);
    return result;
  }
}

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

console.log('最小节点: ', tree.findMin());  // 3
console.log('最大节点: ', tree.findMax());  // 25

console.log('中序遍历: ', tree.inOrderTraversal()); // [3, 5, 7, 10, 15, 20, 25]

// 删除节点
tree.delete(20);
console.log('删除 20 后的中序遍历: ', tree.inOrderTraversal()); // [3, 5, 7, 10, 15, 25]

javascript

4. 代码功能解释

  • findMin: 通过不断遍历左子树找到最左边的节点,返回最小节点。
  • findMax: 通过不断遍历右子树找到最右边的节点,返回最大节点。
  • delete: 处理删除节点的三种情况:叶子节点、只有一个子节点、两个子节点的情况,并在删除两个子节点的情况下找到后继节点。

总结

通过这些方法,可以更全面地操作二叉搜索树,包括插入、查找、遍历、删除等。

4. 二叉树的应用

二叉树在计算机科学中的应用非常广泛,包括:

  1. 二叉搜索树(Binary Search Tree,BST) :一种有序树结构,用于高效地进行查找、插入和删除操作。在最优情况下,时间复杂度为 O(log n)。
  2. 堆(Heap) :二叉树的一个特殊形式,用于实现优先队列。堆分为最大堆和最小堆,常用于排序算法(如堆排序)。
  3. 霍夫曼编码(Huffman Coding) :用于数据压缩的算法,霍夫曼树是一种带权路径长度最短的二叉树。
  4. 表达式树(Expression Tree) :用来表示数学表达式的二叉树,叶子节点表示操作数,内部节点表示操作符。
  5. 决策树:机器学习中的一种树模型,用于分类和回归任务。

总结

二叉树作为一种基础数据结构,具有灵活的节点连接方式和递归特性,适用于各种高效算法的实现。掌握二叉树的定义、实现和应用,是编程和算法设计中的重要一步。