[总结]树

树作为一种基本的数据结构,也是算法题常考的题型。基本的如树的遍历,树的高度,树的变种数据结构等。

树的遍历

树的遍历有四种:前序,中序,后序,层次。都需要掌握其递归与非递归方式。

[leetcode]94.Binary Tree Inorder Traversal

中序遍历

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        p = root
        stack = []
        res = []
        while p or stack:
            while p:
                stack.append(p)
                p = p.left
            p = stack.pop()
            res.append(p.val)
            p = p.right
        return res
[leetcode]102.Binary Tree Level Order Traversal

层次遍历

import collections
class Solution:
  def levelOrder(self, root: TreeNode) -> List[List[int]]:
      res = []
      if not root: return res
      queue = collections.deque()
      queue.append(root)
      while queue:
          level = []
          for i in range(len(queue)):
              curr = queue.popleft()
              level.append(curr.val)
              if curr.left:
                  queue.append(curr.left)
              if curr.right:
                  queue.append(curr.right)
          res.append(level)
      return res
[leetcode]144.Binary Tree Preorder Traversal

前序遍历

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    # def preorderTraversal(self, root: TreeNode) -> List[int]:
    #     """
    #     :type root: TreeNode
    #     :rtype: List[int]
    #     """
    #     res = []
    #     self.dfs(root,res)
    #     return res
    # def dfs(self,root,res):
    #     if not root:
    #         return
    #     res.append(root.val)
    #     self.dfs(root.left,res)
    #     self.dfs(root.right,res)

    def preorderTraversal(self, root: TreeNode) -> List[int]:
        res = []
        stack = []
        p = root
        if not root:return res
        while p or stack:
            while p:
                stack.append(p)
                res.append(p.val)
                p = p.left
            p = stack.pop()
            p = p.right
        return res
[leetcode]145.Binary Tree Postorder Traversal

后序遍历

class Solution:
    def postorderTraversal1(self, root: TreeNode) -> List[int]:
        """
        :type root: TreeNode
        :rtype: List[int]
        """
        res = []
        if not root:return res
        self.dfs(root,res)
        return res
    def dfs(self,root,res):
        if not root:return
        self.dfs(root.left,res)
        self.dfs(root.right,res)
        res.append(root.val)

    def postorderTraversal2(self, root):
        """
        :type root: TreeNode
        :rtype: List[int]
        """
        #后续遍历的非递归解法,双压栈版
        res=[]
        stack=[]
        if not root:
            return res
        stack.append(root)
        stack.append(root)
        while (stack):
            p=stack.pop()
            if(stack and p==stack[-1]):
                if (p.right):
                    stack.append(p.right)
                    stack.append(p.right)
                if (p.left):
                    stack.append(p.left)
                    stack.append(p.left)
            else:
                res.append(p.val)
        return res

    def postorderTraversal3(self, root):
        ans = []
        if root == None:
            return ans
        stack = [root]
        while stack:
            p = stack.pop()
            ans.append(p.val)
            if p.left:
                stack.append(p.left)
            if p.right:
                stack.append(p.right)
        ans.reverse()
        return ans
[leetcode]297.Serialize and Deserialize Binary Tree

树的序列化与去序列化。

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Codec:
    def serialize(self, root):
        """Encodes a tree to a single string.

        :type root: TreeNode
        :rtype: str
        """
        res = []
        self.preorder(root, res)
        # 将结点值序列转化为一个字符串
        return ','.join(res)

    def preorder(self, root, res):
        if not root:
            # 对于空结点,返回#字符加以标识
            res.append('#')
            return
        res.append(str(root.val))
        self.preorder(root.left, res)
        self.preorder(root.right, res)

    def deserialize(self, data):
        """Decodes your encoded data to tree.

        :type data: str
        :rtype: TreeNode
        """
        res = data.split(',')
        root = self.preorderdes(res)
        return root

    def preorderdes(self, res):
        if not res:
            return None
        # 遇到#字符,直接删除,返回空结点
        if res[0] == '#':
            del res[0]
            return None
        root = TreeNode(int(res[0]))
        del res[0]
        root.left = self.preorderdes(res)
        root.right = self.preorderdes(res)
        return root

# Your Codec object will be instantiated and called as such:
# codec = Codec()
# codec.deserialize(codec.serialize(root))
[leetcode]450.Delete Node in a BST

删除二叉搜索树的一个节点。递归查找右子树的最小值节点。

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def deleteNode(self, root: TreeNode, key: int) -> TreeNode:
        if not root: return None
        if root.val == key:
            if not root.right:
                return root.left
            else:
                right = root.right
                while right.left:
                    right = right.left
                root.val, right.val = right.val, root.val
        root.left = self.deleteNode(root.left, key)
        root.right = self.deleteNode(root.right, key)
        return root

树+dfs

[leetcode]113.Path Sum II

深度优先搜索,查找结果。

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def pathSum(self, root: TreeNode, sum: int) -> List[List[int]]:
        res = []
        self.dfs(root,sum,[],res)
        return res

    def dfs(self,root,sum,result,res):
        if not root:return
        if not root.left and not root.right and root.val == sum:
            result.append(root.val)
            res.append(result)
            return
        self.dfs(root.left,sum-root.val,result+[root.val],res)
        self.dfs(root.right,sum-root.val,result+[root.val],res)
[leetcode]124.Binary Tree Maximum Path Sum 设置全局变量

树结构显然用递归来解,解题关键:
1、对于每一层递归,只有包含此层树根节点的值才可以返回到上层。否则路径将不连续。
2、返回的值最多为根节点加上左右子树中的一个返回值,而不能加上两个返回值。否则路径将分叉。
在这两个前提下有个需要注意的问题,最上层返回的值并不一定是满足要求的最大值,因为最大值对应的路径不一定包含root的值,可能存在于某个子树上。因此解决方案为设置全局变量maxSum,在递归过程中不断更新最大值。

class Solution:
    def maxPathSum(self, root: TreeNode) -> int:
        self.res = -float('inf')
        if  not root:
            return 0
        self.dfs(root)
        return self.res

    def dfs(self,root):
        if not root:
            return 0
        left = self.dfs(root.left)
        right = self.dfs(root.right)
        self.res = max(self.res,root.val+max(0,left)+max(0,right))
        return max(left+root.val,right+root.val,root.val)
[leetcode]95.Unique Binary Search Trees II

对于本题来说,采取的是自底向上的求解过程。
1.选出根结点后应该先分别求解该根的左右子树集合,也就是根的左子树有若干种,它们组成左子树集合,根的右子树有若干种,它们组成右子树集合。
2.然后将左右子树相互配对,每一个左子树都与所有右子树匹配,每一个右子树都与所有的左子树匹配。然后将两个子树插在根结点上。
3.最后,把根结点放入链表中。

class Solution:
    def generateTrees(self, n: int) -> List[TreeNode]:
        if n == 0:
            return []
        return self.dfs(1, n+1)

    def dfs(self, start, end):
        result = []
        for i in range(start, end):
            for l in self.dfs(start, i) or [None]:
                for r in self.dfs(i+1, end) or [None]:
                    node = TreeNode(i)
                    node.left, node.right  = l, r
                    result.append(node)
        return result
[leetcode]236.Lowest Common Ancestor of a Binary Tree

我们可以用递归来实现,在递归函数中,我们首先看当前结点是否为空,若为空则直接返回空,若为p或q中的任意一个,也直接返回当前结点。否则的话就对其左右子结点分别调用递归函数,由于这道题限制了p和q一定都在二叉树中存在,那么如果当前结点不等于p或q,p和q要么分别位于左右子树中,要么同时位于左子树,或者同时位于右子树
此题还有一种情况,若题目中没有明确说明p和q是否是树中的节点,如果不是,应该返回NULL,而上面的方法就不正确了

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        if root == None or root == p or root == q:
            return root
        left = self.lowestCommonAncestor(root.left,p,q)
        right = self.lowestCommonAncestor(root.right,p,q)

        if left and right:
            return root
        else:
            return left if left else(right)
[leetcode]337.House Robber III

定义dfs函数, 返回两个值: 从当前节点偷能获取的最大值rob_now和从子节点开始偷能获得的最大值rob_later.Base case是当root为空时, 返回(0, 0).如果从当前节点偷, 那么左右的子节点不能偷, 如果从子节点开始偷, 那么总金额为两个节点能偷到的最大值的和.

class Solution:
    def rob(self, root: TreeNode) -> int:
        return max(self.dfs(root))

    def dfs(self,root):
        # return the max money if rob this root and the max money if not rob this root
        if not root: return (0, 0)
        left, right = self.dfs(root.left), self.dfs(root.right)
        rob_now = root.val + left[1] + right[1]
        rob_later = max(left) + max(right)
        return (rob_now, rob_later)

字典树

字典树和线段树作为高级的树结构,都有其特定的构成方式与解题思路。
字典树,又称前缀树或单词查找树。字典树主要有如下三点性质:

  • 根节点不包含字符,除根节点意外每个节点只包含一个字符。
  • 从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。
  • 每个节点的所有子节点包含的字符串不相同。

它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。缺点就是空间开销大。
对于N个字符串,如果其平均长度为M,则建立字典树的算法复杂度为\(O(M*N)\),在字典树上查找某个长度为M的字符串的算法复杂度\(O(M)\)
下面以几个例题说明。

[leetcode]208.Implement Trie (Prefix Tree)
class Node(object):
    def __init__(self):
        self.children = collections.defaultdict(lambda :Node())
        self.isword = False

class Trie(object):

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.root = Node()

    def insert(self, word):
        """
        Inserts a word into the trie.
        :type word: str
        :rtype: void
        """
        current = self.root
        for w in word:
            current = current.children[w]
        current.isword = True

    def search(self, word):
        """
        Returns if the word is in the trie.
        :type word: str
        :rtype: bool
        """
        current = self.root
        for w in word:
            # current = current.children.get(w)
            # if current == None:
            #     return False
            if w  not in current.children:return False
            current = current.children[w]
        return current.isword

    def startsWith(self, prefix):
        """
        Returns if there is any word in the trie that starts with the given prefix.
        :type prefix: str
        :rtype: bool
        """
        current = self.root
        for w in prefix:
            # current = current.children.get(w)
            # if current == None:
            #     return False
            if w  not in current.children:return False
            current = current.children[w]
        return True

# Your Trie object will be instantiated and called as such:
# obj = Trie()
# obj.insert(word)
# param_2 = obj.search(word)
# param_3 = obj.startsWith(prefix)
[leetcode]212.Word Search II

将待查找的单词存放在Trie(字典树)中,利用DFS(深度优先搜索)在board中搜索即可,每次查找成功,进行剪枝操作

class Solution:
    def findWords(self, board: List[List[str]], words: List[str]) -> List[str]:
        res = []
        tree = Trie()
        visited = [[False for j in  range(len(board[0]))] for i in range(len(board))]
        for i in range(len(words)):
            tree.insert(words[i])
        for i in range(len(board)):
            for j in range(len(board[0])):
                self.dfs(tree,board,tree.root, visited,i,j,'',res)
        return res

    def dfs(self,tree,board,curr, visited,i,j,word,res):
        if i < 0 or j < 0 or i > len(board) - 1 or j > len (board[0]) - 1 or visited[i][j]:
            return
        word += board[i][j]
        curr = curr.children.get(board[i][j])
        if not curr:
            return
        if curr.isword:
            res.append(word)
            tree.delete(word)
        visited[i][j] = True
        self.dfs(tree,board,curr,visited,i+1,j,word,res)
        self.dfs(tree,board,curr,visited,i-1,j,word,res)
        self.dfs(tree,board,curr,visited,i,j+1,word,res)
        self.dfs(tree,board,curr,visited,i,j-1,word,res)
        visited[i][j] = False

class Node(object):
    def __init__(self):
        self.children = collections.defaultdict(Node)
        self.isword = False

class Trie(object):

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.root = Node()

    def insert(self, word):
        """
        Inserts a word into the trie.
        :type word: str
        :rtype: void
        """
        current = self.root
        for w in word:
            current = current.children[w]
        current.isword = True

    def search(self, word):
        """
        Returns if the word is in the trie.
        :type word: str
        :rtype: bool
        """
        current = self.root
        for w in word:
            current = current.children.get(w)
            if current == None:
                return False
            # if w  not in current:return
            # .children[w]
        return current.isword

    def startsWith(self, prefix):
        """
        Returns if there is any word in the trie that starts with the given prefix.
        :type prefix: str
        :rtype: bool
        """
        current = self.root
        for w in prefix:
            if w not in current.children:return False
            current = current.children[w]
            current = current.children.get(w)
            if current == None:
                return False
        return True

    def delete(self, word):
        current = self.root
        stack = []
        for w in word:
            stack.append([current, w])
            current = current.children.get(w)
            if current is None:
                return False
        if not current.isword:
            return False
        if current.children:
            current.isword = False
            return True
        else:
            while stack:
                node, string = stack.pop()
                del node.children[string]
                if node.children or node.isword:
                    break

            return True

线段树

线段树在各个节点保存一条线段(数组中的一段子数组),主要用于高效解决连续区间的动态查询问题,由于二叉结构的特性,它基本能保持每个操作的复杂度为O(logn)。线段树的每个节点表示一个区间,子节点则分别表示父节点的左右半区间,例如父亲的区间是[L,R],那么(m=(L+R)/2)左儿子的区间是[L,m],右儿子的区间是[m+1,R]。

[leetcode]307.Range Sum Query - Mutable

解决方法有Segement Tree(线段树),Binary Indexed Tree(树状数组) ,和平方根分解三种办法。
以线段树为例:

#Segment tree node
class Node(object):
    def __init__(self, start, end):
        self.start = start
        self.end = end
        self.total = 0
        self.left = None
        self.right = None

class NumArray(object):
    def __init__(self, nums):
        """
        initialize your data structure here.
        :type nums: List[int]
        """
        #helper function to create the tree from input array
        def createTree(nums, l, r):

            #base case
            if l > r:
                return None

            #leaf node
            if l == r:
                n = Node(l, r)
                n.total = nums[l]
                return n

            mid = (l + r) // 2

            root = Node(l, r)

            #recursively build the Segment tree
            root.left = createTree(nums, l, mid)
            root.right = createTree(nums, mid+1, r)

            #Total stores the sum of all leaves under root
            #i.e.those elements lying between (start, end)
            root.total = root.left.total + root.right.total

            return root

        self.root = createTree(nums, 0, len(nums)-1)

    def update(self, i, val):
        """
        :type i: int
        :type val: int
        :rtype: int
        """
        #Helper function to update a value
        def updateVal(root, i, val):

            #Base case.The actual value will be updated in a leaf.
            #The total is then propogated upwards
            if root.start == root.end:
                root.total = val
                return val

            mid = (root.start + root.end) // 2

            #If the index is less than the mid, that leaf must be in the left subtree
            if i <= mid:
                updateVal(root.left, i, val)

            #Otherwise, the right subtree
            else:
                updateVal(root.right, i, val)

            #Propogate the changes after recursive call returns
            root.total = root.left.total + root.right.total

            return root.total

        return updateVal(self.root, i, val)

    def sumRange(self, i, j):
        """
        sum of elements nums[i..j], inclusive.
        :type i: int
        :type j: int
        :rtype: int
        """
        #Helper function to calculate range sum
        def rangeSum(root, i, j):

            #If the range exactly matches the root, we already have the sum
            if root.start == i and root.end == j:
                return root.total

            mid = (root.start + root.end) // 2

            #If end of the range is less than the mid, the entire interval lies
            #in the left subtree
            if j <= mid:
                return rangeSum(root.left, i, j)

            #If start of the interval is greater than mid, the entire inteval lies
            #in the right subtree
            elif i >= mid + 1:
                return rangeSum(root.right, i, j)

            #Otherwise, the interval is split.So we calculate the sum recursively,
            #by splitting the interval
            else:
                return rangeSum(root.left, i, mid) + rangeSum(root.right, mid+1, j)

        return rangeSum(self.root, i, j)

原文地址:https://www.cnblogs.com/hellojamest/p/11703168.html

时间: 2024-11-13 06:45:29

[总结]树的相关文章

HDU 6203 ping ping ping [LCA,贪心,DFS序,BIT(树状数组)]

题目链接:[http://acm.hdu.edu.cn/showproblem.php?pid=6203] 题意 :给出一棵树,如果(a,b)路径上有坏点,那么(a,b)之间不联通,给出一些不联通的点对,然后判断最少有多少个坏点. 题解 :求每个点对的LCA,然后根据LCA的深度排序.从LCA最深的点对开始,如果a或者b点已经有点被标记了,那么continue,否者标记(a,b)LCA的子树每个顶点加1. #include<Bits/stdc++.h> using namespace std;

HDU 5542 The Battle of Chibi dp+树状数组

题目:http://acm.hdu.edu.cn/showproblem.php?pid=5542 题意:给你n个数,求其中上升子序列长度为m的个数 可以考虑用dp[i][j]表示以a[i]结尾的长度为j的上升子序列有多少 裸的dp是o(n2m) 所以需要优化 我们可以发现dp的第3维是找比它小的数,那么就可以用树状数组来找 这样就可以降低复杂度 #include<iostream> #include<cstdio> #include<cstring> #include

【树4】二叉树的遍历

简介 遍历二叉树就是按照某种顺序,将树中的结点都枚举一遍,且每个结点仅仅访问一次.因为树不是线性的结构,遍历不像线性表那样简单,因此他的遍历需要特点的算法来完成. 从某种角度讲,对二叉树的遍历就是将树形结构转换为线性结构的操作. 二叉树的遍历方法主要有如下几种: 先序遍历:先访问root结点,再先序遍历左子树,再先序遍历右子树. 中序遍历:先中序遍历左子树,再访问root结点,再中序遍历右子树. 后序遍历:先后序遍历左子树,再后序遍历右子树,再访问root结点. 层遍历:从上到下,从左到右,一层

关于左偏树的一些东东

大概所有的预备知识这里都有https://baike.baidu.com/item/%E5%B7%A6%E5%81%8F%E6%A0%91/2181887?fr=aladdin 例题1:洛谷 P3377 [模板]左偏树(可并堆) 383通过 1.2K提交 题目提供者HansBug 站长团 标签 难度提高+/省选- 时空限制1s / 128MB 提交 讨论 题解 最新讨论更多讨论 加了路径压缩就WA,路过dal… 左偏树用指针写会MLE吗..… m,n写反了也可以过,数据有… 哪位大神有pbds库

ZJOI 2008 树的统计

ZJOI2008 树的统计 题目描述 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w. 我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值 III. QSUM u v: 询问从点u到点v的路径上的节点的权值和 注意:从点u到点v的路径上的节点包括u和v本身 输入输出格式 输入格式: 输入文件的第一行为一个整数n,表示节点的个数. 接下来n – 1行,每行2个整数

luoguP2590 [ZJOI2008]树的统计 [树链剖分] [TLE的LCT]

题目描述 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w. 我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值 III. QSUM u v: 询问从点u到点v的路径上的节点的权值和 注意:从点u到点v的路径上的节点包括u和v本身 输入输出格式 输入格式: 输入文件的第一行为一个整数n,表示节点的个数. 接下来n – 1行,每行2个整数a和b,表示节点a和节点b之

(POJ 3067) Japan (慢慢熟悉的树状数组)

Japan Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 29295   Accepted: 7902 Description Japan plans to welcome the ACM ICPC World Finals and a lot of roads must be built for the venue. Japan is tall island with N cities on the East coas

[poj2104]可持久化线段树入门题(主席树)

解题关键:离线求区间第k小,主席树的经典裸题: 对主席树的理解:主席树维护的是一段序列中某个数字出现的次数,所以需要预先离散化,最好使用vector的erase和unique函数,很方便:如果求整段序列的第k小,我们会想到离散化二分和线段树的做法, 而主席树只是保存了序列的前缀和,排序之后,对序列的前缀分别做线段树,具有差分的性质,因此可以求任意区间的第k小,如果主席树维护索引,只需要求出某个数字在主席树中的位置,即为sort之后v中的索引:若要求第k大,建树时反向排序即可 1 #include

【BZOJ 3551】[ONTAK2010] Peaks加强版 Kruskal重构树+树上倍增+主席树

这题真刺激...... I.关于Kruskal重构树,我只能开门了,不过补充一下那玩意还是一棵满二叉树.(看一下内容之前请先进门坐一坐) II.原来只是用树上倍增求Lca,但其实树上倍增是一种方法,Lca只是他的一种应用,他可以搞各种树上问题,树上倍增一般都会用到f数组. |||.我们跑出来dfs序就能在他的上面进行主席树了. IV.别忘了离散. V.他可能不连通,我一开始想到了,但是我觉得出题人可能会是好(S)人(B),但是...... #include <cstdio> #include

【BZOJ4942】[Noi2017]整数 线段树+DFS(卡过)

[BZOJ4942][Noi2017]整数 题目描述去uoj 题解:如果只有加法,那么直接暴力即可...(因为1的数量最多nlogn个) 先考虑加法,比较显然的做法就是将A二进制分解成log位,然后依次更新这log位,如果最高位依然有进位,那么找到最高位后面的第一个0,将中间的所有1变成0,那个0变成1.这个显然要用到线段树,但是复杂度是nlog2n的,肯定过不去. 于是我在考场上yy了一下,这log位是连续的,我们每次都要花费log的时间去修改一个岂不是很浪费?我们可以先在线段树上找到这段区间