二叉树专题

二叉树类型的题目为常考题型

1、能够结合队列、栈、链表、字符串等很多数据结构。

2、需要掌握图的基本遍历方式,比如BFS和DFS。

3、需要掌握递归函数的使用,并自己设计出递归过程。

4、与实际工作结合紧密。

用递归和非递归的形式分别实现二叉树的先序、中序和后序的遍历打印。

实现代码:

# -*- coding:utf-8 -*-
#打印二叉树
#实现功能:
#  树的构造
#  递归实现先序遍历,中序遍历,后序遍历
#  堆栈实现先序遍历,中序遍历,后序遍历
#  队列实现层次遍历

class Node():
    #节点类
    def __init__(self,data = -1):
        self.data = data
        self.left = None
        self.right = None
class Tree():
    #树类
    def __init__(self):
        self.root = Node()

    def add(self,data):
        # 为树加入节点
        node  = Node(data)
        if self.root.data == -1:        #如果树为空,就对根节点赋值
            self.root = node
        else:
            myQueue = []
            treeNode = self.root
            myQueue.append(treeNode)
            while myQueue:              #对已有的节点进行层次遍历
                treeNode = myQueue.pop(0)
                if not treeNode.left:
                    treeNode.left = node
                    return
                elif not treeNode.right:
                    treeNode.right = node
                    return
                else:
                    myQueue.append(treeNode.left)
                    myQueue.append(treeNode.right)

    def pre_order_recursion(self,root):     #递归实现前序遍历
        if not root:
            return
        print root.data,
        self.pre_order_recursion(root.left)
        self.pre_order_recursion(root.right)

#非递归实现前序遍历
    # 1、首先申请一个新的栈,记为stack。
    # 2、然后将头即诶安head压入stack中。
    # 3、每次从stack中弹出栈顶节点,记为cur,然后打印cur节点的值。如果cur右孩子不为空的话,
    # 将cur的右孩子先压入stack中。最后如果cur的左孩子不为空的话,将cur的左孩子压入stack中。
    # 4、不断重复步骤3,直到stack为空,全部过程结束。


    def pre_order_stack(self,root):         #堆栈实现前序遍历
        if not root:
            return
        myStack = []
        node = root
        while myStack or node:
            while node:       #从根节点开始,一直寻找他的左子树
                print node.data,
                myStack.append(node)
                node = node.left
            node = myStack.pop()    #while结束表示当前节点node为空,即前一个节点没有左子树了
            node = node.right       #开始查看它的右子树

    def in_order_recursion(self,root):      #递归实现中序遍历
        if not root:
            return
        self.in_order_recursion(root.left)
        print root.data,
        self.in_order_recursion(root.right)

#非递归实现中序遍历
    # 1、申请一个新的栈,记为stack,申请一个变量cur,初始时令cur等于头节点。
    # 2、先把cur节点压入栈中,对以cur节点为头的政客子树来说,一次把整棵树的左边界压入栈中,
    # 即不断令cur = vur.left,然后重复步骤2。
    # 3、不断重复步骤2,直到发现cur为空,此时stack中弹出一个节点,记为node。
    # 打印node的值,并让cur = node.right,然后继续重复步骤2
    # 4、当stack为空并且cur为空时,整个过程结束。

    def in_order_stack(self,root):         #堆栈实现中序遍历
        if not root:
            return
        myStack = []
        node = root
        while myStack or node:     #从根节点开始,一直寻找它的左子树
            while node:
                myStack.append(node)
                node = node.left
            node = myStack.pop()
            print node.data,
            node = node.right


    def post_order_recursion(self,root):     #递归实现后序遍历
        if not root:
            return
        self.post_order_recursion(root.left)
        self.post_order_recursion(root.right)
        print root.data,

#非递归实现后序遍历(前序遍历的逆序)
    # 1、申请一个栈,记为s1,然后将头节点压入s1中。
    # 2、从s1中弹出的节点记为cur,然后先把cur的左孩子压入s1中,然后把cur的左孩子压入s1中,然后把cur1的右孩子压入s1中。
    # 3、在整个过程中,每一个从s1中弹出的节点都放进第二个栈s2中。
    # 4、不断重复步骤2和步骤3,直到s1为空,过程停止。

    def post_order_stack(self, root):  # 堆栈实现后序遍历
        # 先遍历根节点,再遍历右子树,最后是左子树,这样就可以转化为和先序遍历一个类型了,最后只把遍历结果逆序输出就OK了。
        if not root:
            return
        myStack1 = []
        myStack2 = []
        node = root
        while myStack1 or node:
            while node:
                myStack2.append(node)
                myStack1.append(node)
                node = node.right
            node = myStack1.pop()
            node = node.left
        while myStack2:
            print myStack2.pop().data,

    def level_order_queue(self,root):
        if not root :
            return
        myQueue = []
        node = root
        myQueue.append(node)
        while myQueue:
            node = myQueue.pop(0)
            print node.data,
            if node.left:
                myQueue.append(node.left)
            if node.right:
                myQueue.append(node.right)

if __name__ == '__main__':
    #主函数
    datas = [2,3,4,5,6,7,8,9]
    tree = Tree()          #新建一个树对象
    for data in datas:
        tree.add(data)      #逐个加入树的节点

    print '递归实现前序遍历:'
    tree.pre_order_recursion(tree.root)

    print '\n堆栈实现前序遍历'
    tree.pre_order_stack(tree.root)

    print "\n\n递归实现中序遍历:"
    tree.in_order_recursion(tree.root)

    print "\n堆栈实现中序遍历:"
    tree.in_order_stack(tree.root)

    print '\n\n递归实现后序遍历:'
    tree.post_order_recursion(tree.root)

    print '\n堆栈实现后序遍历:'
    tree.post_order_stack(tree.root)

    print '\n\n队列实现层次遍历:'
    tree.level_order_queue(tree.root)










经典案例:

案例一

二叉树的子树

在二叉树中以任何任何一个节点为头部的整棵树称做二叉树的子树。


案例二:

平衡二叉树(AVL树)

1、空树是平衡二叉树

2、如果一棵树不为空,并且其中所有的子树都满足各自的左子树与右子树的高度差都不超过1。


案例三:

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

1、二叉树----->字符串(序列化)

2、字符串----->二叉树(反序列化)

序列化的方式:

根据先序遍历序列化

根据中序遍历序列化

根据后序遍历序列化

按层序列化

这里以先序遍历为例子:

  1. 假设序列化结果为str,初始str为空字符串
  2. 先序遍历二叉树时如果遇到空节点,在str末端加上"#!"
  3. 如果遇到不为空的节点,假设节点值为3,就在str的末尾加上"3!"

(这里加上!的原因是为了防止产生歧义,比如说12和1,2,序列化后都是12)

反序列化:

  1. 将字符串转化为数组
  2. 选择用什么样的遍历方式序列化,就选择用什么样的方式反序列化
  3. 一棵树序列化的结果是唯一的,唯一的结果生成的二叉树也是唯一的

按层遍历的方式对二叉树进行序列化

  1. 用队列进行二叉树的按层遍历,即宽度优先遍历。
  2. 除了访问节点的顺序是按层遍历之外,对结果字符串的处理,与之前介绍的处理方式一样。


案例四:

给定一棵二叉树的头节点head,判断一棵树是否是平衡二叉树。

思路:

1、左子树是否为平衡二叉树(true or false)--如果左子树为false,则直接返回false

2、左子树最深到哪一层,LH

3、右子树是否为平衡二叉树(true or false)

4、右子树最深到哪一层,RH

5、如果左子树和右子树都是平衡二叉树,则比较LH和RH


案例五:

搜索二叉树

特征:

每棵树的头节点的值都比各自左子树上的所有节点值要大,也都比各自右子树上的所有节点值要小。

搜索二叉树按照中序遍历得到的序列,一定是从小到大排列的。(也可以利用这一性质判断一棵树是否为平衡二叉树)

红黑树、平衡搜索二叉树(AVL树)等,其实都是搜索二叉树的不同实现。


给定一棵二叉树的头节点head,请判断这棵树是否为搜索二叉树。

思路:

1、改写二叉树的中序遍历

2、遍历到每个节点的值时,如果一直比上一个遍历的节点值要大,则时搜索二叉树。否则,不是搜索二叉树。

3、为了方便同时得到当前节点,和上一个遍历的节点,二叉树中序遍历非递归的实现比较合适。


案例六:

满二叉树和完全二叉树

满二叉树是除了最后一层的节点无任何子节点外,剩下每一层上的节点都有两个子节点。

满二叉树的层数即为L,节点树即为N,则N= 2^L  - 1 ,  L = log2 ^(N+1)

完全二叉树

完全二叉树是指除了最后一层外,其他每一层的节点数都是满的。最后一层如果也满了,是一棵满二叉树,也是完全二叉树。最后一层如果不满,缺少的节点也全部的几种在右边,那也是一棵完全二叉树。


以上三棵树均为完全二叉树,其中第一个也为满二叉树

给定一棵二叉树的头节点head,判断这棵树是否为完全二叉树?

思路:

1、采用层次遍历二叉树的方式,从每层的左边向右边一次遍历所有的节点。

2、如果当前节点有右孩子,但是没有左孩子,直接返回false。

3、如果当前节点并不是左右孩子全有,那之后的节点必须都为叶节点,否则返回false。


4、遍历过程如果不返回false,遍历结束返回true即可。

注意:

面试中,二叉树节点类型仅包括:数据项、左孩子、右孩子。

工程上的二叉树节点类型,往往多一条指向父节点的指针。

一般默认面试中的二叉树节点结构不包括指向父节点的指针,除非特别说明。


案例七:

后继节点和前驱节点:

后继节点:一个节点的后继节点是指,这个节点在中序遍历序列中的下一个节点。

中序遍历的序列为:DBEAFCG.

现在有一种新的二叉树节点类型,定义如大家所见。

该结构比普通二叉树节点结构多了一个指向父节点的parent指针。假设有一棵这种类型的节点组成的二叉树,树中每个节点的parent指针都正确地指向自己的父节点,头节点的parent指向空。只给定在二叉树中的某个节点node,该节点并不是头节点,可能是树中任何一个节点,请实现返回node的后继节点的函数。

普通解法

1、通过node节点的parent指针不断向上找搭配头节点。

2、通过找到的头节点,做整棵树的中序遍历,生成中序遍历序列。

3、在中序遍历序列中,node节点的下一个节点满就是其后续节点。

普通方法要遍历所有节点,时间复杂度为 O(N),额外空间复杂度为O(N)。

最优解法:

如果node节点和node后继节点之间实际距离为L,最优解法只用走过L个节点,时间复杂度为O(L),额外空间复杂度为O(1)

情况一:

如果node有右子树,那么后继节点就是右子树上最左边的节点。

情况二:

如果node没有右子树,那么先看node 是不是node父节点的左孩子,如果是左孩子,那么此时node的父节点就是node的后继节点。如果是右孩子,就向上寻找node的后继节点。假设向上移动到的节点记为s,s的父节点记为p,如果发现s是p的左孩子,那么节点p就是node的后继节点,否则就一直向上移动。

情况三:

如果一直向上寻找,都移动到空节点了,还是没有发现node的后继节点,说明node根本不存在后继节点,返回空即可。


案例八:

请把一段纸条竖着放在桌子上,然后从纸条的下边向上方对折1次,压出折痕后展开,此时折痕是凹下去的,即折痕突起的方向指向纸条的背面。

如果从纸条的下边向上方连续对折2次,压出折痕后展开,此时有三条折痕,从上到下一次是下折痕、下折痕和上折痕。给定一个输入参数N,代表纸条都从下边向上方连续对折N次,请从上到下打印所有折痕的方向。

思路:

第一次对折产生的折痕:


第二次对折产生的折痕(会在第一次折痕的上下两侧分别产生下折痕和上折痕):


第三次对折产生的折痕:会在每一条第二次对折产生的折痕的上下两侧产生上折痕和下折痕。


以后每次都这样,对折i次后,会在每一条i-1次对折后产生的折痕的上下两侧产生一个上折痕和下折痕。折痕的结构是一个满二叉树结构,这个满二叉树的头节点为下折痕,每一棵左子树的头节点都是上折痕,每一棵右子树的头节点都是下折痕。

实现先右再中、最后左的先序遍历,就是所有折痕的打印顺序。(右、中、左)


案例九:

一棵二叉树原本是搜索二叉树,但是其中有两个节点调换了位置,使得这棵二叉树不再是搜索二叉树,请找到这两个错误节点。

1、对二叉树进行中序遍历,一次出现的节点值会一直升序,如果两个节点值错了,会出现降序。

2、如果在中序遍历时节点值出现了两次降序,第一个错误的节点为第一次降序时较大的节点,第二个错误的节点为第二次降序时较小的节点。比如原序列:1,2,3,4,5,出错序列:1,5,3,4,2,错误节点分别为5,3中的5,4,2中的2

3、如果在中序遍历时节点值只出现了一次降序,第一个错误的节点为这次降序时较大的节点,第二个错误的节点为这次降序时较小的节点。比如原序列:1,2,3,4,5,出错序列:1,2,4,3,5,出错序列:43

2和3可以总结为:第一个错误节点为第一次降序时较大的节点,第二个错误节点为最后一次降序时较小的节点。

所以此题依然是改写一个基本的二叉树中序遍历。


案例十:

从二叉树的节点A出发,可以向上或者向下走,但沿途的节点只能经过一次,当到达节点B时,路径上的节点数叫做A和B的距离。比如大家看到的图中,节点4和节点2的距离为2,节点5和节点6的距离为5。给定一棵二叉树的头节点head,求整棵树上节点间的最大距离。


思路:

一个以h为头的树上,最大距离只可能来自以下三种情况:

情况一:

h的左子树上的最大距离。

情况二:

h的右子树上的最大距离。

情况三:

h左子树上离h左孩子最远的距离,加上h自身这个节点,再加上h右子树上离h右孩子的最远距离,也就是两个节点分别来自h两侧子树的情况。

三个值中最大的那个就是以h为头的整棵树上最远的距离。

步骤:

1、整个过程为后序遍历,在二叉树的每棵子树上执行步骤2.

2、假设子树头为h,处理h左子树,得到两个信息,左子树上的最大距离记为LMax1,左子树上的距离h左孩子的最远距离记为LMax2。处理h右子树得到右子树上的最大距离记为RMax1,距离h右孩子的最远距离记为RMax2,那么跨h节点情况下的最大距离为LMax2 + 1 + RMAX2,这个值与LMax1和RMax1比较,最大值为,以头的树上的最大距离。

3、LMax2 + 1 就是h左子树上离h最远的点到h的距离,RMax2 + 1 就是h右子树上离h最远的点到h的距离,选两者中最大的一个作为h树上距离h最远的距离返回。

4、用返回长度为2的数组的方式,可以做的返回两个值。


案例十一:

给定一棵二叉树的头节点head,已知其中所有节点的值都不一样,找到含有节点最多的搜索二叉子树,并返回这棵子树的头节点。例如,大家现在看到的图1这棵树,最大搜索子树就是图2这棵树。


思路:

以节点node为头的树中,最大的搜索二叉子树只可能来自以下两种情况

1、来自node左子树上的最大搜索二叉子树是以node左孩子为头的,并且来自node右子树上的最大搜索二叉子树是以node右孩子为头的,node左子树上的最大搜索二叉子树的最大值小于node的节点值,node右子树上的最大搜索二叉子树的最小值大于node的节点值,那么以节点node为头的整棵树都是搜索二叉树。

2、如果不满足第一种情况,说明以节点node为头的树整体不能连成搜索二叉树。这种情况下,以node为头的树上的最大搜索二叉子树是来自node的左子树上的最大搜索二叉子树和来自node的右子树上的最大搜索二叉子树之间,节点数较多的那个。

通过以上分析,求解的具体过程如下:

1、整体过程是二叉树的后序遍历。

2、遍历到当前节点记为cur时,先遍历cur的左子树并收集4个信息,分别是左子树上,最大搜索二叉子树的头节点记、节点数、树上最小值和树上最大值。再遍历cur的右子树收集4个信息,分别是右子树上搜索二叉子树的头节点、节点数、最小值和最大值。

3、根据步骤2所收集的信息,判断是否满足第一种情况,也就是是否以cur为头的子树,整体都是搜索二叉树。如果满足第一种情况,就返回cur节点,如果满足第二种情况,就返回cur节点,如果满足第二种情况,就返回左子树和右子树各自的最大搜索二叉树中,节点数较多的那个树的头节点。

4、对于如何返回4个信息,可以使用全局变量更新的方式实现。














猜你喜欢

转载自blog.csdn.net/lq_lq314/article/details/79267403
今日推荐