二叉树数组表示

二叉树数组表示

在链表表示下,二叉树的存储单元为节点 TreeNode ,节点之间通过指针相连接。那我们能否用数组来表示二叉树呢?答案是肯定的。

表示完美二叉树

给定一棵完美二叉树,我们将所有节点按照层序遍历的顺序存储在一个数组中,则每个节点都对应唯一的数组索引。

根据层序遍历的特性,我们可以推导出父节点索引与子节点索引之间的"映射公式":若某节点的索引为 i ,则该节点的左子节点索引为 2i+1 ,右子节点索引为 2i+2

映射公式的角色相当于链表中的节点引用(指针)。给定数组中的任意一个节点,我们都可以通过映射公式来访问它的左(右)子节点。

表示任意二叉树

完美二叉树是一个特例,在二叉树的中间层通常存在许多 None 。由于层序遍历序列并不包含这些 None ,因此我们无法仅凭该序列来推测 None 的数量和分布位置。这意味着存在多种二叉树结构都符合该层序遍历序列

给定一棵非完美二叉树,上述数组表示方法已经失效。

为了解决此问题,我们可以考虑在层序遍历序列中显式地写出所有 None 。这样处理后,层序遍历序列就可以唯一表示二叉树了。

go 复制代码
/* 二叉树的数组表示 */
// 使用 any 类型的切片, 就可以使用 nil 来标记空位
tree := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15}

值得说明的是,完全二叉树非常适合使用数组来表示 。回顾完全二叉树的定义,None 只出现在最底层且靠右的位置,因此所有 None 一定出现在层序遍历序列的末尾

这意味着使用数组表示完全二叉树时,可以省略存储所有 None

以下代码实现了一棵基于数组表示的二叉树,包括以下几种操作。

给定某节点,获取它的值、左(右)子节点、父节点。

获取前序遍历、中序遍历、后序遍历、层序遍历序列。

scss 复制代码
/* 数组表示下的二叉树类 */
type arrayBinaryTree struct{
    tree []any
}
​
/* 构造方法 */
func newArrayBinaryTree(arr []any) *arrayBinaryTree{
    return &arrayBinaryTree{
        tree: arr,
    }
}
​
/* 列表容量 */
func (abt *arrayBinaryTree)size() int{
    return len(abt.tree)
}
​
/* 获取索引为 i 节点的值 */
func (abt *arrayBinaryTree)val(i int) any{
      // 若索引越界,则返回 null ,代表空位
    if i < 0 || i >= abt.size() {
        return nil
    }
    return abt.tree[i]
}
​
/* 获取索引为 i 节点的左子节点的索引 */
func (abt *arrayBinaryTree)left(i int) int{
    return 2*i+1
}
​
/* 获取索引为 i 节点的右子节点的索引 */
func (abt *arrayBinaryTree)right(i int) int{
    return 2*i+2
}
​
/* 获取索引为 i 节点的父节点的索引 */
func (abt *arrayBinaryTree)parent(i int) int{
    return (i - 1) / 2
}
​
/* 层序遍历 */
func (abt *arrayBinaryTree) levelOrder() []any {
    var res []any
    // 直接遍历数组
    for i := 0;i< abt.size();i++{
        if abt.val(i)!=nil{
            res = append(res,abt,val(i))
        }
    }
    return res
}
​
/* 深度优先遍历 */
func (abt *arrayBinaryTree) dfs(i int, order string, res *[]any) {
    // 若为空位,则返回
    if abt.val(i) == nil {
        return
    }
    // 前序遍历
    if order == "pre" {
        *res = append(*res, abt.val(i))
    }
    abt.dfs(abt.left(i), order, res)
    // 中序遍历
    if order == "in" {
        *res = append(*res, abt.val(i))
    }
    abt.dfs(abt.right(i), order, res)
    // 后序遍历
    if order == "post" {
        *res = append(*res, abt.val(i))
    }
}
​
/* 前序遍历 */
func (abt *arrayBinaryTree) preOrder() []any {
    var res []any
    abt.dfs(0, "pre", &res)
    return res
}
​
/* 中序遍历 */
func (abt *arrayBinaryTree) inOrder() []any {
    var res []any
    abt.dfs(0, "in", &res)
    return res
}
​
/* 后序遍历 */
func (abt *arrayBinaryTree) postOrder() []any {
    var res []any
    abt.dfs(0, "post", &res)
    return res
}

优点与局限性

二叉树的数组表示主要有以下优点。

数组存储在连续的内存空间中,对缓存友好,访问与遍历速度较快。

不需要存储指针,比较节省空间。

允许随机访问节点。

然而,数组表示也存在一些局限性。

数组存储需要连续内存空间,因此不适合存储数据量过大的树。

增删节点需要通过数组插入与删除操作实现,效率较低。

当二叉树中存在大量 None 时,数组中包含的节点数据比重较低,空间利用率较低。

相关推荐
苍何10 小时前
小米版 Codex,干活有点猛啊
后端
苍何10 小时前
直播!用 AI 实时生成游戏,速来看
后端
苍何10 小时前
免费版 Typeless来了,不限额度,夯!
后端
linx29511 小时前
单元九 · 零基础路线图-第 7–9 周·类与 RAII
c语言·开发语言·数据结构·c++·嵌入式硬件·算法
布莱克60511 小时前
C++ 七大排序算法详解:选择、冒泡、插入、计数、堆、快速与归并排序
数据结构·c++·算法·排序算法
Charlie_Byte11 小时前
Integer 缓存机制(JDK 17 源码)
后端
梨涡泥窝11 小时前
JavaWeb——基于 Spring Boot + Vue 的图书管理系统的设计与实现
vue.js·spring boot·后端
科学实验家12 小时前
最小生成树:Prim,kruskal
数据结构·c++·算法
jsl_jsl_jsl12 小时前
LLM工具调用速记
后端
YIAN12 小时前
手撸社区后端:一套可落地的用户 - 文章 - 互动体系 MySQL 表设计与优化思路
后端·mysql