二叉树节点结构
type Tree struct {
Val int
Left *Tree
Right *Tree
}
Val:节点的数值
Left:该节点的左孩子节点,指向一个节点
Right:该节点的右孩子节点,指向一个节点
二叉树的递归序
递归整棵二叉树,会发现每一个节点都会被遍历3次,例如以下二叉树
1
2 3
4 5 6 7
从根节点开始遍历,那么1会被访问一次,然后访问1的左孩子节点,则2被访问,再访问2的左孩子节点,4被访问,访问4的第左孩子节点,为空,则回到4,4被访问第二次,然回到2,2被访问第二次,依次这样下去,会得到一个二叉树的递归序为 1 2 4 4 4 2 5 5 5 2 1 3 6 6 6 3 7 7 7 3 1
递归方式实现二叉树的先序,中序,后序遍历
二叉树的递归序:每一个节点都会被访问三次 先序遍历则在访问第一次的时间打印 中序遍历则在访问第二次的时间打印 后序遍历则在访问第三次的时间打印
先序遍历(头左右)
func PreOrderRecur(Node *Tree) {
if Node == nil {
return
}
fmt.Print(Node.Val, " ")
PreOrderRecur(Node.Left)
PreOrderRecur(Node.Right)
}
中序遍历(左头右)
func InOrderRecur(Node *Tree) {
if Node == nil {
return
}
InOrderRecur(Node.Left)
fmt.Print(Node.Val, " ")
InOrderRecur(Node.Right)
}
后序遍历(左右头)
func PosOrderRecur(Node *Tree) {
if Node == nil {
return
}
PosOrderRecur(Node.Left)
PosOrderRecur(Node.Right)
fmt.Print(Node.Val, " ")
}
非递归方式实现二叉树的先序,中序,后序遍历(栈实现)
首先手写一个栈
type Stack struct {
index int
stack []*Tree
}
func newStack() *Stack {
s := &Stack{index: -1}
s.stack = make([]*Tree, 100)
return s
}
func (s *Stack) put(x *Tree) {
s.index++
s.stack[s.index] = x
}
func (s *Stack) pop() *Tree {
x := s.stack[s.index]
s.index--
return x
}
func (s *Stack) isEmpty() bool {
if s.index == -1 {
return true
}
return false
}
栈实现二叉树的先序遍历
实现思路:
第一步:先将根节点入栈
第二步:从栈中弹出一个节点,打印
第三步:如果有左右孩子的情况下,先入弹出节点的右孩子节点,再入弹出节点的左孩子节点(这样可以让下一次弹出时左孩子节点先弹出,先遍历)
第四步:循环第二步和第三步直到栈为空
func PreOrderUnRecur(Node *Tree) {
s := newStack()
if Node == nil {
return
}
s.put(Node)
for !s.isEmpty() {
node := s.pop()
fmt.Print(node.Val, " ")
if node.Right != nil {
s.put(node.Right)
}
if node.Left != nil {
s.put(node.Left)
}
}
}
栈实现二叉树的中序遍历
实现思路:
每棵子树整棵树左边界(也就是一直迭代node=node.left,一直只放左子树进栈)进栈。
如果该节点为迭代到该节点为空节点,就将该节点的父节点打印(将栈顶节点弹出并打印)
然后开始将该弹出节点的右孩子作为下一轮的循环节点,若不为空,则一直重复第一步,若为空,则接着弹出栈顶元素(即该空节点的父节点的父节点)
func InOrderUnRecur(Node *Tree) {
if Node == nil {
return
}
s := newStack()
for !s.isEmpty() || Node != nil {
if Node != nil {
s.put(Node)
Node = Node.Left
} else {
Node = s.pop()
fmt.Print(Node.Val, " ")
Node = Node.Right
}
}
}
栈实现二叉树的后序遍历
准备两个栈,一个s1先压根节点入栈
只要s1弹出一个节点,s2就入栈一个节点
若s1不为空,则一直循环,先入左孩子,后入右孩子,循环开始时先弹出(执行第二条)
最后将s2中所有节点弹出,即是后序遍历的顺序
func PosOrderUnRecur(Node *Tree) {
if Node == nil {
return
}
s1 := newStack()
s2 := newStack()
s1.put(Node)
for !s1.isEmpty() {
Node = s1.pop()
s2.put(Node)
if Node.Left != nil {
s1.put(Node.Left)
}
if Node.Right != nil {
s1.put(Node.Right)
}
}
for !s2.isEmpty() {
fmt.Print(s2.pop().Val, " ")
}
}
如何判断一棵二叉树是搜索二叉树?
定义:搜索二叉树对于每一棵子树来说,左树节点都比它根节点小,右树节点都比它大
搜索二叉树满足其中序遍历为升序
如何判断一棵二叉树是完全二叉树?
满足两个条件
1.任意一个节点,有右孩子无左孩子,就不是完全二叉树
2.在不违反1的条件下,如果遇到了第一个左右孩子不全,后续遇到的都为叶子节点
如何判断一棵二叉树是满二叉树?
计算出其最大深度:L,计算出其节点数:N
若满足N=1<<L -1,则为满二叉树(1<<L代表2^L)
如何判断一棵二叉树是平衡二叉树?
对于任何一个子树来说,左树高度和右数高度差不超过1
知道前序遍历,中序遍历或者知道中序遍历与后序遍历,如何将二叉树构造出来?
leetcode105 106
对于任意一棵树而言,前序遍历总是:
[ 根节点,[左子树的前序遍历结果],[右子树的前序遍历结果] ]
对于任意一棵树而言,中序遍历总是:
[ [左子树的前序遍历结果],根节点,[右子树的前序遍历结果] ]
对于任意一棵树而言,后序遍历总是:
[ [左子树的前序遍历结果],[右子树的前序遍历结果],根节点 ]
因为前序遍历和后序遍历中的根节点都是在第一个和最后一个位置的,确定了根节点的位置,就可以定位到中序遍历中的根节点位置,然后再将左(右)子树的前序遍历和左(右)子树的中序遍历递归执行。
前序中序构造
func buildTree(preorder []int, inorder []int) *TreeNode {
if len(preorder) == 0 {
return nil
}
node := &TreeNode{}
// 根节点
node.Val = preorder[0]
i := 0
for ; i < len(inorder)-1; i++ {
// 定位到根节点在中序序列中的位置
if node.Val == inorder[i] {
break
}
}
node.Left = buildTree(preorder[1:len(inorder[:i])+1], inorder[:i])
node.Right = buildTree(preorder[len(inorder[:i])+1:], inorder[i+1:])
return node
}
中序后序构造
func buildTree2(inorder []int, postorder []int) *TreeNode {
if len(inorder) == 0 {
return nil
}
node := &TreeNode{}
node.Val = postorder[len(postorder)-1]
i := 0
for ; i < len(inorder)-1; i++ {
if node.Val == inorder[i] {
break
}
}
node.Left = buildTree2(inorder[:i], postorder[:len(inorder[:i])])
node.Right = buildTree2(inorder[i+1:], postorder[len(inorder[:i]):len(postorder)-1])
return node
}
二叉树宽度优先遍历
实现思路:队列
1.先将根节点入队
2.做一个队列不为空的循环,出队,打印
3.先入队出队节点的左孩子节点,再入队出队节点的右孩子节点(如果都不为空的话)。
首先手写一个队列
type Queue struct {
headIndex int
tailIndex int
queue []*TreeNode
}
func newQueue() *Queue {
q := &Queue{headIndex: -1, tailIndex: -1}
q.queue = make([]*TreeNode, 6005)
return q
}
func (q *Queue) put(x *TreeNode) {
q.tailIndex++
q.queue[q.tailIndex] = x
}
func (q *Queue) pop() *TreeNode {
q.headIndex++
t := q.queue[q.headIndex]
return t
}
func (q *Queue) isEmpty() bool {
if q.tailIndex == q.headIndex {
return true
}
return false
}
func (q *Queue) get() *TreeNode {
q.headIndex++
t := q.queue[q.headIndex]
q.headIndex--
return t
}
宽度优先遍历代码实现
func BreathFirstPrint(Node *TreeNode) {
q := newQueue()
q.put(Node)
for !q.isEmpty() {
Node := q.pop()
fmt.Print(Node.Val, " ")
if Node.Left != nil {
q.put(Node.Left)
}
if Node.Right != nil {
q.put(Node.Right)
}
}
}
给定两个二叉树的节点node1和node2,找到他们的最低公共祖先节点
HashMap版本
实现思路:用map存储对应节点的父节点
首先,定义一个map,将每个节点(key)的父节点(value)存储进map中
代码实现
func process(root *TreeNode, fatherMap map[*TreeNode]*TreeNode) {
if root == nil {
return
}
fatherMap[root.Left] = root
fatherMap[root.Right] = root
process(root.Left, fatherMap)
process(root.Right, fatherMap)
}
然后,定义一个HashSet,从第一个节点p向上找到它的父节点,然后再找到它父节点的父节点,直到到了整棵树的根节点的位置,得到了一个只有key的map,所有key组成了一个p节点的父节点链。
最后,从第二个节点q向上找它的父节点,一直往上找节点的父节点,直到找到的那个父节点在HashSet中存在,即这个节点就是他们两个节点的最低公共祖先。
完整代码实现
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
fatherMap := make(map[*TreeNode]*TreeNode)
process(root, fatherMap)
fatherSet := make(map[*TreeNode]bool)
fatherSet[root] = true
cur := p
for cur != root {
fatherSet[cur] = true
cur = fatherMap[cur]
}
cur = q
if fatherSet[cur] == true {
return cur
}
for fatherSet[cur] != true {
cur = fatherMap[cur]
if fatherSet[cur] == true {
return cur
}
}
return root
}
func process(root *TreeNode, fatherMap map[*TreeNode]*TreeNode) {
if root == nil {
return
}
fatherMap[root.Left] = root
fatherMap[root.Right] = root
process(root.Left, fatherMap)
process(root.Right, fatherMap)
}
优化版(带注释)
// 优化版
func LowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
// 如果遇到p或者q就返回p或者q,如果遇到空则返回空(说明在此条路径上没有p或q)
if root == nil || root == p || root == q {
return root
}
// 向自己的左孩子要答案
left := LowestCommonAncestor(root.Left, p, q)
// 向自己的右孩子要答案
right := LowestCommonAncestor(root.Right, p, q)
// 如果都不为空说明此root就是两个的最低公共祖先,将其往上返回
if left != nil && right != nil {
return root
}
// 如果左不为空,说明左边有p或q
if left != nil {
return left
}
// 如果左为空,就返回右
return right
}
树型DP
通过向左子树要信息,向右子树要信息,可以解决该树的问题。