常见算法之Tree

119 阅读4分钟

基础知识

二叉树。顾名思义,在二叉树中每个节点最多只有两个子节点,可以分别把它们称为左子节点和右子节点。 二叉树是一种典型的具有递归性质的数据结构。二叉树的根节点可能有子节点,子节点又是对应子树的根节点,它可能也有自己的子节点。一个递归函数dfs(dfs是Depth First Search的缩写,即深度优先搜索)

二叉树的深度优先搜索又可以细分为中序遍历、前序遍历和后序遍历。

  • 中序遍历,先遍历二叉树的左子树,然后遍历二叉树的根节点,最后遍历二叉树的右子树。如果是一个二叉搜索树,那么用中序遍历一遍就是完整的排序。
  • 前序遍历,先遍历二叉树的根节点,再遍历二叉树的左子树,最后遍历二叉树的右子树。适用于二叉树序列化。
  • 后序遍历,先遍历左子树,再遍历右子树,最后遍历根节点。适用于二叉树剪枝。

常见算法

一、二叉树剪枝

题目:一棵二叉树的所有节点的值要么是0要么是1,请剪除该二叉树中所有节点的值全都是0的子树

image.png

解题思路:

后序遍历,递归判断左右分支是否都是0,再对边当前节点是否为0,为的0的全部剪去。

Golang算法:

func pruneTree(root *TreeNode) *TreeNode {

   if prune(root) {
      return root
   }
   return nil
}


func prune(node *TreeNode) bool {
   if node.Left == nil && node.Right == nil {
      if node.Val == 0 {
         return false
      }
      return true
   }

   var (
      r1 bool
      r2 bool
   )

   if node.Left != nil {
      r1 = prune(node.Left)
      if !r1 {
         node.Left = nil
      }
   }

   if node.Right != nil {
      r2 = prune(node.Right)
      if !r2 {
         node.Right = nil
      }
   }

   return r1 || r2 || node.Val == 1
}

二、序列化和反序列化二叉树

题目:请设计一个算法将二叉树序列化成一个字符串,并能将该字符串反序列化出原来二叉树的算法。

解题思路:

前序遍历方式解题。

Golang代码:

type Codec struct {

}

func Constructor() Codec {
   return Codec{}
}

// Serializes a tree to a single string.
func (this *Codec) serialize(root *TreeNode) string {
   list := make([]string, 0)
   this.toString(root, &list)
   //fmt.Println(list)
   return strings.Join(list, ",")
}

func (this *Codec) toString(node *TreeNode, list *[]string) {
   if node == nil {
      *list = append(*list, "*")
      return
   }

   *list = append(*list, strconv.Itoa(node.Val))
   this.toString(node.Left, list)
   this.toString(node.Right, list)
}

// Deserializes your encoded data to tree.
func (this *Codec) deserialize(data string) *TreeNode {
   list := strings.Split(data, ",")
   i := 0
   return this.createTree(list, &i)
}

func (this *Codec) createTree(list []string, i *int) *TreeNode {

   str := list[*i]
   *i++

   if str == "*" {
      return nil
   }
   val, _ := strconv.Atoi(str)
   node := &TreeNode{
      Val: val,
   }

   node.Left = this.createTree(list, i)
   node.Right = this.createTree(list, i)
   return node
}

三、从根节点到叶节点的路径数字之和

题目:在一棵二叉树中所有节点都在0~9的范围之内,从根节点到叶节点的路径表示一个数字。求二叉树中所有路径表示的数字之和。例如,下图的二叉树有3条从根节点到叶节点的路径,它们分别表示数字395、391和302,这3个数字之和是1088。

image.png

解题思路:

前序遍历,将所有数字遍历出来,再相加。

Golang代码:

func sumNumbers(root *TreeNode) int {
   list := make([]string, 0)
   dfsNum(&list, root, "")
   //fmt.Println(list)
   sum := 0
   for _, item := range list {
      tmp, _ := strconv.Atoi(item)
      sum += tmp
   }

   return sum
}

func dfsNum(list *[]string, node *TreeNode, s string ) {
   if node.Left == nil && node.Right == nil {
      s += strconv.Itoa(node.Val)
      *list = append(*list, s)
      return
   }

   left := s
   right := s

   if node.Left != nil {
      left += strconv.Itoa(node.Val)
      dfsNum(list, node.Left, left)
   }

   if node.Right != nil {
      right += strconv.Itoa(node.Val)
      dfsNum(list, node.Right, right)
   }
}

四、展平二叉搜索树

题目:给定一棵二叉搜索树,请调整节点的指针使每个节点都没有左子节点。调整之后的树看起来像一个链表,但仍然是二叉搜索树。

image.png

解题思路:中序遍历

Golang算法:

func increasingBST(root *TreeNode) *TreeNode {
	list := make([]*TreeNode, 0)
	if root == nil {
		return root
	}

	dealIncreasingBST(root, &list)

	tag := &TreeNode{}
	node := tag

	for _, item := range list {
		item.Left = nil
		tag.Right = item
		tag = item
	}

	return node.Right
}

func dealIncreasingBST(node *TreeNode, list *[]*TreeNode) {

	if node != nil {
		dealIncreasingBST(node.Left, list)
		*list = append(*list, node)
		dealIncreasingBST(node.Right, list)
	}

	return
}

五、二叉搜索树的下一个节点

题目:给定一棵二叉搜索树和它的一个节点p,请找出按中序遍历的顺序该节点p的下一个节点。假设二叉搜索树中节点的值都是唯一的。

解题思路:

利用搜索二叉树的属性: 1.如果p的右节点有值,那么就查出右节点的最左节点即可。 2.如果p的右节点没有值,那么从根节点开始,如果当前节点小于或等于p,那么p的下个节点一定在右分支上,继续向右节点遍历;如果当前节点大于p,那么该节点可能是p的下个节点,继续向左节点遍历。

Golang代码:

func inorderSuccessor(root *TreeNode, p *TreeNode) *TreeNode {
   var res *TreeNode
   if p.Right != nil {
      res = p.Right
      for res.Left != nil  {
         res = res.Left
      }
      return res
   }

   node := root
   for node != nil {
      if node.Val > p.Val {
         res = node
         node = node.Left
      } else {
         node = node.Right
      }
   }

   return res
}