在之前的文章中我们有讲过树的相关知识,例如,树的概念、深度优先遍历和广度优先遍历。这篇文章讲述了一个特殊的树——二叉树。
什么是二叉树
二叉树是每个节点最多只能有两个子节点的树,如下图所示:
一个二叉树具有以下几个特质:
要计算在每层有多少个点,可以依据公式2^(i-1)个(i是树的第几层);
如果这颗二叉树的深度为k,那二叉树最多有2^k-1个节点;
在一个非空的二叉树中,若使用n0表示叶子节点的个数,n2是度为2的非叶子节点的个数,那么两者满足关系n0 = n2 + 1。
满二叉树
如果在一个二叉树中,除了叶子节点,其余的节点的每个度都是2,则说明该二叉树是一个满二叉树,
如下图所示:
满二叉树除了满足普通二叉树特质,还具有如下几个特质:
每个层节点公式2^(n-1)(n为层数);
深度为k的满二叉树一定存在2^k-1个节点,叶子节点的个数为2^(k-1);
具有n个节点的满二叉树的深度为log_2^(n+1)。
完全二叉树
如果一个二叉树去掉最后一次层是满二叉树,且最后一次的节点是依次从左到右分布的,则这个二叉树是一个完全二叉树,
如下图所示:
二叉树的存储
存储二叉树的常见方式分为两种,一种是使用数组存储,另一种使用链表存储。
数组存储
使用数组存储二叉树,如果遇到完全二叉树,存储顺序从上到下,从左到右,如下图所示:
如果是一个非完全二叉树,如下图所示:
需要先将其转换为完全二叉树,然后在进行存储,如下图所示:
可以很明显的看到存储空间的浪费。
链表存储
使用链表存储通常将二叉树中的分为3个部分,如下图:
这三个部分依次是左子树的引用,该节点包含的数据,右子树的引用,存储方式如下图所示:
与二叉树相关的算法
以下算法中遍历用到的树如下:
// tree.js const bt = { val: 'A', left: { val: 'B', left: { val: 'D', left: null, right: null }, right: { val: 'E', left: null, right: null }, }, right: { val: 'C', left: { val: 'F', left: { val: 'H', left: null, right: null }, right: { val: 'I', left: null, right: null }, }, right: { val: 'G', left: null, right: null }, }, } module.exports = bt
深度优先遍历
二叉树的深度优先遍历与树的深度优先遍历思路一致,思路如下:
访问根节点;
访问根节点的left
访问根节点的right
重复执行第二三步
实现代码如下:
const bt = { val: 'A', left: { val: 'B', left: { val: 'D', left: null, right: null }, right: { val: 'E', left: null, right: null }, }, right: { val: 'C', left: { val: 'F', left: { val: 'H', left: null, right: null }, right: { val: 'I', left: null, right: null }, }, right: { val: 'G', left: null, right: null }, }, } function dfs(root) { if (!root) return console.log(root.val) root.left && dfs(root.left) root.right && dfs(root.right) } dfs(bt) /** 结果 A B D E C F H I G */
广度优先遍历
实现思路如下:
创建队列,把根节点入队
把对头出队并访问
把队头的left和right依次入队
重复执行2、3步,直到队列为空
实现代码如下:
function bfs(root) { if (!root) return const queue = [root] while (queue.length) { const node = queue.shift() console.log(node.val) node.left && queue.push(node.left) node.right && queue.push(node.right) } } bfs(bt) /** 结果 A B C D E F G H I */
先序遍历
二叉树的先序遍历实现思想如下:
访问根节点;
对当前节点的左子树进行先序遍历;
对当前节点的右子树进行先序遍历;
如下图所示:
递归方式实现如下:
const bt = require('./tree') function preorder(root) { if (!root) return console.log(root.val) preorder(root.left) preorder(root.right) } preorder(bt) /** 结果 A B D E C F H I G */
迭代方式实现如下:
// 非递归版 function preorder(root) { if (!root) return // 定义一个栈,用于存储数据 const stack = [root] while (stack.length) { const node = stack.pop() console.log(node.val) /* 由于栈存在先入后出的特性,所以需要先入右子树才能保证先出左子树 */ node.right && stack.push(node.right) node.left && stack.push(node.left) } } preorder(bt) /** 结果 A B D E C F H I G */
中序遍历
二叉树的中序遍历实现思想如下:
对当前节点的左子树进行中序遍历;
访问根节点;
对当前节点的右子树进行中序遍历;
如下图所示:
递归方式实现如下:
const bt = require('./tree') // 递归版 function inorder(root) { if (!root) return inorder(root.left) console.log(root.val) inorder(root.right) } inorder(bt) /** 结果 D B E A H F I C G */
迭代方式实现如下:
// 非递归版 function inorder(root) { if (!root) return const stack = [] // 定义一个指针 let p = root // 如果栈中有数据或者p不是null,则继续遍历 while (stack.length || p) { // 如果p存在则一致将p入栈并移动指针 while (p) { // 将 p 入栈,并以移动指针 stack.push(p) p = p.left } const node = stack.pop() console.log(node.val) p = node.right } } inorder(bt) /** 结果 D B E A H F I C G */
后序遍历
二叉树的后序遍历实现思想如下:
对当前节点的左子树进行后序遍历;
对当前节点的右子树进行后序遍历;
访问根节点;
如下图所示:
递归方式实现如下:
const bt = require('./tree') // 递归版 function postorder(root) { if (!root) return postorder(root.left) postorder(root.right) console.log(root.val) } postorder(bt) /** 结果 D E B H I F G C A */
迭代方式实现如下:
// 非递归版 function postorder(root) { if (!root) return const outputStack = [] const stack = [root] while (stack.length) { const node = stack.pop() outputStack.push(node) // 这里先入left需要保证left后出,在stack中后出,就是在outputStack栈中先出 node.left && stack.push(node.left) node.right && stack.push(node.right) } while (outputStack.length) { const node = outputStack.pop() console.log(node.val) } } postorder(bt) /** 结果 D E B H I F G C A */
关于二叉树及各种遍历算法相关内容已讲述完毕。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/127772.html
摘要:本篇主要介绍二叉树的概念二叉树的表示二叉树的操作三种遍历方式实现求二叉树的子树求节点的父节点二叉树高度,可能是考试中的,也可能是面试中的。通常二叉树的遍历根据根节点的遍历次序分为先根遍历中根遍历后根遍历。 声明:码字不易,转载请注明出处,欢迎文章下方讨论交流。 前言:Java数据结构与算法专题会不定时更新,欢迎各位读者监督。本篇主要介绍二叉树的概念、二叉树的表示、二叉树的操作(三种遍历...
摘要:左子树的加法运算结果为,右子树的减法运算结果为。如图,该图说明了随着每个新的字符被读入后该解析树的内容和结构。使函数走向基点的递归过程就是调用求值函数计算当前节点的左子树右子树的值。最后,我们将在图中创建的解析树上遍历求值。 解析树 完成树的实现之后,现在我们来看一个例子,告诉你怎么样利用树去解决一些实际问题。在这个章节,我们来研究解析树。解析树常常用于真实世界的结构表示,例如句子或数...
摘要:区块体则包括当前区块经过验证的区块创建过程中生成的所有交易记录。假如要验证区块结构图中交易,节点会通过向相邻节点索要通过消息包括从交易哈希值沿树上溯至区块头根哈希处的哈希序列即哈希节点称为认证路径来确认交易的存在性和正确性。 本文首发于深入浅出区块链社区原文链接:比特币区块结构Merkle树及简单支付验证分析原文已更新,请读者前往原文阅读 在比特币网络中,不是每个节点都有能力储存完整的...
摘要:题目链接题目分析在二叉树中,若两个叶子节点的层数相同,但具有不同的父节点,那么这两个节点互为节点。给定一个二叉树及两个节点,返回两个节点在二叉树中,是否互为节点。遍历完成后,直接判断数组中对应的值是否相同即可。 D76 993. Cousins in Binary Tree 题目链接 993. Cousins in Binary Tree 题目分析 在二叉树中,若两个叶子节点的层数相同...
阅读 547·2023-03-27 18:33
阅读 732·2023-03-26 17:27
阅读 630·2023-03-26 17:14
阅读 591·2023-03-17 21:13
阅读 521·2023-03-17 08:28
阅读 1801·2023-02-27 22:32
阅读 1292·2023-02-27 22:27
阅读 2178·2023-01-20 08:28