前端就该用 JS 刷算法9

256 阅读3分钟

每日一题 -- 树

序列化与反序列化

序列化与反序列化

思考:

  • 这道题其实就是让你了解一下,为啥我们做树题的时候,明明在做树题(或者链表)这些题目的时候,控制台的例子都是数组,而不是一个可视化的树结构的数据,之前我一直很难理解,直到了解到序列化和反序列化之后。
  • 个人理解这是为了兼容不同语言内置数据结构的不同而做出来的优化策略,比方说 JS 就没有树这种结构,所以我们在做树的时候,需要自己构建一个类,然后用我们常用的数据结构转成树,然后再进行运算,而这个过程,其实就是树的反序列化。而数组,字符串这些作为基本数据结构,几乎在常用语言中都会内置,所以就成了树这些结构序列化结构的优先选择。
  • 其实最开始我是不知道这叫做序列化,但是我们肯定有尝试在本地中进行测试,而如果入参树结构是一个脑壳痛的问题,所以一般都是写好 utils 方法,通过数组转成树,然后再作为实参传进去。如下:
// utils/makeTreeByArr
/**
 * @description 根据数组创建一课二叉树
 */

// 根据一个值获取一个节点
class TreeNode {
    constructor(val) {
        this.val = val
        this.left = null
        this.right = null
    }
}

// 根据数组构建一课树
class Tree {
    constructor(arr) {
        let nodeList = []
        let length = arr.length
        let root
        for (let i = 0; i < length; i++) {
            const node = new TreeNode(arr[i])
            nodeList.push(node)
            // 根据 node List 的前后节点串起来
            if (i > 0) {
                let parentIndex = Math.floor((i - 1) / 2)
                const parent = nodeList[parentIndex]
                if (parent.left) {
                    parent.right = node
                } else {
                    parent.left = node
                }
            }
        }
        root = nodeList.shift()
        nodeList.length = 0
        return root
    }
}

module.exports = {
    Tree,
    TreeNode
}

// 用的时候引出来,然后再做测试,下面是一个前序遍历
const { Tree } = require('./util/Tree')
// ...省略
const root = new Tree([1, null, 2, 3])
const result = preTravel(root)
console.log(result)
  • 同时序列化和反序列化是一组函数,也就是我用数组转成树,我得有一个方法,将树重新转回数组才行。
  • 这就是我在序列化和反序列学习中的一点点感悟

正文:

  • 这道题参考学习了 leetcode-solution-leetcode-pp.gitbook.io/leetcode-so…
  • 题目给的例子都是返回数组字符串,这很直观,但是 JS 中数组其实不属于基本数据类型,所以如果用数组存储,然后还得用 JSON.stringify 来转,反序列化的时候还得 parse 一下,感觉不太好,所以还得沿用字符串这种基本数据类型比较 nice
  • 这里用到了完全二叉树中,根节点与左右节点下标的关系,只是使用的时候是直接将 i 进行累加,不喜欢的可以按照 2n+1 的方式去写,只需要循环的终止条件要注意一下。
// https://leetcode-cn.com/problems/xu-lie-hua-er-cha-shu-lcof/
// 参考 https://leetcode-solution-leetcode-pp.gitbook.io/leetcode-solution/thinkings/tree#er-cha-sou-suo-shu

/**
 * 序列化 -- 将树序列化成一个字符串
 * @param {TreeNode} root 树节点
 * @return {string}
 * 
 * @分析
 * 1. 直接层序遍历,空节点用 null 表示
 */
var serialize = function (root) {
    if (!root) return ''
    let res = ''
    const queue = []
    queue.push(root)
    while (queue.length) {
        let len = queue.length
        const temp = ''
        const flag = false
        while (len--) {
            const root = queue.shift()
            if (!root) {
                temp += 'null,'
            } else {
                flag = true
                temp += root.val + ','
                queue.push(root.left)
                queue.push(root.right)
            }

        }
        if(flag) res+=temp // 如果这一层全是 null 节点,则不放到 res 中去了
    }
    return res.slice(0, -1)
};

/**
 * 反序列化 -- 将字符串反序列化成树
 * @param {string} data 序列化的数组字符串
 * @return {TreeNode} 返回树的根节点
 */
var deserialize = function (data) {
    if (!data) return null //空
    const nodes = data.split(',')
    const root = new TreeNode(nodes[0]) // 这个是根节点
    const queue = []
    queue.push(root)
    let i = 0 // 下标
    while (queue.length && i < nodes.length - 2) {
        const root = queue.shift()
        lv= nodes[i+1]
        rv= nodes[i+2]
        i+=2 // 其实就是 l=2n+1,r=2n+2
        if(lv!=='null'){
            const ln = new TreeNode(lv)
            root.left = ln
            queue.push(ln)
        }
        if(rv!=='null'){
            const rn = new TreeNode(rv)
            root.right = rn
            queue.push(rn)
        }
    }
    return root
};