二叉樹序列化和反序列化
二叉樹的序列化和反序列化
1. 二叉樹序列化:
(a) 如果當前的樹為空,則表示成X
(b) 如果當前的樹不為空,則表示成(<left_sub_tree>)cur_num(<right_sub_tree>),其中,
<left_sub_tree>: 是左子樹序列化後的結果
<right_sub_tree>: 是右子樹序列化後的結果
cur_num: 當前節點的值
中序遍歷可以得到序列化的結果。
2. 二叉樹的反序列化
根據二叉樹序列化,我們可以推匯出這樣的巴科斯正規化:
T -> (T) num (T) | x
用T代表一棵樹序列化的結果,| 表示T構成的為(T) num (T) 或者X,| 左邊是對T的遞迴定義,右邊規定了遞迴終止的邊界條件。
T 的定義中,序列中的第一個字元要麼是 X,要麼是 (,所以這個定義是不含左遞迴的,當我們開始解析一個字串的時候,如果開頭是 X,我們就知道這一定是解析一個「空樹」的結構,如果開頭是 (,我們就知道需要解析 (T) num (T) 的結構,因此這裡兩種開頭和兩種解析方法一一對應,可以確定這是一個無二義性的文法
我們只需要通過開頭的第一個字母是 X 還是 ( 來判斷使用哪一種解析方法,所以這個文法是 LL(1) 型文法,如果你不知道什麼是 LL(1) 型文法也沒有關係,你只需要知道它定義了一種遞迴的方法來反序列化,也保證了這個方法的正確性——我們可以設計一個遞迴函式:
這個遞迴函式傳入兩個引數,帶解析的字串和當前當解析的位置 ptr,ptr 之前的位置是已經解析的,ptr 和 ptr 後面的字串是待解析的。如果當前位置為 X 說明解析到了一棵空樹,直接返回。否則當前位置一定是 (,對括號內部按照 (T) num (T) 的模式解析
class Codec {
public:
string serialize(TreeNode* root) {
if (!root) {
return "X";
}
auto left = "(" + serialize(root->left) + ")";
auto right = "(" + serialize(root->right) + ")";
return left + to_string(root->val) + right;
}
inline TreeNode* parseSubtree(const string &data, int &ptr) {
++ptr; // 跳過左括號
auto subtree = parse(data, ptr);
++ptr; // 跳過右括號
return subtree;
}
inline int parseInt(const string &data, int &ptr) {
int x = 0, sgn = 1;
if (!isdigit(data[ptr])) {
sgn = -1;
++ptr;
}
while (isdigit(data[ptr])) {
x = x * 10 + data[ptr++] - '0';
}
return x * sgn;
}
TreeNode* parse(const string &data, int &ptr) {
if (data[ptr] == 'X') {
++ptr;
return nullptr;
}
auto cur = new TreeNode(0);
cur->left = parseSubtree(data, ptr);
cur->val = parseInt(data, ptr);
cur->right = parseSubtree(data, ptr);
return cur;
}
TreeNode* deserialize(string data) {
int ptr = 0;
return parse(data, ptr);
}
};
3.複雜度分析
時間複雜度:序列化時做了一次遍歷,漸進時間複雜度為 O(n)O(n)。反序列化時,在解析字串的時候 ptr 指標對字串做了一次順序遍歷,字串長度為 O(n)O(n),故這裡的漸進時間複雜度為 O(n)O(n)。
空間複雜度:考慮遞迴使用的棧空間的大小,這裡棧空間的使用和遞迴深度有關,遞迴深度又和二叉樹的深度有關,在最差情況下,二叉樹退化成一條鏈,故這裡的漸進空間複雜度為 O(n)O(n)。
摘自:https://leetcode-cn.com/problems/serialize-and-deserialize-binary-tree/solution/er-cha-shu-de-xu-lie-hua-yu-fan-xu-lie-hua-by-le-2