数据结构与算法(Dart)之树(七)

245 阅读3分钟

树(Tree)是由多个节点(Node)的集合组成,每个节点又有多个与其关联的子节点(Child Node)。子节点就是直接处于节点之下的节点,而父节点(Parent Node)则位于节点直接关联的上方。树的根(Root)指的是一个没有父节点的单独的节点。

所有的树都呈现了一些共有的性质:

  • 只有一个根节点;
  • 除了根节点,所有节点都有且仅有一个父节点;
  • 无环。将任意一个节点作为起始节点,都不存在任何回到该起始节点的路径(正是前两个性质保证了无环的成立)。

二叉树及其性质

二叉树(Binary Tree): 每个节点最多有两个子节点,分别称为左子节点和右子节点。

  • 性质1:二叉树的第i层上至多有2i-1(i≥1)个节点。

  • 性质2:深度为h的二叉树中至多含有2h-1个节点。

  • 性质3:若在任意一棵二叉树中,有n0个叶子节点,有n2个度为2的节点,则必有n0=n2+1。

  • 性质4:具有n个节点的满二叉树深为log2n+1。

  • 性质5:若对一棵有n个节点的完全二叉树进行顺序编号(1≤i≤n),那么,对于编号为i(i≥1)的节点:

    • 当i=1时,该节点为根,它无双亲节点。

    • 当i>1时,该节点的双亲节点的编号为i/2。

    • 若2i≤n,则有编号为2i的左节点,否则没有左节点。

    • 若2i+1≤n,则有编号为2i+1的右节点,否则没有右节点。

截屏2024-02-17 上午10.37.41.png

完全二叉树

树中的结点按从上至下、从左到右的顺序进行编号

除了最后一层外,每一层的节点都是满的,并且最后一层的节点都尽可能地靠左排列。

若从上至下、从左至右编号,则编号为 i 的节点,其左子节点编号必为 2i,其右子节点编号必为 2i+1;其父节点的编号必为 i/2(i=1 时为根,除外)

满二叉树

每个节点都有 0 或 2 个子节点。

二叉树数组实现

使用数组(顺序表)实现二叉树。数组元素表示二叉树的节点, 利用数组的索引关系构建二叉树的结构。

一般来说,数组中节点的索引从 1 开始,那么给定节点的索引为 i,其左子节点的索引为 2*i,右子节点的索引为 2*i + 1

import 'dart:core';

class BinaryTree {
  late List _trees;

  BinaryTree(int size) {
    _trees = List.generate(size, (index) => null);
  }

  // 插入节点
  void insert(int index, int value) {
    if (index > _trees.length - 1 || index < 1) {
      throw RangeError('Index out of bounds');
    }
    _trees[index] = value;
  }

  // 获取根节点
  int? getRoot() {
    if (_trees.isNotEmpty) {
      return _trees[1];
    }
    return null;
  }

  // 获取指定节点的左子节点
  int? getLeftChild(int index) {
    int leftChildIndex = 2 * index;
    if (leftChildIndex < _trees.length) {
      return _trees[leftChildIndex];
    }
    return null;
  }

  // 获取指定节点的右子节点
  int? getRightChild(int index) {
    int rightChildIndex = 2 * index + 1;
    if (rightChildIndex < _trees.length) {
      return _trees[rightChildIndex];
    }
    return null;
  }

  // 打印
  display() {
    print('_trees: $_trees');
  }
}

void main() {
  BinaryTree binaryTree = BinaryTree(10);
  binaryTree.insert(1, 1);
  binaryTree.insert(2, 2);
  binaryTree.insert(3, 3);
  binaryTree.insert(4, 4);
  binaryTree.insert(5, 5);

  print('Root: ${binaryTree.getRoot()}');
  print('Left child of root: ${binaryTree.getLeftChild(1)}');
  print('Right child of root: ${binaryTree.getRightChild(1)}');
}

二叉树队列实现

使用队列实现二叉树的构建、遍历和其他操作。队列是一种先进先出(FIFO)的数据结构,非常适合用于广度优先遍历(BFS)二叉树。

class TreeNode {
  int value;
  TreeNode? left;
  TreeNode? right;

  TreeNode(this.value);
}

class BinaryTree {
  TreeNode? root;

  // 插入节点
  void insert(int value) {
    root = _insertRec(root, value);
  }

  TreeNode _insertRec(TreeNode? root, int value) {
    if (root == null) {
      root = TreeNode(value);
      return root;
    }

    if (value < root.value) {
      root.left = _insertRec(root.left, value);
    } else if (value > root.value) {
      root.right = _insertRec(root.right, value);
    }

    return root;
  }

  // 广度优先遍历
  void bfs() {
    if (root == null) return;

    Queue<TreeNode> queue = Queue<TreeNode>();
    queue.add(root!);

    while (queue.isNotEmpty) {
      TreeNode current = queue.removeFirst();
      print(current.value);

      if (current.left != null) {
        queue.add(current.left!);
      }
      if (current.right != null) {
        queue.add(current.right!);
      }
    }
  }
}

void main() {
  BinaryTree binaryTree = BinaryTree();
  binaryTree.insert(4);
  binaryTree.insert(2);
  binaryTree.insert(6);
  binaryTree.insert(1);
  binaryTree.insert(3);
  binaryTree.insert(5);
  binaryTree.insert(7);

  binaryTree.bfs(); // 广度优先遍历
}

广度优先遍历(Breadth-First Traversal),简称 BFS,是一种用于遍历或搜索树或图的算法。在二叉树中,广度优先遍历是按层级顺序逐层遍历节点,从根节点开始,先访问根节点,然后逐层访问每一层的节点,直到遍历完所有节点为止。

参考资料

Python 数据结构与算法详解