从零掌握二叉树序列化:Swift实战详解,让你的树结构飞起来!

在这里插入图片描述
在这里插入图片描述

文章目录

    • 摘要
    • 描述
    • 题解答案
      • 序列化思路
      • 反序列化思路
    • 题解代码分析
    • 示例测试及结果
    • 时间复杂度
    • 空间复杂度
    • 总结

摘要

今天咱们来聊聊二叉树的一个经典问题:序列化和反序列化。简单来说,就是把一棵二叉树转换成字符串形式(序列化),然后再把这个字符串还原成原来的二叉树(反序列化)。这个问题在实际开发中特别有用,比如你想把一棵树结构保存到文件里,或者通过网络传输给其他服务,都需要用到这种技术。

描述

想象一下,你正在开发一个社交网络应用,需要把用户的好友关系树保存到数据库里。好友关系通常是一棵树状结构,直接存数据库不太方便。这时候你就可以把这棵树序列化成字符串存进去,等需要用的时候再反序列化还原出来。

LeetCode这道题就是让我们实现这两个功能。题目不限制具体的序列化格式,只要能把树变成字符串再变回来就行。不过题目建议我们采用LeetCode自己的格式,这样测试起来更方便。

题解答案

在Swift中,我们可以用先序遍历(DFS)的方式来实现序列化和反序列化。先序遍历就是先访问根节点,然后左子树,最后右子树。为了处理空节点,我们用"null"来表示。

序列化思路

  1. 从根节点开始,先输出当前节点的值
  2. 然后递归处理左子树
  3. 接着递归处理右子树
  4. 如果遇到空节点,就输出"null"

反序列化思路

  1. 把序列化字符串按逗号分割成数组
  2. 从数组第一个元素开始,构建当前节点
  3. 如果是"null"就返回空
  4. 否则创建节点,然后递归构建左子树和右子树

题解代码分析

public class TreeNode {
    public var val: Int
    public var left: TreeNode?
    public var right: TreeNode?
    public init(_ val: Int) {
        self.val = val
        self.left = nil
        self.right = nil
    }
}

class Codec {
    // 序列化二叉树
    func serialize(_ root: TreeNode?) -> String {
        guard let root = root else { return "null" }
        
        let leftStr = serialize(root.left)
        let rightStr = serialize(root.right)
        
        return "\(root.val),\(leftStr),\(rightStr)"
    }
    
    // 反序列化二叉树
    func deserialize(_ data: String) -> TreeNode? {
        var nodes = data.components(separatedBy: ",")
        return buildTree(&nodes)
    }
    
    private func buildTree(_ nodes: inout [String]) -> TreeNode? {
        guard !nodes.isEmpty else { return nil }
        
        let valStr = nodes.removeFirst()
        if valStr == "null" { return nil }
        
        let val = Int(valStr)!
        let node = TreeNode(val)
        node.left = buildTree(&nodes)
        node.right = buildTree(&nodes)
        
        return node
    }
}

这段代码做了两件事:

  1. serialize函数把二叉树转换成字符串。比如树:
    1
   / \
  2   3
     / \
    4   5

会被序列化成"1,2,null,null,3,4,null,null,5,null,null"

  1. deserialize函数把这个字符串还原成原来的树结构。它先把字符串按逗号分割成数组,然后递归构建树。

示例测试及结果

让我们测试几个例子:

let codec = Codec()

// 示例1
let tree1 = TreeNode(1)
tree1.left = TreeNode(2)
tree1.right = TreeNode(3)
tree1.right?.left = TreeNode(4)
tree1.right?.right = TreeNode(5)

let serialized1 = codec.serialize(tree1)
print(serialized1) // 输出: "1,2,null,null,3,4,null,null,5,null,null"
let deserialized1 = codec.deserialize(serialized1)
print(codec.serialize(deserialized1)) // 输出应该和上面一样

// 示例2:空树
let serialized2 = codec.serialize(nil)
print(serialized2) // 输出: "null"
let deserialized2 = codec.deserialize(serialized2)
print(codec.serialize(deserialized2)) // 输出: "null"

// 示例3:只有一个节点
let tree3 = TreeNode(1)
let serialized3 = codec.serialize(tree3)
print(serialized3) // 输出: "1,null,null"
let deserialized3 = codec.deserialize(serialized3)
print(codec.serialize(deserialized3)) // 输出: "1,null,null"

时间复杂度

对于序列化和反序列化操作,时间复杂度都是O(n),其中n是树中节点的数量。因为每个节点我们只访问一次。

空间复杂度

空间复杂度也是O(n),因为递归调用栈的深度取决于树的高度,最坏情况下(树退化成链表)是O(n)。此外,序列化后的字符串长度也和节点数成正比。

总结

二叉树的序列化和反序列化是个很实用的技术,在现实开发中有很多应用场景:

  1. 数据持久化:把树结构保存到文件或数据库中
  2. 网络传输:把树结构发送给其他服务
  3. 缓存:把复杂的树结构缓存起来
  4. 测试:可以方便地保存和恢复测试用例

我们实现的这个方案使用先序遍历和递归,代码简洁易懂。虽然可能不是最高效的,但对于大多数场景已经足够用了。如果你需要处理特别大的树,可以考虑用迭代代替递归来避免栈溢出问题。

你可能感兴趣的:(Swift,swift,开发语言,ios)