简简单单解决二叉树问题

229 阅读4分钟

本文所有代码都是go语言编写

基础知识

基本介绍

  • root 根
  • child 儿子
  • parent 父节点
  • path 路径 定义为节点n1,n2...nk的一个序列,使得对于1 <= i < k,节点ni是ni+1的父亲。这个路径的为该路径上的边的条数,即k-1。从每个节点到他自己有一条长为0的路径,在一棵树中从跟到每个节点恰好存在一条路径
  • depth 深度 对任一节点ni,ni的深度定义为从根到ni的唯一路径的长。
  • height 高 高是从ni到一片树叶的最长路径的长,树的高就是根节点的高。
  • degree 度 节点拥有的子树的个数,树的度就是各节点度的最大值

树的实现 第一儿子下一兄弟表示法

type TreeNode struct{
	val  int
	FirstChild *TreeNode
	NestSibling  *TreeNode
}
A(root)
|
B--->C--->D                 dep=1
     |    |
	 E    F---->G           dep=2

二叉树

二叉树是一棵树,其中每个节点都不能有多于两个儿子。

  • 满二叉树 一颗深度为k且有2^k-1个结点的二叉树称为满二叉树

  • 完全二叉树 当且仅当其每一个结点都与深度为k的满二叉树中编号为1至n的结点一一对应时,称为完全二叉树。

二叉树的性质

  1. 在二叉树的第i层上至多有2^(i-1)个结点
  2. 深度为k的二叉树上至多有2^(k-1)个结点
  3. 对任意一颗二叉树T,如果其终端节点数为n0,度为2的节点数为n2,则n0=n2+1
  4. 具有n个结点的完全二叉树的深度为不大于log2 n的最大整数+1
  • 求二叉树的最大深度 (DFS)
func maxDepth(root *TreeNode) int {
    if root==nil{
        return 0
    }
    l:=maxDepth(root.Left)
    r:=maxDepth(root.Right)
    if l>r{
        return l+1
    }else {
        return r+1
    }
}
  • 求二叉树的路径 (同理递归DFS)
func binaryTreePaths(root *TreeNode) []string {
	path := make([]string, 0)
	if root == nil {
		return path
	}
	var DFS func(root *TreeNode, str string)
	DFS = func(root *TreeNode, str string) {
		if root == nil {
			return
		}
		str = fmt.Sprintf("%s%d->", str,root.Val)
		if root.Left == nil && root.Right == nil {
			path = append(path, str[:len(str)-2])
		}
		DFS(root.Left, str)
		DFS(root.Right, str)
	}
	DFS(root, "")
	return path
}

二叉树的存储结构

  • 顺序存储结构 通过一个一维数组表示,最好适用于完全二叉树
  • 链式存储结构
type TreeNode struct {
	Val   int
	Left  *TreeNode
	Right *TreeNode
}


二叉树的遍历

二叉树的先序遍历

先遍历根节点 在遍历左子树 右子树

  • 递归解法
    时间复杂度:O(n)
    空间复杂度:O(n)
func preorderTraversal(root *TreeNode) []int {
	ret := make([]int, 0)
	if root == nil {
		return ret
	}
	var PT func(root *TreeNode)
	PT = func(root *TreeNode) {
		ret = append(ret, root.Val)
		if root.Left != nil {
			PT(root.Left)
		}
		if root.Right != nil {
			PT(root.Right)
		}
	}
	PT(root)
	return ret
}
  • 迭代解法 通过一个栈,每次将当前节点的右儿子先入栈,随后左儿子入栈,循环遍历时将当前节点出栈放入数组即可。
    时间复杂度:O(n)
    空间复杂度:O(n)最坏情况
func preorderTraversal2(root *TreeNode) []int {
	ret := make([]int, 0)
	if root == nil {
		return ret
	}
	stack := []*TreeNode{root}
	for len(stack) > 0 {
		node := stack[len(stack)-1]
		stack = stack[:len(stack)-1]
		ret = append(ret, node.Val)
		if node.Right != nil {
			stack = append(stack, node.Right)
		}
		if node.Left != nil {
			stack = append(stack, node.Left)
		}
	}
	return ret
}

二叉树的中序遍历

先遍历左子树,在遍历根节点,然后遍历右子树。

二叉树的后序遍历

先遍历左子树,在遍历右子树,最后遍历根节点

二叉树的层序遍历(DFS,BFS)

  • 迭代实现 通过广度优先遍历,借用队列作为辅助结构
    首先将根节点放入队列,不断遍历队列
    如果根节点做右子树不为空,根节点出队,将左右子树节点放入队列
    我们把每层遍历到的节点都放入到一个结果集中,最后返回这个结果集就可以了。

    时间复杂度: O(n)
    空间复杂度:O(n)

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

func levelOrder(root *TreeNode) [][]int {
	ret := [][]int{}
	if root == nil {
		return ret
	}
	queue := []*TreeNode{root}
	for len(queue) > 0 {
		l := len(queue)
		ans := make([]int, 0)
		for i := 0; i < l; i++ {
			node := queue[i]
			ans = append(ans, node.Val)
			if node.Left != nil {
				queue = append(queue, node.Left)
			}
			if node.Right != nil {
				queue = append(queue, node.Right)
			}
		}
		ret = append(ret, ans)
		queue = queue[l:]
	}
	return ret
}
  • 递归实现

    按照深搜的方法,每次递归代入一个index表示当前层数,当遍历到一个新的深度而最终结果 res 中还没有创建当前对应的列表时,应该在 res 中新建一个列表用来保存该层的所有节点。
    时间复杂度: O(n)
    空间复杂度: O(h) h为二叉树的高度

func levelOrderDFS(root *TreeNode) [][]int {
	ret := [][]int{}
	if root == nil {
		return ret
	}
	var SerchRoot func(root *TreeNode, index int)
	SerchRoot = func(root *TreeNode, index int) {
		if root == nil {
			return
		}
		if len(ret) == index {
			ret = append(ret, []int{})
		}
		ret[index] = append(ret[index], root.Val)
		SerchRoot(root.Left, index+1)
		SerchRoot(root.Right, index+1)
	}
	SerchRoot(root, 0)
	return ret
}

二叉树的展开(单链表)

方法

  1. 先序遍历 然后通过获得的顺序进行展开
  2. 先序遍历和展开同时进行
   func flatten(root *TreeNode)  {
    if root == nil {
        return
    }
    stack := []*TreeNode{root}
    var prev *TreeNode
    for len(stack) > 0 {
        curr := stack[len(stack)-1]
        stack = stack[:len(stack)-1]
        if prev != nil {
            prev.Left, prev.Right = nil, curr
        }
        left, right := curr.Left, curr.Right
        if right != nil {
            stack = append(stack, right)
        }
        if left != nil {
            stack = append(stack, left)
        }
        prev = curr
    }
}
  1. 寻找前驱节点 按照前序遍历的顺序,左子树的最右边节点一定是在当前节点右子树的根节点之前
       【1】
   【2】    【5】
【3】 【4】     【6】

例如 当前根节点的左儿子节点存在,那么左子树的最右节点4一定在5之前,4就称为5的前驱节点。
对于当前节点,如果其左子节点不为空,则在其左子树中找到最右边的节点,作为前驱节点,将当前节点的右子节点赋给前驱节点的右子节点,然后将当前节点的左子节点赋给当前节点的右子节点,并将当前节点的左子节点设为空。对当前节点处理结束后,继续处理链表中的下一个节点,直到所有节点都处理结束。

func flatten3(root *TreeNode) {
	if root == nil {
		return
	}
	curr := root
	for curr != nil {
		if curr.Left != nil {
			next := curr.Left
			per := next
			for per.Right != nil {
				per = per.Right
			}
			per.Right = curr.Right
			curr.Left = nil
			curr.Right = next
		}
		curr = curr.Right
	}
}

时间复杂度:O(n)
空间复杂度:O(1)