【数据结构】树

229 阅读4分钟

树的定义

有根节点、子节点、叶子节点构成。

树有高度(距离叶子节点的最远距离)、深度(距离根节点的距离)、层次。

二叉树

最多有 2 个子节点。

满二叉树:叶子节点全在最底层,除叶子节点外,每个节点下面都有左右 2 个子节点。上图就是一个满二叉树。

完全二叉树:叶子节点都在最地下两层,最后一层的叶子节点都靠左排列,且除了最后一层外,其他层的节点个数都是 2 个。

完全二叉树可以用数组存储。见下图。

二叉树的遍历

前序遍历:自己 → 左节点 → 右节点 的顺序打印出所有节点

中序遍历:左节点 → 自己 → 右节点 的顺序打印出所有节点

后续遍历:左节点 → 右节点 → 自己 的顺序打印出所有节点

  • 递归的方式

先构建一个二叉树:

class TreeNode {
  data;
  leftChild;
  rightChild;
  constructor(data) {
    this.data = data;
  }
}
 
function createBinaryTree(list) {
  let node = null;
  if (list === null || !(list && list.length)) {
    return null;
  }
  data = list.shift();
  if (data !== null) {
    node = new TreeNode(data);
    node.leftChild = createBinaryTree(list);
    node.rightChild = createBinaryTree(list);
  }
  return node;
}
 
const treeNode = createBinaryTree([3, 2, 9, 1, null, null, 5, null, null, 10, null, null, 8, null, 4]);

前序遍历

function preOrder(node) {
  if (node == null) return;
  console.log(node.data);
  preOrder(node.leftChild);
  preOrder(node.rightChild);
}

preOrder(treeNode);

中序遍历

function midOrder(node) {
  if (node == null) return;
  midOrder(node.leftChild);
  console.log(node.data);
  midOrder(node.rightChild);
}

midOrder(treeNode);

后序遍历

function postOrder(node) {
  if (node == null) return;
  postOrder(node.leftChild);
  postOrder(node.rightChild);
  console.log(node.data);
}

postOrder(treeNode);
  • 循环的方式

前序遍历

// 前序遍历 循环方法
function preOrderWithStack(node) {
  const arr = new Array();
  let treeNode = node;
  while (treeNode !== null || arr.length) {
    while (treeNode !== null) {
      console.log(treeNode.data); // 打印出自己
      arr.push(treeNode); // 将节点塞入栈中
      treeNode = treeNode.leftChild; // 拿到左节点,用于打印和 push 到栈中
    }
    // 第二个 while 循环结束后,左节点都访问结束了,所以接下去要访问右节点了
    if (arr.length) {
      treeNode = arr.pop(); // 从栈中取出节点
      treeNode = treeNode.rightChild; // 拿到右节点,用于打印 和 push 到栈中
    }
  }
}

中序遍历

// 中序遍历 循环方法
function midOrderWithStack(node) {
  const arr = new Array();
  let treeNode = node;
  while (treeNode !== null || arr.length) {
    while (treeNode !== null) {
      arr.push(treeNode); // 将节点塞入栈中
      treeNode = treeNode.leftChild;
    }
    if (arr.length) {
      treeNode = arr.pop(); // 从栈中取出节点
      console.log(treeNode.data);
      treeNode = treeNode.rightChild;
    }
  }
}

后序遍历

// 后序遍历 循环方法
function postOrderWithStack(node) {
  const arr1 = new Array();
  const arr2 = new Array();
  let treeNode = node;
  while (treeNode !== null || arr1.length) {
    while (treeNode !== null) {
      arr1.push(treeNode);
      arr2.push(treeNode);
      treeNode = treeNode.rightChild; // 拿到右节点,以此塞到 arr1 和 arr2 中
    }
    if (arr1.length) {
      treeNode = arr1.pop(); // 依次从 arr1 中取出节点,将每个节点的左节点取出,塞到 arr2 中。
      treeNode = treeNode.leftChild;
    }
  }
  while (arr2.length) {
    treeNode = arr2.pop(); // arr2 中的顺序就是后序遍历的相反顺序,用 pop 从栈中依次取出即可
    console.log(treeNode.data);
  }
}

二叉查找树

节点的左节点比它小,右节点比它大。见下图。

二叉查找树的查找、插入、删除

class TreeNode {
  data;
  leftChild = null;
  rightChild = null;
  constructor(data) {
    this.data = data;
  }
}

class BinarySearchTree {
  tree;
  constructor() {}
  // 插入
  insert(data) {
    if (!this.tree) {
      this.tree = new TreeNode(data);
      return;
    }
    let node = this.tree;
    while (node) {
      if (data > node.data) {
        if (!node.rightChild) {
          node.rightChild = new TreeNode(data);
          return;
        }
        node = node.rightChild;
      } else {
        if (!node.leftChild) {
          node.leftChild = new TreeNode(data);
          return;
        }
        node = node.leftChild;
      }
    }
  }
  // 查找
  find(data) {
    if (!this.tree) return null;
    let node = this.tree;
    while (node) {
      if (data === node.data) return node;
      else if (data > node.data) {
        node = node.rightChild;
      } else {
        node = node.leftChild;
      }
    }
  }
  // 删除
  delete(data) {
    if (!this.tree) return null;
    let node = this.tree; // 要删除的节点
    let nodeParent; // node 的父节点
    while (node && node.data !== data) {
      nodeParent = node;
      if (data > node.data) {
        node = node.rightChild;
      } else {
        node = node.leftChild;
      }
    }
    // 没找到要删除的节点 node,返回 null
    if (!node) return null;
    // 找到要删除的节点 node
    // 左右子节点都存在,寻找右子节点中最小的节点
    if (node.leftChild && node.rightChild) {
      let minNode = node.rightChild;
      let minNodeParent = node;
      while (minNode.leftChild) {
        minNodeParent = minNode;
        minNode = minNode.leftChild;
      }
      // 把右子节点中最小的节点的值替换到要删除的节点中
      node.data = minNode.data;
      // 下面要运用下面的规则删除这个最小的节点
      // node 就是当前右子节点中最小的节点,nodeParent 就是当前右子节点中最小节点的父节点
      node = minNode;
      nodeParent = minNodeParent;
    }
    // 下面是删除节点的规则
    // 不存在子节点
    let child = null;
    // 只存在左子节点
    if (node.leftChild) {
      child = node.leftChild;
    }
    // 只存在右子节点
    if (node.rightChild) {
      child = node.rightChild;
    }
    // 如果删除的是根节点,根节点是没有父节点的
    if (!nodeParent) {
      this.tree = child; // 将 tree 直接指向 要删除节点的 child,即删除了节点
    }
    // 删除的节点是它的父节点的左子节点,左子节点指向该删除节点的 child
    if (nodeParent.leftChild === node) {
      nodeParent.leftChild = child;
    }
    // 删除的节点是它的父节点的右子节点,右子节点指向该删除节点的 child
    if (nodeParent.rightChild === node) {
      nodeParent.rightChild = child;
    }
  }
}

const binarySearchTree = new BinarySearchTree();

const list = [33, 16, 13, 15, 18, 17, 25, 27, 19, 50, 34, 58, 51, 55, 66];
for (let item of list) {
  binarySearchTree.insert(item);
}

console.log(binarySearchTree);

const res = binarySearchTree.find(55);
console.log(res);

const res2 = binarySearchTree.delete(18);

console.log(binarySearchTree);