二叉树的题目练习总集(一次做到爽)

摘要:建立二叉树、前中后层序、求树高度、叶子结点个数、k层结点个数、结点是否存在于二叉树、两个叶子最近公共祖先、判断平衡树、求二叉树最远两个结点距离。


目录

分析需求:

一、节点

二、队列

三、树

1、树的基本方法:建立二叉树。

2、求前中后层遍历

3、求树的高度:

4、求叶子节点个数

5、求二叉树第k层的结点个数

6、判断一个结点是否在二叉树中? 

7、求两个结点的最近公共祖先 

8、判断一颗二叉树是否是平衡二叉树

9、求二叉树最远两个结点距离

10、镜像二叉树

11、由前序遍历和中序遍历重建一颗二叉树

12、输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。


分析需求:

  1. 首先二叉树的元素是一个一个节点所以我们要定义一个二叉树的节点类,节点左孩子也有右孩子,所以我们需要两个指针(变量)分别指向他们。
  2. 其次能代表二叉树在内存所在位置就是头结点的位置,所以我们还需要一个树类,树还要实现元素添加、先序、中序、后序、层序遍历的方法。
  3. 层序遍历需要用到队列,我通过列表实现一个简单的队列,最好不要用queue.Queue的队列,毕竟它是阻塞的。

一、节点

class Node:
    def __init__(self, data=None, left=None, right=None):
        self.data, self.left, self.right = data, left, right

当然其实我觉得这样写并不优雅,应该是一个变量一行。

class Node:
    def __init__(self, data=None, left=None, right=None):
         """"
        节点类,属性:
        data:存放节点的数值
        left:存放左孩子节点
        right:存放右孩子节点
        """
        self.data = data
        self.left = left
        self.right = right

二、队列

注意给这个类命名的时候最好不要和一些内置数据结构重名。

class Que:
    """
    实现先入先出,append是从后面添加元素,pop(0)删除第一个元素实现先出;
    pop是不安全的需要实现len方法来辅助,避免下标越界。
    """
    def __init__(self):
        self.que = []

    def __len__(self):
        return len(self.que)

    def append(self, item):
        self.que.append(item)

    def popleft(self):
        if len(self.que) > 0:
            return self.que.pop(0)

三、树

1、树的基本方法:建立二叉树。

最近复习了一下装饰器,@classmethod可以不实例化对象就可以使用类的方法。详细的操作看注释。

以下的所有方法都是写在树类里面的方法。

class Tree:
    def __init__(self, root=None):
        self.root = root
        self.leaf_count = 0

    @classmethod
    def build(cls, node_list2):
        """
            node_dict={}存放node节点的字典,
            第一个for循环遍历node_list的元素,把元素转换成节点,并存放到node_dict里面。
            第二个for循环遍历node_list给node节点的左孩子和右孩子赋值。
            那个因为用元组存放所以利用拆包把数据分离
            :param node_list: 存放节点数据是列表,但是列表的元素是字典不是节点
            :return: 返回树
        """
        node_dict = {}
        root = None
        for node_data in node_list2:
            data, *_ = node_data
            node_dict[data] = Node(data)
        for item in node_list2:
            data, left, right, is_root = item
            node = node_dict[data]
            if is_root:
                root = node
            node.left = node_dict.get(left)
            node.right = node_dict.get(right)
        return cls(root)

添加数据:

为什么我用元组?(面试问元组是什么可以这样回答)元组不仅仅是不可变序列,当元组的顺序不可变时,它的顺序就有了意义,就好像下列元素分别指代是data, left, right, is_root。这里还可以使用具名元组nametuple。

node_list1 = [
#data, left, right, is_root
    ('1', '2', '3', True),
    ('2', '4', '5', False),
    ('3', '6', '7', False),
    ('4', None, None, False),
    ('5', 8, None, False),
    ('6', None, None, False),
    ('7', '9', '10', False),
    ('8', None, None, False),
    ('9', None, None, False),
]

最后建立出来的树是这样的:

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

2、求前中后层遍历

   网上看见用@classmethod有点骚,利用@classmethod可以直接用类名.方法名调用不用新建实例对象。前中后层遍历直接用了递归。提一句,遍历叶子节点的顺序三者是一样的,跟是什么遍历无关。

 # 先序
    def preorder(self, subtree):
        if subtree:
            print(subtree.data)
            self.preorder(subtree.left)
            self.preorder(subtree.right)

    # 中序
    def midorder(self, subtree):
        if subtree:
            self.midorder(subtree.left)
            print(subtree.data)
            self.midorder(subtree.right)

    # 后序
    def postorder(self, subtree):
        if subtree:
            self.postorder(subtree.left)
            self.postorder(subtree.right)
            print(subtree.data)

    # 层序
    def BFS(self, subtree):
        que = Que()
        root = subtree
        que.append(root)
        while que:
            node = que.popleft()
            print(node.data)
            if node.left:
                que.append(node.left)
            if node.right:
                que.append(node.right)

测试: 

if __name__ == '__main__':
    tree = Tree.build(node_list1)
    # 先序答案:124583679
    print('----先序------')
    tree.preorder(tree.root)
    print('----中序------')
    # 中序答案:428516397
    tree.midorder(tree.root)
    print('----后序------')
    # 后序答案:485269731
    tree.postorder(tree.root)
    print('----层序------')
    # 层序答案:123456789
    tree.BFS(tree.root)

结果: 

Connected to pydev debugger (build 171.4694.67)
----先序------
1
2
4
5
3
6
7
9
----中序------
4
2
5
1
6
3
9
7
----后序------
4
5
2
6
9
7
3
1
----层序------
1
2
3
4
5
6
7
9

Process finished with exit code 0

3、求树的高度:

  递归到叶子结点的时候,你会发现他的high=0,当return时就会进行left_high+1,得到结果后,同时会判断是否有有结点,没有右结点就right_high+1继续return right_high和left_high不断+1最后返回的是最大的高度并加上根节点的高度1。

# 二叉树的高度
    def high(self, subtree):
        """
        求二叉树的高度(深度),通过首先当只有一个结点的时候此时的高度为1,
        有左子树就遍历左子树,高度不断增加,右子树同理,求出两颗子树的高度,
        通过比较左子树的高度和右子树的高度,选择最大的一者,加上根节点的高度得到最后的高度
        :param subtree:
        :return:
        """
        left_high = 0
        right_high = 0
        if subtree.left:
            left_high = self.high(subtree.left)
        if subtree.right:
            right_high = self.high(subtree.right)
        return right_high + 1 if right_high > left_high else left_high + 1

测试: 

if __name__ == '__main__':
    tree = Tree.build(node_list1)
    high = tree.high(tree.root)
    print("The high of the tree is ",high)

结果:

tree's high is  4

Process finished with exit code 0

4、求叶子节点个数

  遍历结点左子树,遍历结点右子树,假如遇到左右子树都为空的情况下就把leaf_num+=1,最后才返回结点数。

    def get_leafnum(self, subtree):
        """
        遍历结点左子树,遍历结点右子树,假如遇到左右子树都为空的情况下就把leaf_num+=1,
        最后才返回结点数。
        :param subtree:
        :return:
        """
        if subtree:
            if subtree.left is None and subtree.right is None:
                self.leaf_count += 1
            self.get_leafnum(subtree.left)
            self.get_leafnum(subtree.right)
        return self.leaf_count

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

5、求二叉树第k层的结点个数

  我审题不当理解成了求每一层的结点个数,不过也没差,局部变量很多,请看图。

    # 求二叉树第k层的结点个数
    def get_k_node(self, k, subtree):
        """
        求第k层元素,选择层序遍历,
        :param k: 第k层
        :param subtree: 传入根节点
        :return: 第k层元素个数
        """
        queue = Que()  # 类似于层序遍历队列的作用
        if subtree:  # 判断是不是空树
            queue.append(subtree)  # 把根结点添加到队列
            parent_num = 1  # 为当前双亲结点的数量,记录队列弹出元素,当它减为0时,为换层的标志
            child_num = 0  # 新添加到队列的子节点个数
            plies = 1  # 层数
            ele_per_plies = []  # 存放每一层有多少元素的列表
            ele_per_plies.append(-1)  # 列表是从0开始的,-1表示第0层搜索不合法
            ele_per_plies.append(parent_num)  # 第一层只有一个结点(根结点)
        else:
            return 0  # 树为空返回0表示没有元素
        while plies < k:
            node = queue.popleft()
            parent_num -= 1
            if node.left:
                queue.append(node.left)
                child_num += 1
            if node.right:
                queue.append(node.right)
                child_num += 1
            if parent_num == 0:  # 换层的标志
                plies += 1
                parent_num = child_num
                ele_per_plies.append(parent_num)
                child_num = 0  # 换层后子结点数清零
        return ele_per_plies[k]

6、判断一个结点是否在二叉树中? 

因为递归返回的值会传给上个调用它的函数,所以当你在判断data值是否相等时不能return否则会不断返回给上一级最后函数的结果返回None,所以我选择了返回类变量,作为是否存在元素的标志。

# 6、判断一个结点是否在二叉树中?
    def is_in_tree(self, subtree, find_node):
        """
        因为递归返回的值会传给上个调用它的函数,所以当你在判断data值是否相等时不能return
        否则会不断返回给上一级最后函数的结果返回None,所以我选择了返回类变量,作为是否存在元素的标志。
        :param subtree: 传入根节点
        :param find_node: 要查询的node
        :return: True则结点在树,False则结点不在树
        """
        find_data = find_node.data
        if subtree:
            data = subtree.data
            if find_data == data:
                self.in_tree = True
            self.is_in_tree(subtree.left, find_node)
            self.is_in_tree(subtree.right, find_node)
        return self.in_tree

测试 :

if __name__ == '__main__':
    tree = Tree.build(node_list1)
    node = Node(data='4')
    if tree.is_in_tree(tree.root, node):
        print("The node is in the tree")
    else:
        print("The node is not in the tree")
#---------换成10-------------------
if __name__ == '__main__':
    tree = Tree.build(node_list1)
    node = Node(data='10')
    if tree.is_in_tree(tree.root, node):
        print("The node is in the tree")
    else:
        print("The node is not in the tree")

结果1:

node is in the tree

Process finished with exit code 0

结果2: 

The node is not in the tree

Process finished with exit code 0

7、求两个结点的最近公共祖先 

因为要往上找父结点我立马就想到了用递归,这一次我们找结点4和结点9的最近父结点,下面有四个if语句分别编号1到4, 一直遍历左子树,第一个if语句第二个判断条件成立,结点4返回一个结点4,回到结点2遍历右子树结点5,遍历结点5左子树为8第一个if语句判断成立,返回结点8,右子树为None返回None给结点5,此时第三个if条件成立左结点不为空返回左子树即左结点8,此时结点2符合左右结点均不为空,返回结点2,然后遍历结点1的右子树,像刚才那样最后返回None,右子树为空,直接走第三个if,返回结点2,最后返回的是节点对象,取它的data属性即可。

白色是遍历路径,灰色不要在意,有颜色是有返回值,不同的返回值颜色不一样。

# 7、求两个结点的最近公共祖先
    def find_near_dad(self, subtree, node1, node2):
        if subtree is None or node1.data == subtree.data or node2.data == subtree.data:
            return subtree
        left = self.find_near_dad(subtree.left, node1, node2)
        right = self.find_near_dad(subtree.right, node1, node2)
        if left is not None and right is not None:
            return subtree
        # 左结点不为空如:结点5
        if left is not None:
            return left
        if right is not None:
            return right
        return None

测试:

if __name__ == '__main__':
    tree = Tree.build(node_list1)
    node4 = Node(data='4')
    node8 = Node(data='8')
    father = tree.find_near_dad(tree.root, node4, node8)
    print(father.data)

结果: 

2
Process finished with exit code 0

测试别的结点:

有点粗暴。

if __name__ == '__main__':
    tree = Tree.build(node_list1)
    node4 = Node(data='4')
    node8 = Node(data='8')
    node9 = Node(data='9')
    node5 = Node(data='5', left=node8)
    father = tree.find_near_dad(tree.root, node4, node5)
    dad = tree.find_near_dad(tree.root, node8, node9)
    print("father", father.data)
    print("dad", dad.data)
father 2
dad 1
Process finished with exit code 0

8、判断一颗二叉树是否是平衡二叉树

调用之前写的求高度函数,求出每一棵子树的高度,左右子树的高度相差不超过1就说明这棵树平衡。 要小心到叶结点时可能为空传给求高度函数不能为空,因为递归不能突然终止,所以只能添加balance作为标记。balance是在只有一个根结点的时候,它是平衡的。 只要遇到height超过1的就是不平衡,通过与运算,凡是遇到FALSE一直都为FALSE。

下图是测试的二叉平衡树。

    def is_balance(self, subtree):
        """
        调用之前写的求高度函数,求出每一棵子树的高度,
        左右子树的高度相差不超过1就说明这棵树平衡。
        要小心到叶结点时可能为空传给求高度函数不能为空,
        因为递归不能突然终止,所以只能添加balance作为标记。
        balance是在只有一个根结点的时候,它是平衡的。
        只要遇到height超过1的就是不平衡,通过与运算,凡是遇到FALSE一直都为FALSE
        :param subtree: 传入根节点
        :return: 平衡返回TRUE否则返回FALSE
        """
        balance = True
        l_height = 0
        r_height = 0
        if subtree:
            if subtree.left:
                l_height = self.high(subtree.left)
            if subtree.right:
                r_height = self.high(subtree.right)
            height = abs(l_height - r_height)
            if height <= 1:
                temp = True
                balance &= temp
            else:
                temp = False
                balance &= temp
            self.is_balance(subtree.left)
            self.is_balance(subtree.right)
        return balance

测试:

balanced_list = [
    ('1', '2', '3', True),
    ('2', '4', '5', False),
    ('3', '6', '7', False),
    ('4', None, None, False),
    ('5', None, None, False),
    ('6', None, None, False),
    ('7', None, None, False),
]

node_xie = [
    ('x', 'y', None, True),
    ('y', 'z', None, False),
    ('z', None, None, False)
]

if __name__ == '__main__':
    tree = Tree.build(balanced_list)
    if tree.is_balance(tree.root):
        print("This is a balanced tree")
    else:
        print("This is not a balanced tree")

结果:

balance_list生成的树:
This is a balanced tree

Process finished with exit code 0

node_xie生成的树:
This is not a balanced tree

Process finished with exit code 0

9、求二叉树最远两个结点距离

获取子树的高度,最大的距离设定为最高的左子树与最高右子树的和,可能经过根节点,也可能不经过根节点。测试的时候要测试极端的情况,例如斜树。

根(结点1)结点开始遍历,计算左子树(2为根的树)的高度,然后计算右子树(3为根的右子树)的高度,当前最大的距离等于左子树的高度加上右子树的高度。先序遍历左子树,根节点的左孩子(结点2)充当根结点,计算该结点的左子树高度(以结点4为根的树)和右子树(以结点5为根的树)高度通过比较当前根结点(结点2)和上一个根结点(结点1)的最大距离,假如当前根结点(结点2)的得到的最大距离比上一根结点(结点1)的最大距离要大,则记录当前根节点(结点)的最大距离为max_len,否则还是结点1记录的为最大距离。因为每一次递归都需要比较前一根结点的值,所以max_len应该作为参数,传入下一次递归。

一定要设置左子树和右子树默认的高度为0,假如只是把r_height和l_height设为全局变量,当树是一棵斜树(下面会给出斜树)的时候,没有右子树,在计算max_len的时候就会r_height就不会被定义,因此也不能进行加法运算。

# 9、求二叉树最远两个结点距离
    def fathest_nodes(self, subtree, max_l):
        """
        根节点开始遍历,计算左子树的高度,然后计算右子树的高度,
        当前最大的距离等于左子树的高度加上右子树的高度,
        先序遍历左子树,根节点的左孩子充当根节点,计算该节点的左子树高度和右子树高度
        通过比较当前根节点和上一个根节点的最大距离,假如当前根节点的得到的最大距离比
        上一节点的最大距离要大,则记录当前根节点的最大距离为max_len,
        因为每一次递归都需要比较前一根结点的值,所以max_len应该作为参数,
        用于每一次递归。
        :param subtree: 传入根节点
        :param max_l:最大长度初始值为0
        :return:最远两个结点距离
        """
        l_height = 0 # 左子树的高度默认为0
        r_height = 0 # 右子树的高度默认为1
        max_len = max_l # 记录之前递归得到的最大距离
        if subtree:
            if subtree.left: # 求出左子树高度
                l_height = self.high(subtree.left)
            if subtree.right: # 求出右子树高度
                r_height = self.high(subtree.right)
            node_len = r_height + l_height #结点间最大距离为左右子树高度之和
            if max_len < node_len: 
                max_len = node_len 
            self.fathest_nodes(subtree.left, max_len) # 一直遍历左子树
            self.fathest_nodes(subtree.right, max_len) # 遍历右子树
            return max_len # 返回当前最大路径
        return 0 # 当树为空时返回结点间最大距离是0

测试: 

if __name__ == '__main__':
    tree = Tree.build(node_list1)
    print("node_list1 tree:", tree.fathest_nodes(tree.root, 0))
    print("balanced tree:", tree.fathest_nodes(tree.root, 0))
    print("xie tree:", tree.fathest_nodes(tree.root, 0))

结果: 

node_list1 tree: 6
balanced tree: 6
xie tree: 6

Process finished with exit code 0

10、镜像二叉树

就是把左右子树交换,然后遍历左子树,再遍历右子树。

    def Mirror(self, root):
        # write code here
        if root is None:
            return root
        if root.left or root.right:
            temp = root.right
            root.right = root.left
            root.left = temp
        if root.left:
            self.Mirror(root.left)
        if root.right:
            self.Mirror(root.right)
        return root

测试: 

if __name__ == '__main__':
    tree = Tree.build(node_list1)
    print("-----镜像前-------")
    print("前序:", end="")
    tree.preorder(tree.root)
    print("中序:", end="")
    tree.midorder(tree.root)
    tree.Mirror(tree.root)
    print("\n-----镜像后-------")
    print("前序:", end="")
    tree.preorder(tree.root)
    print("中序:", end="")
    tree.midorder(tree.root)

结果: 

-----镜像前-------
前序:1 2 4 5 8 3 6 7 9 中序:4 2 8 5 1 6 3 9 7 
-----镜像后-------
前序:1 3 7 9 6 2 5 8 4 中序:7 9 3 6 1 5 8 2 4 
Process finished with exit code 0

11、由前序遍历和中序遍历重建一颗二叉树

前序遍历第一个结点是根结点,在中序遍历中找到该结点位置它的左侧所有元素是左子树,右侧所有元素是右子树。 注意此时左子树所有结点个数正好是根结点的下标+1,传参数的时候要注意一下是左闭右开区间。

  # 通过前序和中序遍历的结果重建二叉树
  def reConstructBinaryTree(self, pre, tin):
        """
        前序遍历第一个结点是根结点,在中序遍历中找到该结点位置它的左侧所有元素是左子树,右侧所有元素是右子树。
        注意此时左子树所有结点个数正好是根结点的下标+1,传参数的时候要注意一下是左闭右开区间。
        :param pre:前序遍历的数组
        :param tin:中序遍历的结点
        :return:重建子树的根结点
        """
        if len(pre) == 0 and len(tin) == 0:
            return None
        root = TreeNode(pre[0])
        index = tin.index(pre[0])
        # 中序根结点的下标是当前前序遍历的左子树的所有结点的个数
        root.left = self.reConstructBinaryTree(pre[1:index+1], tin[:index])
        root.right = self.reConstructBinaryTree(pre[index+1:], tin[index + 1:])
        return root

测试:

if __name__ == '__main__':
    pre = [1, 2, 4, 7, 3, 5, 6, 8]
    tin = [4, 7, 2, 1, 5, 3, 8, 6]
    pre_1 = []
    tin_1 = []
    tree = Tree()
    root = tree.reConstructBinaryTree(pre, tin)
    print("前序:", end="")
    tree.preorder(root)
    print("\n中序:", end="")
    tree.midorder(root)

结果:

前序:1 2 4 7 3 5 6 8 
中序:4 7 2 1 5 3 8 6 
Process finished with exit code 0

12、输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

1、二叉搜索树的中序遍历输出是一个顺序递增的列表。

2、把所有节点放到列表中进行遍历,记得要去重。

3、把每一个结点i(列表中的下标)的左子树指向前结点i-1,把右子树指向i+1个结点。

class Solution:
    def __init__(self):
        self.tree = []

    def Convert(self, pRootOfTree):
        # write code here
        if pRootOfTree is None:
            return None
        if pRootOfTree and pRootOfTree.left is None and pRootOfTree.right is None:
            return pRootOfTree
        self.midorder(pRootOfTree)
        for i in range(len(self.tree)-1):
            self.tree[i].right = self.tree[i+1]
            self.tree[i+1].left = self.tree[i]
        return self.tree[0]

    def midorder(self, pRootOfTree):
        if pRootOfTree.left:
            left = self.midorder(pRootOfTree.left)
            if left not in self.tree:
                self.tree.append(left)
        if pRootOfTree not in self.tree:
            if pRootOfTree not in self.tree:
                self.tree.append(pRootOfTree)
        if pRootOfTree.right:
            right = self.midorder(pRootOfTree.right)
            if right not in self.tree:
                self.tree.append(right)
        return pRootOfTree

测试: 

if __name__ == '__main__':
    so = Solution()
    tree = Tree.build(node_list1)
    arr = so.Convert(tree.root)
    print(arr)
    for i in arr:
        if not i.left:
            lefval = "None"
        else:
            lefval = i.left.val
        if not i.left:
            rigval = "None"
        else:
            rigval = i.left.val
        print("[",lefval,",",i.val,",",rigval,"]", end = " ")

结果:

平凡大佬就用了16行把程序写出来了,思路和我一模一样,平凡大佬的代码真的超级简洁超级优雅!平凡大佬他很喜欢借助list当辅助。能阅读平凡大佬的代码真的是一种福报。

发布了46 篇原创文章 · 获赞 75 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_38875300/article/details/89299647