基础知识
二叉树。顾名思义,在二叉树中每个节点最多只有两个子节点,可以分别把它们称为左子节点和右子节点。 二叉树是一种典型的具有递归性质的数据结构。二叉树的根节点可能有子节点,子节点又是对应子树的根节点,它可能也有自己的子节点。一个递归函数dfs(dfs是Depth First Search的缩写,即深度优先搜索)
二叉树的深度优先搜索又可以细分为中序遍历、前序遍历和后序遍历。
- 中序遍历,先遍历二叉树的左子树,然后遍历二叉树的根节点,最后遍历二叉树的右子树。如果是一个二叉搜索树,那么用中序遍历一遍就是完整的排序。
- 前序遍历,先遍历二叉树的根节点,再遍历二叉树的左子树,最后遍历二叉树的右子树。适用于二叉树序列化。
- 后序遍历,先遍历左子树,再遍历右子树,最后遍历根节点。适用于二叉树剪枝。
常见算法
一、二叉树剪枝
题目:一棵二叉树的所有节点的值要么是0要么是1,请剪除该二叉树中所有节点的值全都是0的子树
解题思路:
后序遍历,递归判断左右分支是否都是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。
解题思路:
前序遍历,将所有数字遍历出来,再相加。
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)
}
}
四、展平二叉搜索树
题目:给定一棵二叉搜索树,请调整节点的指针使每个节点都没有左子节点。调整之后的树看起来像一个链表,但仍然是二叉搜索树。
解题思路:中序遍历
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
}