放肆青春的博客
首页
前端
算法
网络
面试
技术
后端
运维
杂项
数据库
工具
网址
电脑
个人
文章
  • 分类
  • 标签
  • 归档
github (opens new window)
gitee (opens new window)

放肆青春

一个前端菜鸟的技术成长之路
首页
前端
算法
网络
面试
技术
后端
运维
杂项
数据库
工具
网址
电脑
个人
文章
  • 分类
  • 标签
  • 归档
github (opens new window)
gitee (opens new window)
  • 算法基础

    • 数据结构和算法
    • 算法blog
    • 算法基础
    • 算法思维
  • 算法汇总

    • 常见算法题
    • 穷举算法(枚举算法)
    • 递归
    • 二分法
    • 双指针

      • 双指针
      • 双指针算法题
      • 滑动窗口
      • 滑动窗口算法题
    • 回溯法

      • 回溯法
      • 回溯法算法题
    • 分治算法
    • 贪心算法

      • 贪心算法
      • 贪心算法题
    • 动态规划

      • 动态规划
      • 动态规划算法题
  • 排序算法

    • 排序算法
    • 1. 冒泡排序
    • 2. 选择排序
    • 3. 插入排序
    • 4. 希尔排序
    • 5. 归并排序
    • 6. 快速排序
    • 7. 堆排序
    • 8. 计数排序
    • 9. 桶排序
    • 10. 基数排序
  • 数据结构

    • 数组

      • 数组概览
      • 数组算法题
    • 字符串

      • 字符串概览
      • 字符串算法题
    • 树

      • 树概览
        • 树概览
        • 代码表示二叉树
          • 1. 链式存储法
          • 数组存储法(适用于完全二叉树)
        • 二叉树的遍历
          • 深度优先遍历(DFS)
          • 广度优先遍历(BFS)
          • 深度优先遍历(DFS)和广度优先遍历(BFS)区别
          • 实例题-四种遍历结果
      • 树算法题
    • 链表

      • 链表概览
      • 双向链表
      • 链表算法题
    • 队列

      • 队列概览
      • 队列算法题
    • 堆

      • 堆概览
      • 堆算法题
    • 散列表(哈希表)

      • 散列表(哈希表)概览
      • 散列表(哈希表)算法题
    • 栈

      • 栈概览
      • 栈算法题
    • 图

      • 图概览
      • 图算法题
  • algorithm
放肆青春
2020-08-26

树概览

# 树概览

  • 树的高度

从下往上数的,等于根节点的高度(0,1,2,3)

  • 节点的深度

是根节点到这个节点所经历的边的个数

  • 节点的高度

是该节点到叶子节点的最长路径(边数)

  • demo 图

    image

  • 完全二叉树

深度为 h,除第 h 层外,其它各层 (1 ~ h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边

满二叉树一定是完全二叉树,但完全二叉树不一定是满二叉树

  • 满二叉树

一颗深度为 h 且有 2h-1个结点的二叉树

image

  • 平衡二叉树(AVL Tree)

二叉树中,每一个节点的左右子树的高度相差不能大于 1,称为平衡二叉树

# 代码表示二叉树

# 1. 链式存储法

二叉树的存储很简单,在二叉树中,我们看到每个节点都包含三部分:

  • 当前节点的 val
  • 左子节点 left
  • 右子节点 right

所以我们可以将每个节点定义为:

function Node(val) {
  // 保存当前节点 key 值
  this.val = val;
  // 指向左子节点
  this.left = null;
  // 指向右子节点
  this.right = null;
}
1
2
3
4
5
6
7
8

一棵二叉树可以由根节点通过左右指针连接起来形成一个树。

function BinaryTree() {
  let Node = function(val) {
    this.val = val;
    this.left = null;
    this.right = null;
  };
  let root = null;
}
1
2
3
4
5
6
7
8

# 数组存储法(适用于完全二叉树)

如果我们把根节点存放在位置 i=1 的位置,则它的左子节点位置为 2i = 2 ,右子节点位置为 2i+1 = 3 。

如果我们选取 B 节点 i=2 ,则它父节点为 i/2 = 1 ,左子节点 2i=4 ,右子节点 2i+1=5 。

以此类推,我们发现所有的节点都满足这三种关系:

  • 位置为 i 的节点,它的父节点位置为 i/2
  • 它的左子节点 2i
  • 它的右子节点 2i+1

因此,如果我们把完全二叉树存储在数组里(从下标为 1 开始存储),我们完全可以通过下标找到任意节点的父子节点。从而完整的构建出这个完全二叉树。这就是数组存储法。

数组存储法相对于链式存储法不需要为每个节点创建它的左右指针,更为节省内存。

image

# 二叉树的遍历

# 深度优先遍历(DFS)

  1. 前序遍历:访问根结点->遍历左子树->遍历右子树
// 递归实现
var preorderTraversal = function(root, array = []) {
  if (root) {
    array.push(root.val);
    preorderTraversal(root.left, array);
    preorderTraversal(root.right, array);
  }
  return array;
};
1
2
3
4
5
6
7
8
9
// 非递归实现
var preorderTraversal = function(root) {
  const result = [];
  const stack = [];
  let current = root;
  while (current || stack.length > 0) {
    while (current) {
      result.push(current.val);
      stack.push(current);
      current = current.left;
    }
    current = stack.pop();
    current = current.right;
  }
  return result;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  1. 中序遍历:遍历左子树->访问根结点->遍历右子树
// 递归实现
var inorderTraversal = function(root, array = []) {
  if (root) {
    inorderTraversal(root.left, array);
    array.push(root.val);
    inorderTraversal(root.right, array);
  }
  return array;
};
1
2
3
4
5
6
7
8
9
// 非递归实现
var inorderTraversal = function(root) {
  const result = [];
  const stack = [];
  let current = root;
  while (current || stack.length > 0) {
    while (current) {
      stack.push(current);
      current = current.left;
    }
    current = stack.pop();
    result.push(current.val);
    current = current.right;
  }
  return result;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  1. 后序遍历:遍历左子树->遍历右子树->访问根结点

image

// 递归实现
var postorderTraversal = function(root, array = []) {
  if (root) {
    postorderTraversal(root.left, array);
    postorderTraversal(root.right, array);
    array.push(root.val);
  }
  return array;
};
1
2
3
4
5
6
7
8
9
// 非递归实现:我们需要先遍历右子树,再遍历左子树
var postorderTraversal = function(root) {
  // 初始化数据
  const result = [];
  const stack = [];
  while (root || stack.length) {
    while (root) {
      stack.push(root);
      result.unshift(root.val); // 添加到数组开头
      root = root.right;
    }
    root = stack.pop();
    root = root.left;
  }
  return result;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

所谓前、中、后,不过是根的顺序,即也可以称为先根遍历、中根遍历、后根遍历

# 广度优先遍历(BFS)

层序遍历:从上往下一层一层遍历,每层按照从左往右遍历

  1. 递归实现

广度优先搜索:有一个队列,维护当前深度变量,保证入队列的时候左子节点在右子节点前面

var levelOrder = function(root) {
  const res = [];
  function traversal(root, depth) {
    if (root !== null) {
      // 如果这一层级没有的情况下,初始化为空数组
      if (!res[depth]) res[depth] = [];
      res[depth].push(root.val);
      // 保证左子节点在右子节点前面就行,这样入队列的情况下也能保持左右顺序
      traversal(root.left, depth + 1);
      traversal(root.right, depth + 1);
    }
  }
  traversal(root, 0);
  return res;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  1. 迭代实现
var levelOrder = function(root) {
  /* 非递归的实现方式 */
  let res = [];
  if (root == null) return res;
  let queue = [root];
  // while 循环控制从上向下一层层遍历
  while (queue.length) {
    let size = queue.length;
    // 记录这一层的节点值
    let level = [];
    // for 循环控制每一层从左向右遍历
    for (let i = 0; i < size; i++) {
      let cur = queue.shift();
      level.push(cur.val);
      if (cur.left != null) queue.push(cur.left);
      if (cur.right != null) queue.push(cur.right);
    }
    res.push(level);
  }
  return res;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 深度优先遍历(DFS)和广度优先遍历(BFS)区别

  1. 二叉树的深度优先遍历的非递归的通用做法是采用栈,广度优先遍历的非递归的通用做法是采用队列。

  2. 深度优先遍历:对每一个可能的分支路径深入到不能再深入为止,而且每个结点只能访问一次。要特别注意的是,二叉树的深度优先遍历比较特殊,可以细分为先序遍历、中序遍历、后序遍历。具体说明如下:

    先序遍历:对任一子树,先访问根,然后遍历其左子树,最后遍历其右子树。

    中序遍历:对任一子树,先遍历其左子树,然后访问根,最后遍历其右子树。

    后序遍历:对任一子树,先遍历其左子树,然后遍历其右子树,最后访问根。

广度优先遍历:又叫层次遍历,从上往下对每一层依次访问,在每一层中,从左往右(也可以从右往左)访问结点,访问完一层就进入下一层,直到没有结点可以访问为止。

  1. 深度优先搜素算法:不全部保留结点,占用空间少;有回溯操作(即有入栈、出栈操作),运行速度慢。

广度优先搜索算法:保留全部结点,占用空间大; 无回溯操作(即无入栈、出栈操作),运行速度快。

通常 深度优先搜索法不全部保留结点,扩展完的结点从数据库中弹出删去,这样,一般在数据库中存储的结点数就是深度值,因此它占用空间较少。

所以,当搜索树的结点较多,用其它方法易产生内存溢出时,深度优先搜索不失为一种有效的求解方法。

广度优先搜索算法,一般需存储产生的所有结点,占用的存储空间要比深度优先搜索大得多,因此,程序设计中,必须考虑溢出和节省内存空间的问题。

但广度优先搜索法一般无回溯操作,即入栈和出栈的操作,所以运行速度比深度优先搜索要快些

# 实例题-四种遍历结果

image

前序遍历结果:0137849256

后续遍历结果:7839415620

中序遍历结果:7381940526

层序遍历结果:0123456789



参考:前端进阶算法 7:小白都可以看懂的树与二叉树 (opens new window)

更新时间: 1/21/2022, 7:50:17 PM
字符串算法题
树算法题

← 字符串算法题 树算法题→

最近更新
01
前端权限管理
02-24
02
vue2指令
02-24
03
vue2 hook
02-24
更多文章>
Theme by Vdoing | Copyright © 2019-2022 放肆青春
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式