数据结构和算法-树

237 阅读4分钟

定义

树是一种非线性表结构,是n(>=0)个节点的有限集。当n=0是,称之为空树。

任何一颗非空树有以下两个特征

  1. 有且只有一个称为root的节点,即根节点
  2. 其余节点可以划分为m(>=0)个互不相交有限集,称之为root的子树

基本概念

  • 节点的度:节点拥有的子树的数量
  • 树的度:所有节点的度的最大值

节点分类

  • 根节点:非空树的第一个节点
  • 叶子节点:度为0的节点
  • 内部节点:除了根节点和叶子节点之外的节点

节点的关系

  • parant:双亲节点
  • child:子节点,节点子树的根,该节点即为双亲节点
  • sibling:兄弟节点,双亲节点一样的节点
  • 祖先节点:从根节点到该节点经历的任一节点
  • 子孙节点:以该节点为为根的子树中的任一节点

高度

  • 节点的高度:节点到叶子节点的最长路径(即边的个数)
  • 树的高度:根节点的高度

深度

  • 节点的深度:根节点到这个节点所经历的边的个数
  • 树的深度:叶子结点的深度

节点的深度+1

二叉树

节点的度最大为2的树

满二叉树

叶子节点全部在最底层,除了叶子节点,每个节点的度都为2

完全二叉树

叶子节点全部在最底下两层,最后一层叶子节点向左对齐,除了最后一层,其它层的节点个数必须达到最大

实现

之前说过,所有的高级数据结构都可以使用数组和链表实现,树也不例外。

实际一般使用链表来实现树。

基于数组

以二叉为例子,设根节点索引为i,左子节点为il,右子节点为ir,则有

i = 0il = 2 * i + 1
ir = 2 * i + 2

根据这个规律,每个节点都可以在数组中找到唯一的一个位置存储。

这种方式存在一个缺点,当二叉树比较稀疏时,比较浪费空间。换句话说,满二叉树和完全二叉树非常适合使用数组实现,可以完全利用数组空间。

基于链表

以二叉树为例

type TreeNode struct {
    Val int
    Left *TreeNode
    Right *TreeNode
}

算法

接下来为了方便代码实现,以链表实现的二叉树为例

深度优先遍历

树的深度优先遍历包括:前序、中序、后序遍历。

深度优先遍历就是一个递归问题,以前序遍历为例,可以按以下步骤写出递归代码。

  1. 递归公式
preOrderTravel = node.Val + preOrderTravel(node.Left) + preOrderTravel(node.Right)
  1. 终止条件
node == nil
  1. 翻译成代码
//前序
func preOrderTravel(root *TreeNode) {
    if root == nil {
        return 
    }
    val := root.Val
    //do something
    preOrderTravel(root.Left)
    preOrderTravel(root.Right)
}
//-------------------------------------
//中序
func preOrderTravel(root *TreeNode) {
    if root == nil {
        return 
    }
    preOrderTravel(root.Left)
    val := root.Val
    //do something
    preOrderTravel(root.Right)
}
//后序
func preOrderTravel(root *TreeNode) {
    if root == nil {
        return 
    }
    preOrderTravel(root.Left)
    preOrderTravel(root.Right)
    val := root.Val
    //do something
}

广度优先遍历

树的广度优先遍历一般称之为层次遍历,层次遍历需要借助队列来实现。

func levelTravel(root *TreeNode) [][]int{
    ans := [][]int{}
    queue := []int{}
    if root == nil {
        queue = append(queue, root)
    }
    for len(queue) > 0 {
        size := len(queue)
        item := []int{}
        for size > 0 {
            node := queue[0]
            queue := queue[1:]
            item = append(item, node.Val)
            if node.Left != nil {
                queue = append(queue, node.Left)
            }
            if node.Right != nil {
                queue = append(queue, node.Right)
            }
            size -= 1
        }
        ans = append(ans, item)
    }
    return ans
}

查找节点

基于遍历算法,基于先序遍历的实现如下

func find(root *TreeNode, val int) *TreeNode {
    var ans *TreeNode
    var dfs func(*TreeNode)
    dfs = func(node *TreeNode) {
        //当访问到叶子节点或者已经找到目标节点时,直接返回
        if node == nil || ans != nil {
            return 
        }
        if node.Val == val {
            ans = node
            return
        }
        dfs(node.Left)
        dfs(node.Right)
    }
    dfs(root)
    return ans
}

插入/删除节点

节点间没有规则的树,讨论插入/删除节点操作并没有什么意义。

拓展问题

如何通过非递归的方式实现二叉树的前中后序遍历

所有递归代码,理论上都可以通过手动模拟入栈出栈的方式转化成循环迭代代码。

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
//前序遍历
func preorderTraval(root *TreeNode) []int {
    ans := []int{}
    stack := []*TreeNode{}
    node := root
    for node != nil || len(stack) > 0 {
        for node != nil {
            ans = append(ans, node.Val)
            stack = append(stack, node)
            node = node.Left
        }
        node = stack[len(stack)-1]
        stack = stack[:len(stack)-1]
        node = node.Right
    }
    return ans
}
//中序遍历
func inorderTraval(root *TreeNode) []int {
    ans := []int{}
    stack := []*TreeNode{}
    node := root
    for node != nil || len(stack) > 0 {
        for node != nil {
            stack = append(stack, node)
            node = node.Left
        }
        node = stack[len(stack)-1]
        stack = stack[:len(stack)-1]
        ans = append(ans, node.Val)
        node = node.Right
    }
    return ans
}
//后序遍历
func postorderTraval(root *TreeNode) []int {
    ans := []int{}
    stack := []*TreeNode{}
    node := root
    var pre *TreeNode
    for node != nil || len(stack) > 0 {
        for node != nil {
            stack = append(stack, node)
            node = node.Left
        }
        node = stack[len(stack)-1]
        stack = stack[:len(stack)-1]
        if node.Right == nil || node.Right == pre {
            ans = append(ans, node.Val)
            pre = node
            node = nil
        } else {
            stack = append(stack, node)
            node = node.Right
        }
    }
    return ans
}

如何求二叉树的最大深度

基于深度优先、深度优先遍历都可以求得二叉树的最大深度。

//深度优先
func maxDepth(root *TreeNode) int {
    if root == nil {
        return 0
    }
    return 1 + max(maxDepth(root.Left), maxDepth(root.Right))
}

func max(a, b int) {
    if a < b {
        return b
    }
    return a
}

//广度优先,这里只求深度,可以使用一个变量来替代队列
func maxDepth(root *TreeNode) int {
    ans := 0
    queue := []int{}
    if root == nil {
        queue = append(queue, root)
    }
    for len(queue) > 0 {
        size := len(queue)
        for size > 0 {
            node := queue[0]
            queue := queue[1:]
            if node.Left != nil {
                queue = append(queue, node.Left)
            }
            if node.Right != nil {
                queue = append(queue, node.Right)
            }
            size -= 1
        }
        ans += 1
    }
    return ans
}