[算法入土之路]二叉树

一、二叉树知识点概述

二叉树节点结构

        class Node:
            def __init__(self, value, left=None, right=None, parent=None):
                self.value = value
                self.left = left
                self.right = right
                self.parent = parent # 非必须

搜索二叉树      

        一棵树中的所有子树中, 所有的左孩子都比父节点小, 所有的右孩子都比父节点大

完全二叉树

        1 任意节点有右孩子没有左孩子  return false
        2 在不违背第一条件的情况下, 如果遇到左右孩子不全的情况下, 在加下来遇到的所有孩子都必须为 叶子节点 

平衡二叉树

        每一科子树的左树和右树的高度差不超过1

满二叉树

        满足 结点数量为 2^树的高度 - 1 的二叉树

二、树, 队列, 堆栈的简单数据结构

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

    def add_left(self, l_node):
        self.left = l_node

    def add_right(self, r_node):
        self.right = r_node

    def change_value(self, val):
        self.value = val

class Stack:
    def __init__(self):
        self.elems = []

    def push(self, item):
        self.elems.append(item)

    def pop(self):
        if not self.elems:
            raise "堆栈已经空了"
        return self.elems.pop()

    def is_empty(self):
        return self.elems == []

    def top(self):
        return self.elems[-1]

    def size(self):
        return len(self.elems)

class Queue:
    def __init__(self):
        self.elems = []

    def pop(self):
        if self.elems:
            return self.elems.pop(0)
        else:
            raise "队列已经空了"

    def push(self, item):
        self.elems.append(item)

    def size(self):
        return len(self.elems)

    def is_empty(self):
        return self.elems == []

    def top(self):
        return self.elems[0]

三、二叉树问题汇总

class BinaryTreeQues:
    def __init__(self, tree=None):
        self.tree = tree

    def pre(self):
        """
        前序遍历 根左右
        :return:
        """
        s = Stack()
        s.push(self.tree)
        pre_arr = []

        while s.size():
            pre_arr.append(s.pop())
            print(pre_arr[-1].value, end=" ")
            if pre_arr[-1].left:
                s.push(pre_arr[-1].left)
            if pre_arr[-1].right:
                s.push(pre_arr[-1].right)
        print()
        return pre_arr

    def post(self):
        """
        后序遍历 左右根
        :return:
        """
        # 注释代码为用堆栈存储遍历序列
        post_arr = []
        s1 = Stack()
        # s2 = Stack()
        s1.push(self.tree)

        while s1.size():
            # item = s1.pop()
            # s2.push(item)
            post_arr.append(s1.pop())
            if post_arr[-1].right:
                s1.push(post_arr[-1].right)
            if post_arr[-1].left:
                s1.push(post_arr[-1].left)
        # while s2.size():
        #     print(s2.pop().value, end=" ")
        # print()
        return list(reversed(post_arr))

    def mid(self):
        """
        中序遍历 左根右
        :return:
        """
        mid_arr = []

        def push_all_left(tree_node, stack: Stack):
            """
            将当前根节点的所有左孩子节点放入堆栈
            :param tree_node: 子树根节点
            :param stack: 辅助堆栈
            :return:
            """
            while tree_node:
                stack.push(tree_node)
                tree_node = tree_node.left

        s = Stack()  # 初始化一个堆栈
        push_all_left(self.tree, s)  # 将树的根节点的所有左孩子放入堆栈
        while s.size():
            mid_arr.append(s.pop())  # 在堆栈中拿出栈顶节点
            print(mid_arr[-1].value, end=" ")
            if mid_arr[-1].right:  # 如果有右孩子 则将右孩子节点为子树的根节点 并将该子树中的所有左孩子节点放入堆栈(包括自身)
                push_all_left(mid_arr[-1].right, s)
        print()
        return mid_arr

    @staticmethod
    def mid_by_left(head):
        """
        左神写的中序遍历算法
        Java代码转翻
        :param head: self.tree
        :return:
        """
        if head:
            s = Stack()
            while s.size() or head:
                if head:
                    s.push(head)
                    head = head.left
                else:
                    head = s.pop()
                    print(head.value, end=" ")
                    head = head.right
        print()
        return

    def DFS(self):
        """
        宽度优先遍历
        层序遍历
        :return:
        """
        if not self.tree:
            return
        q = Queue()
        q.push(self.tree)
        while not q.is_empty():
            item = q.pop()
            print(item.value, end=" ")
            if item.left:
                q.push(item.left)
            if item.right:
                q.push(item.right)
        print()
        return

    def DFS_with_level(self):
        """
        获取每层都有哪些节点, 和拥有最大节点数量的层的节点数量
        :return: 树每层的节点值, 树中有最大节点数量的层的节点数量
        """
        if not self.tree:  # 如果树不存在 则返回
            return
        nodes_dic = {}  # 纪录每层的节点值(顺序为由左至右)
        cur_level = level = 0  # 当前层数, 所在层数
        q = Queue()  # 辅助队列
        max_nodes_index = -1  # 拥有最多节点的层数的节点数量
        cur_nodes_index = 0  # 当前层的节点数量(暂时量)

        q.push((self.tree, level))  # 将根节点放入辅助队列

        while not q.is_empty():
            item, level = q.pop()  # 弹出节点, 节点所在层
            ''''''
            '''区域内代码为计算拥有最大节点数量的层的节点数量, 如若不需要可以删除'''
            if level == cur_level:  # 如果当前层和所在层一致
                cur_nodes_index += 1  # 当前层节点 +1
            else:
                cur_level = level  # 将当前层置换为所在层
                cur_nodes_index = 1  # 当前节点数量 置为 1
            if cur_nodes_index > max_nodes_index:  # 判断当前层节点数量是否大于了纪录的最大节点数
                max_nodes_index = cur_nodes_index
            ''''''
            if level in nodes_dic:  # 是否为该层的第一个节点
                nodes_dic[level].append(item.value)  # 如果不是 直接扩充
            else:
                nodes_dic[level] = [item.value]  # 如果是 新建列表并将当前节点添加到列表中
            if item.left:  # 如果有左孩子, 则将左孩子放入队列
                q.push((item.left, level + 1))
            if item.right:  # 如果有右孩子, 则将右孩子放入队列
                q.push((item.right, level + 1))
        return nodes_dic, max_nodes_index

    def DFS_get_max_nodes(self):
        """
        拥有最大节点数量的层的节点数量
        :return: 拥有最大节点数量的层的节点数量
        """
        cur_level = level = 0
        q = Queue()
        max_nodes = 0
        cur_node = 0
        q.push((self.tree, level))
        while not q.is_empty():
            item, level = q.pop()
            if cur_level != level:
                cur_level = level
                cur_node = 1
            else:
                cur_node += 1
            max_nodes = cur_node if cur_node > max_nodes else max_nodes
            if item.left:
                q.push((item.left, level + 1))
            if item.right:
                q.push((item.right, level + 1))
        return max_nodes

    @staticmethod
    def DFS_get_max_nodes_by_left(head):
        """
        左神 Java代码转翻
        拥有最大节点数量的层的节点数量
        :return: 拥有最大节点数量的层的节点数量
        """
        if not head:
            return
        q = Queue()
        cur_end = head  # 当前层数最后一个节点
        next_end = None  # 下一层最后一个节点
        cur_nodes_index = 0  # 当前层的节点数量
        max_nodes_index = 0  # 拥有最多节点的层的节点数量
        q.push(head)  # 放入根节点
        while not q.is_empty():
            item = q.pop()  # 拿出队列中的第一个节点
            cur_nodes_index += 1  # 当前层节点数量 + 1
            if item.left:  # 如果有左孩子
                q.push(item.left)  # 将左孩子放入队列
                next_end = item.left  # 下一层的最后一个节点置为放入队列中的最后一个节点
            if item.right:  # 同理
                q.push(item.right)
                next_end = item.right
            if item == cur_end:  # 如果当前节点为当前层的最后一个节点
                # 比较当前层的节点数量是否比以往层的最大节点数量多 如果多的话则给 max_nodes 赋值
                max_nodes_index = cur_nodes_index if max_nodes_index < cur_nodes_index else max_nodes_index
                cur_nodes_index = 0  # 当前层的节点数量置零
                cur_end = next_end  # 当前层的最后节点为下一层的最后一个节点
        return max_nodes_index

    def isBST(self):
        """
        Binary Search Tree
        是否为搜索二叉树
        方法: 中序遍历, 将原本的打印操作变为比较操作
        :return:
        """
        min_value = -1

        def push_all_left(tree_node, stack: Stack):
            while tree_node:
                stack.push(tree_node)
                tree_node = tree_node.left

        s = Stack()  # 初始化一个堆栈
        push_all_left(self.tree, s)  # 将树的根节点的所有左孩子放入堆栈
        while s.size():
            item = s.pop()  # 在堆栈中拿出栈顶节点
            '''核心代码'''
            if item.value <= min_value:
                return False
            else:
                min_value = item.value
            '''核心代码结束'''
            if item.right:  # 如果有右孩子 则将右孩子节点为子树的根节点 并将该子树中的所有左孩子节点放入堆栈(包括自身)
                push_all_left(item.right, s)
        return True

    def isCBT(self):
        """
        Complete Binary Tree
        是否为完全二叉树
            方法: 层序遍历
            1 任意节点有右孩子没有左孩子  return false
            2 在不违背第一条件的情况下, 如果遇到左右孩子不全的情况下,
                在加下来遇到的所有孩子都必须为 叶子节点
        :return:
        """
        if not self.tree:
            return True
        q = Queue()
        q.push(self.tree)
        flag = 0  # 0: 没有左右孩子 1: 有左孩子 2: 有右孩子 3: 有左右孩子 -1: 从此节点开始之后应全为叶子节点
        while not q.is_empty():
            item = q.pop()
            if item.left:
                if flag == -1:
                    return False
                flag += 1
                q.push(item.left)
            if item.right:
                if flag <= 0:  # 0 or -1
                    return False
                flag += 2
                q.push(item.right)
            print(f'({flag}, {item.value})', end=" ")
            if -1 <= flag <= 1:  # -1 or 0 or 1
                flag = -1
            elif flag:
                flag = 0
        return True

    '''
        二叉树解题套路(递归方法)(树形DP)
            1 分解子问题(搜寻条件)
            2 问询左右子树信息, 将所有可能性列出
            3 根据获取到的信息 做判断
        举例(是否我平衡二叉树)
            1 左子树是否为平衡二叉树, 右子树是否为平衡二叉树
                左侧的value_max < cur_node.value
                右侧的value_min > cur_node.value
            2 得到信息
            3 判断 
                左子树为平衡二叉树
                右子树是平衡二叉树
                左右子树加上父节点是否为平衡二叉树
                如果上述全部 满足, 则为平衡二叉树
    '''

    def is_BST_by_recursion(self):
        """
        是否为搜索二叉树 ( 递归解法 )
        :return:
        """

        def process(node: Node):
            if not node:
                return True, None, None
            l_is_bst, l_min, l_max = process(node.left)
            r_is_bst, r_min, r_max = process(node.right)
            is_bst = True
            max_ = min_ = node.value
            if l_min:
                min_ = min(min_, l_min)
            if r_min:
                max_ = max(max_, r_max)
            # 左子树存在, 左子树不是搜索二叉树或者 左侧的最大值大于等于了当前节点的值
            # 右子树存在, 右子树不是搜索二叉树或者 左侧的最小值小于等于了当前节点的值
            if (l_min and (not l_is_bst or l_max >= node.value)) \
                    or (r_min and (not r_is_bst or r_min <= node.value)):
                is_bst = False  # 该树不是搜索二叉树
            return is_bst, min_, max_  # 返回结果

        return process(self.tree)[0]

    def is_balanced_by_recursion(self):

        def process(node: Node):
            if not node:
                return True, 0
            l_height, l_is_balanced = process(node.left)  # 问询左子树是否为平衡二叉树
            r_height, r_is_balanced = process(node.right)  # 问询右子树是否为平衡二叉树

            height = max(l_height, r_height) + 1  # 获取当前节点发散出去的树的高度
            # 判断当前树是否为平衡二叉树
            #               左树              右树                 拼接后的树
            is_balanced = l_is_balanced and r_is_balanced and abs(l_height - r_height) < 2
            # 返回结果和当前树的高度
            return height, is_balanced

        return process(self.tree)[0]

    def is_FBT_by_recursion(self):
        """
        Full Binary Tree
        是否为满二叉树 ( 递归解法 )
        :return:
        """

        def process(node: Node):
            if not node:
                return 0, 0
            l_height, l_nodes_index = process(node.left)
            r_height, r_nodes_index = process(node.right)

            inner_height = max(l_height, r_height) + 1
            inner_nodes_index = l_nodes_index + r_nodes_index + 1

            return inner_height, inner_nodes_index

        height, node_index = process(self.tree)
        # 判断根据, 节点数量 等于 2^树的高度 - 1
        return node_index == (1 << height) - 1

    def lowest_common_ancestor(self, node_1, node_2):
        '''
        找到 node_1 和 node_2 的共同祖先
        :param node_1:
        :param node_2:
        :return: 树的学院关系表, node_1 和 node_2 的共同祖先
        '''
        ancestor_dic = {self.tree: [self.tree]}

        def generate_relational_table(node):
            if not node:  # 如果节点不存在返回
                return
            if node.left:  # 如果存在左孩子
                ancestor_dic[node.left] = ancestor_dic.get(node, []).copy()  # 复制父节点的血缘关系
                ancestor_dic[node.left].append(node.left)  # 添加本节点
                generate_relational_table(node.left)  # 向下获取血缘关系
            if node.right:
                ancestor_dic[node.right] = ancestor_dic.get(node, []).copy()
                ancestor_dic[node.right].append(node.right)
                generate_relational_table(node.right)

        generate_relational_table(self.tree)  # 生成整棵树的血缘关系

        def check_in():
            flag = None
            print("-----------------")
            for i in reversed(ancestor_dic.get(node_1)):  # 从下往上查血缘关系
                print(i.value)
                if i in ancestor_dic.get(node_2):  # 如果查找到的血缘关系在node_2的血缘关系表中
                    flag = i  # 获取共同祖先节点
                    break
            print("-----------------")
            return flag  # 返回

        return ancestor_dic, check_in()

    def lowest_common_ancestor_by_left(self, cur_node, node_1, node_2):
        '''
        左神 Java 代码转翻
            1 node_1 是 node_2 的祖先 或者反过来
            2 node_1 和 node_2 是通过汇聚找到一个共同祖先
        :param cur_node: 当前节点
        :param node_1: 要寻找共同祖先节点1
        :param node_2: 要寻找共同祖先节点2
        :return:    1: 空, node_1, node_2
                    2: 共同祖先(如果不是根节点 会直接向上传递 直到最后返回)
                    3: 左节点为 None 返回右节点 否则返回左节点
        '''
        # 当前节点为空, 当前节点为节点1 或者当前节点为节点2
        if not cur_node or cur_node == node_1 or cur_node == node_2:
            return cur_node  # 1
        left_node = self.lowest_common_ancestor_by_left(cur_node.left, node_1, node_2)
        right_node = self.lowest_common_ancestor_by_left(cur_node.rightm, node_1, node_2)
        # 如果左右节点都不为空
        if left_node and right_node:
            # 则表示当前节点为 node_1, node_2的共同祖先
            return cur_node  # 2
        return left_node if left_node else right_node  # 3

    @staticmethod
    def find_successor_node(node):
        """
        找 node 的后继节点
        在二叉树的中序遍历中, node的下一个节点叫 node 的后继节点
        :param node: 带有父节点指针的节点 且假定整棵树节点都有父节点的指针
        :return: node的后继节点, 如果没有 返回 None
        """
        if not node:
            return
        if rn := node.right:
            # 如果该节点有右子树, 则右子树的最左节点为 node 的后继节点
            while rn.left: rn = rn.left
            return rn
        else:
            parent = node.parent
            while parent and parent.right is node:
                '''如果父节点不为 None 且该节点为父节点的右孩子
                    向上查找一直查找到某一层为该层父节点的左孩子
                    则此时的父节点即为 node 的后继节点
                   如果最后找到父节点为 None 即找到了根节点 
                    则说明该节点为此树的最后一个节点 无后继节点 返回 None
                '''
                node = parent
                parent = node.parent
            return parent

    def Serialization(self):
        """
        序列化
        :return:
        """
        if not self.tree:
            return
        q = Queue()
        q.push(self.tree)
        dic = {self.tree.value: None}
        while not q.is_empty():
            item = q.pop()
            if item.left:
                dic[item.left.value] = f'{item.value}_l'
                q.push(item.left)
            if item.right:
                dic[item.right.value] = f'{item.value}_r'
        with open("test.txt", "w", encoding="utf8") as f:
            json.dump(dic, f)

    def deserialization(self, ser_dic):
        '''
        反序列化
        :param ser_dic:存储树结构的字典
        :return:
        '''
        node_list = {}
        for key, val in ser_dic.items():
            node_list[key] = Node(key)
            if val:
                value, loc = val.split("_")
                if loc == "l":
                    node_list[value].add_left(node_list[key])
                else:
                    node_list[value].add_right(node_list[key])
        for key in ser_dic:
            self.tree = node_list[key]
            return self.tree

    def origami_problem(self, n):
        """
        折纸问题
        :param n: 折了几次
        :return:
        """
        if n < 1:
            return
        root_node = Node(0)
        n_level_nodes = [root_node]
        last_level_nodes = []
        for i in range(1, n + 1):
            for node in last_level_nodes:
                node.add_left(Node(0))
                node.add_right(Node(1))
                n_level_nodes.extend([node.left, node.right])
            last_level_nodes = n_level_nodes.copy()
            n_level_nodes.clear()
        self.tree = root_node
        return self.mid()

四、自定义打印函数

def print_nodes_value(nodes_arr):
    '''
    根据遍历序列输出
    :param nodes_arr:
    :return:
    '''
    if not nodes_arr:
        return
    for i in nodes_arr:
        print(i.value, end=" ")
    print()

def print_nodes_in_dic_value(nodes_dic):
    for key, value in nodes_dic.items():
        print(key.value, end=" ")
        for val in value:
            print(val.value, end=" ")
        print()
    print()

五、主函数

if __name__ == '__main__':
    # tree_10 = Node(10)
    # tree_9 = Node(9)
    # tree_8 = Node(8)
    # tree_6 = Node(6, )
    # tree_1 = Node(1, )
    # tree_4 = Node(4, )
    # tree_7 = Node(7, tree_6, tree_8)
    # tree_2 = Node(2, tree_1)
    # tree_3 = Node(3, tree_2, tree_4)
    # tree_5 = Node(5, tree_3, tree_7)
    # b = BinaryTreeQues(tree_5)
    # # 层序遍历
    # b.DFS_with_level()
    # # 获取拥有最多节点的层的节点数量
    # res = b.DFS_get_max_nodes()
    # res = b.DFS_get_max_nodes_by_left()
    # # 中序遍历
    # res = b.mid()
    # print_nodes_value(res)
    # # 是否为搜索二叉树
    # res = b.isBST_by_recursion()
    # # 是否为完全二叉树
    # res = b.isCBT()
    # # 是否为满二叉树
    # res = b.is_FBT_by_recursion()
    # print(res)
    # # 查找共同祖先
    # res, check_res = b.lowest_common_ancestor(tree_1, tree_4)
    # print_nodes_in_dic_value(res)
    # print(check_res.value)
    # # 序列化和反序列化
    # b.Serialization()
    # with open("test.txt", "r", encoding="utf8") as f:
    #     dic = json.load(f)
    b = BinaryTreeQues()
    # res = b.deserialization(dic)
    # b.DFS()
    res = b.origami_problem(3)
    print_nodes_value(res)

本文根据 左神课程编写(一周刷爆LeetCode,算法大神左神(左程云)耗时100天打造算法与数据结构基础到高级全家桶教程,直击BTAJ等一线大厂必问算法面试题真题详解_哔哩哔哩_bilibili)

其中代码部分 除标_by_left 其余均为博主自己编写(可能有些跟视频中的算法相重合)

你可能感兴趣的:(算法入土之路,算法,数据结构,python)