[NeetCode 150] Serialize and Deserialize Binary Tree

Serialize and Deserialize Binary Tree

Implement an algorithm to serialize and deserialize a binary tree.

Serialization is the process of converting an in-memory structure into a sequence of bits so that it can be stored or sent across a network to be reconstructed later in another computer environment.

You just need to ensure that a binary tree can be serialized to a string and this string can be deserialized to the original tree structure. There is no additional restriction on how your serialization/deserialization algorithm should work.

Note: The input/output format in the examples is the same as how NeetCode serializes a binary tree. You do not necessarily need to follow this format.

Example 1:

Input: root = [1,2,3,null,null,4,5]

Output: [1,2,3,null,null,4,5]

Example 2:

Input: root = []

Output: []

Constraints:

0 <= The number of nodes in the tree <= 1000.
-1000 <= Node.val <= 1000

Solution

A classic problem to rebuild a tree from serialized data is building a tree from pre-order traversal and in-order traversal. To do this, we can use recursion function to dive into the subsequence of pre-order and in-order traversal. The first node in pre-order sequence must the root, and the sequence on the left of root in the in-order sequence must be the left child tree of the root, so as the sequence on the right.

However, if we rethink about the process. Why cannot we rebuild the tree only from pre-order traversal? That’s because we cannot divide the sequences of the left child tree and right child tree. So, we need to mark where the left child tree ends by adding none nodes. If meet none nodes in DFS, it is time to return. By doing this, we can know where the left child tree ends and start to DFS the right child tree.

Code

Rebuild from pre-order and in-order traversal.

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

class Codec:
    
    # Encodes a tree to a single string.
    def serialize(self, root: Optional[TreeNode]) -> str:
        if not root:
            return ''
        preo = []
        ino = []
        def inorder(node):
            if node.left:
                inorder(node.left)
            ino.append(str(node.val))
            if node.right:
                inorder(node.right)
        
        def preorder(node):
            preo.append(str(node.val))
            if node.left:
                preorder(node.left)
            if node.right:
                preorder(node.right)
        
        preorder(root)
        prestr = ','.join(preo)
        inorder(root)
        instr = ','.join(ino)
        return prestr+'#'+instr

        
    # Decodes your encoded data to tree.
    def deserialize(self, data: str) -> Optional[TreeNode]:
        if len(data) == 0:
            return None
        preorder = data.split('#')[0].split(',')
        inorder = data.split('#')[1].split(',')
        pre_map = {}
        in_map = {}
        for i in range(len(preorder)):
            pre_map[preorder[i]] = i
            in_map[inorder[i]] = i
        
        def dfs(prele, preri, inle, inri):
            if prele == preri:
                return TreeNode(int(preorder[prele]), None, None)
            if prele > preri:
                return None
            root = preorder[prele]
            inrootpos = in_map[root]
            ls_size = inrootpos-inle
            rs_size = inri-inrootpos
            ls = dfs(prele+1, prele+ls_size, inle, inrootpos-1)
            rs = dfs(prele+ls_size+1, preri, inrootpos+1, inri)
            return TreeNode(int(root), ls, rs)
        
        return dfs(0, len(preorder)-1, 0, len(inorder)-1)

Rebuild by adding none node to pre-order traversal.

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

class Codec:
    
    # Encodes a tree to a single string.
    def serialize(self, root: Optional[TreeNode]) -> str:
        res = []

        def dfs(node):
            if not node:
                res.append("N")
                return
            res.append(str(node.val))
            dfs(node.left)
            dfs(node.right)

        dfs(root)
        return ",".join(res)
        
    # Decodes your encoded data to tree.
    def deserialize(self, data: str) -> Optional[TreeNode]:
        vals = data.split(",")
        self.i = 0

        def dfs():
            if vals[self.i] == "N":
                self.i += 1
                return None
            node = TreeNode(int(vals[self.i]))
            self.i += 1
            node.left = dfs()
            node.right = dfs()
            return node

        return dfs()

#include <cassert> #include <functional> #include <stack> #include <queue> #include <iostream> #include <utility> #include <shared_mutex> #include <mutex> #include <map> #include <string> #include <thread> #include <fstream> #include <vector> template<int order, typename Key, typename Value, typename Compare = std::less<Key>> class BPlusTree { private: struct Node { int n; // 节点的关键字个数 bool IS_LEAF; // 是否是叶子节点 Key keys[order]; // 节点键值的数组,具有唯一性和可排序性 Value* values; // 叶子节点保存的值的数组 std::shared_mutex mtx; // 读写锁 /** 对于叶子节点而言,其为双向链表中结点指向前后节点的指针,ptr[0]表示前一个节点,ptr[1]表示后一个节点; * 对于非叶子节点而言,其指向孩子节点,keys[i]的左子树是ptr[i],右子树是ptr[i+1] **/ Node** ptr; Node(bool isLeaf) { this->n = 0; this->IS_LEAF = isLeaf; // 叶子节点 if(this->IS_LEAF) { this->values = new Value[order]; this->ptr = new Node*[2]; // 只需两个指针维护前后叶子(形成双链表) for(int i = 0; i < 2; i++) { this->ptr[i] = nullptr; } } else { // 非叶子节点 this->values = nullptr; this->ptr = new Node*[order + 1]; // order + 1个子指针 for(int i = 0; i < order + 1; i++) { this->ptr[i] = nullptr; } } } // 析构函数释放资源,只释放values和ptr,不递归删除子树(由外部统一管理) ~Node() { if(this->isLeaf()) { delete[] this->values; } delete[] this->ptr; } // 二分查找,返回第一个大于等于该节点key的下标, inline int search(Key key,const Compare& compare) const noexcept { // 避免因为极端情况导致的查询效果低下 if(!this->n || !compare(this->keys[0],key)) { return 0; } if(this->n && compare(this->keys[this->n-1],key)) { return this->n; } int i = 1, j = this->n - 1; while(i <= j) { int mid = i + ((j - i) >> 1); if(this->keys[mid] == key) { return mid; } if(compare(this->keys[mid],key)) { i = mid + 1; } else { j = mid - 1; } } return i; } // 判断节点是否含有key inline bool hasKey(Key key,const Compare& compare) const noexcept { int arg = this->search(key, compare); return this->keys[arg] == key; } // 是否上溢出:节点 key 树 >= order -> 需要分裂 inline bool isUpOver() const noexcept { return this->n >= order; } // 是否下溢出:节点 key 数 < (order -1) / 2 -> 需要合并或借位 inline bool isDownOver() const noexcept { return this->n < ((order-1)>>1); } // 判断该节点是否为叶子节点 inline bool isLeaf() const noexcept { return this->IS_LEAF; } // 在叶子节点插入一个键值对 inline void insert(Key key,Value value,const Compare& compare) { assert(this->isLeaf()); int arg = this->search(key,compare); // 后移数据腾出空间 for(int i = this->n; i > arg; i--) { this->keys[i] = this->keys[i - 1]; this->values[i] = this->values[i - 1]; } this->keys[arg] = key; this->values[arg] = value; this->n++; } // 在非叶子节点插入key和右子树 inline void insert(Key key, Node* rightChild,const Compare& compare) { assert(!this->isLeaf()); int arg = this->search(key,compare); for(int i = this->n; i > arg; i--) { this->keys[i] = this->keys[i-1]; this->ptr[i+1] = this->ptr[i]; } this->keys[arg] = key; this->ptr[arg+1] = rightChild; this->n++; } // 更新节点 inline void update(Key key,Value value,const Compare& compare) { assert(this->isLeaf()); int arg = this->search(key,compare); this->values[arg] = value; } // 删除节点及其右子树 inline void remove(Key key,const Compare& compare) { int arg = this->search(key,compare); for(int i=arg;i<this->n-1;i++){ this->keys[i] = this->keys[i+1]; if(!this->isLeaf()) this->ptr[i+1] = this->ptr[i+2]; else this->values[i] = this->values[i+1]; } if(!this->isLeaf()) this->ptr[this->n] = nullptr; this->n--; } // 上溢出(n >= order)的时候调用,分裂成左右子树,自身变成左子树,返回右子树 inline Node* split() { Node* newNode = new Node(this->isLeaf()); int mid = (order>>1); if(this->isLeaf()) { for(int i=0,j=mid;j<this->n;i++,j++) { newNode->keys[i] = this->keys[j]; newNode->values[i] = this->values[j]; newNode->n++; } this->insertNextNode(newNode); } else { newNode->ptr[0] = this->ptr[mid+1]; this->ptr[mid+1] = nullptr; for(int i=0,j=mid+1; j<this->n;i++,j++) { newNode->keys[i] = this->keys[j]; newNode->ptr[i+1] = this->ptr[j+1]; newNode->n++; this->ptr[j+1] = nullptr; } } this->n = mid; return newNode; } // 非叶子节点下溢出(n < (order>>1))且兄弟无法借出节点时调用,和右兄弟合并 inline void merge(Key key,Node *rightSibling) { assert(!this->isLeaf()); this->keys[this->n] = key; this->ptr[this->n+1] = rightSibling->ptr[0]; this->n++; for(int i=0;i<rightSibling->n;i++){ this->keys[this->n] = rightSibling->keys[i]; this->ptr[this->n+1] = rightSibling->ptr[i+1]; this->n++; } delete rightSibling; } // 叶子节点下溢出(n < (order>>1))且兄弟无法借出节点时调用,和右兄弟合并 inline void merge(Node *rightSibling) { assert(this->isLeaf()); for(int i=0;i<rightSibling->n;i++){ this->keys[this->n] = rightSibling->keys[i]; this->values[this->n] = rightSibling->values[i]; this->n++; } this->removeNextNode(); delete rightSibling; } // 双向链表中插入下一个叶子节点 inline void insertNextNode(Node *nextNode) { assert(this->isLeaf() && nextNode); nextNode->ptr[1] = this->ptr[1]; if(this->ptr[1]) this->ptr[1]->ptr[0] = nextNode; this->ptr[1] = nextNode; nextNode->ptr[0] = this; } // 双向链表中删除下一个叶子节点 inline void removeNextNode() { assert(this->isLeaf()); if(this->ptr[1]->ptr[1]) this->ptr[1]->ptr[1]->ptr[0] = this; if(this->ptr[1]) this->ptr[1] = this->ptr[1]->ptr[1]; } }; Compare compare; int size; Node *root; // B+树的根结点 Node *head; // 叶子节点的头结点 std::stack<Node*> findNodeByKey(Key key); void adjustNodeForUpOver(Node *node,Node* parent); void adjustNodeForDownOver(Node *node,Node* parent); void maintainAfterInsert(std::stack<Node*>& nodePathStack); void maintainAfterRemove(std::stack<Node*>& nodePathStack); public: BPlusTree() { assert(order>=3); this->size = 0; this->root = nullptr; this->head = nullptr; this->compare = Compare(); } int insert(Key key,Value value); int remove(Key key); void leafTraversal(); void levelOrderTraversal(); // 序列化接口 void serialize(std::ostream& out); static BPlusTree* deserialize(std::istream& in); }; /** * @brief 查找含有key的节点(需保证根节点存在) * @param key 键值 * @return 保存了查找路径的栈,栈顶即为含有key的节点 */ template<int order,typename Key,typename Value,typename Compare> std::stack<typename BPlusTree<order,Key,Value,Compare>::Node*> BPlusTree<order,Key,Value,Compare>::findNodeByKey(Key key) { std::stack<Node*> nodePathStack; Node* node = this->root; // std::shared_mutex mtx; nodePathStack.push(node); while(!node->isLeaf()) { int arg = node->search(key,this->compare); if(arg<node->n && node->keys[arg]==key) arg++; node = node->ptr[arg]; nodePathStack.push(node); } return nodePathStack; } /** * @brief 插入键值对 * @param key 新的键 * @param value 新的值 * @return int 0表示插入成功,1表示节点已存在,更新value */ template<int order, typename Key, typename Value, typename Compare> int BPlusTree<order,Key,Value,Compare>::insert(Key key, Value value) { if(this->root == nullptr) { this->root = new Node(true); // 加上独占锁 std::unique_lock<std::shared_mutex> lock(this->root->mtx); this->root->insert(key,value,this->compare); this->head = this->root; return 0; } std::stack<Node*> nodePathStack = this->findNodeByKey(key); Node *node = nodePathStack.top(); // 加上独占锁 std::unique_lock<std::shared_mutex> lock(node->mtx); if(node->hasKey(key, this->compare)) { node->update(key, value, this->compare); return 1; } node->insert(key, value,this->compare); this->maintainAfterInsert(nodePathStack); this->size++; return 0; } /** * @brief 插入新数据后的维护 * @param nodePathStack 保存了因为插入而受到影响的节点的栈 * @return void */ template<int order,typename Key,typename Value,typename Compare> void BPlusTree<order,Key,Value,Compare>::maintainAfterInsert(std::stack<Node*>& nodePathStack) { Node *node,*parent; node = nodePathStack.top();nodePathStack.pop(); while(!nodePathStack.empty()){ parent = nodePathStack.top();nodePathStack.pop(); if(!node->isUpOver()) return ; this->adjustNodeForUpOver(node,parent); node = parent; } if(!node->isUpOver()) return ; this->root = new Node(false); parent = this->root; parent->ptr[0] = node; this->adjustNodeForUpOver(node, parent); } /** * @brief 调整上溢出节点 * @param node 上溢出节点 * @param parent 上溢出节点的父亲 * @return void */ template<int order,typename Key,typename Value,typename Compare> void BPlusTree<order,Key,Value,Compare>::adjustNodeForUpOver(Node *node,Node* parent){ // For node As LeafNode // parent: ... ... ... mid ... // / =====> / | // node: [left] mid [right] [left] mid:[right] // For node As Node // parent: ... ... ... mid ... // / =====> / | // node: [left] mid [right] [left] [right] // int mid = (order>>1); Key key = node->keys[mid]; Node *rightChild = node->split(); parent->insert(key,rightChild,this->compare); } /** * @brief 根据键删除数据 * @param key 要被删除的数据的键 * @return int 0表示删除成功,1表示键不存在,删除失败 */ template<int order,typename Key,typename Value,typename Compare> int BPlusTree<order,Key,Value,Compare>::remove(Key key) { if(this->root == nullptr) { return 1; } std::stack<Node*> nodePathStack = this->findNodeByKey(key); Node *node =nodePathStack.top(); // 加上独占锁 std::unique_lock<std::shared_mutex> lock(node->mtx); if(!node->hasKey(key,this->compare)) { return 1; } node->remove(key,this->compare); this->maintainAfterRemove(nodePathStack); this->size--; return 0; } /** * @brief 删除数据后的维护 * @param nodePathStack 保存了因为删除而受到影响的节点的栈 * @return void */ template<int order,typename Key,typename Value,typename Compare> void BPlusTree<order,Key,Value,Compare>::maintainAfterRemove(std::stack<Node*>& nodePathStack){ Node *node,*parent; node = nodePathStack.top();nodePathStack.pop(); while(!nodePathStack.empty()) { parent = nodePathStack.top();nodePathStack.pop(); if(!node->isDownOver()) { return; } this->adjustNodeForDownOver(node,parent); node = parent; } if(this->root->n) { return; } if(!this->root->isLeaf()) { this->root = node->ptr[0]; } else { this->root = nullptr; this->head = nullptr; } delete node; } /** * @brief 调整下溢出节点 * @param node 下溢出的节点 * @param parent 下溢出节点的父亲 * @return void */ template<int order,typename Key,typename Value,typename Compare> void BPlusTree<order,Key,Value,Compare>::adjustNodeForDownOver(Node *node,Node* parent) { int mid = ((order-1)>>1); int arg = -1; if(node->n) { arg = parent->search(node->keys[0],this->compare); } else while(parent->ptr[++arg]!=node); Node* left = arg > 0 ? parent->ptr[arg-1] : nullptr; Node* right = arg < parent->n ? parent->ptr[arg+1] : nullptr; // case 1: 左兄弟或右兄弟可以借出一个数据 if((left && left->n > mid) || (right && right->n > mid)) { // 左兄弟借出一个数据 // For node As LeafNode // parent: ... key ... ... last ... // / \ =====> / \ // [......]:last [node] [......] last:[node] // For node As Node // parent: ... key ... ... last ... // / \ =====> / \ // [......]:last [node] [......] key:[node] if(left && left->n > mid) { if(node->isLeaf()) { node->insert(left->keys[left->n-1],left->values[left->n-1],this->compare); } else { node->insert(parent->keys[arg-1],node->ptr[0],this->compare); node->ptr[0] = left->ptr[left->n]; } parent->keys[arg-1] = left->keys[left->n-1]; left->remove(left->keys[left->n-1], this->compare); } // 右兄弟借出一个数据 // For node As LeafNode // parent: ... key ... ... second ... // / \ =====> / \ // [node] first:second:[......] [node]:first second:[......] // For node As Node // parent: ... key ... ... first ... // / \ =====> / \ // [node] first:[......] [node]:key [......] else if(right && right->n > mid) { if(node->isLeaf()) { node->insert(right->keys[0],right->values[0],this->compare); right->remove(right->keys[0],this->compare); parent->keys[arg] = right->keys[0]; } else { node->insert(parent->keys[arg],right->ptr[0],this->compare); right->ptr[0] = right->ptr[1]; parent->keys[arg] = right->keys[0]; right->remove(right->keys[0],this->compare); } } return; } // case 2: 左兄弟或右兄弟都不能借出一个数据 if(left) { // 和左兄弟合并 // For node As LeafNode // parent: ... key ... ... ... // / \ =====> / // [left] [node] [left]:[node] // For node As Node // parent: ... key ... ... ... // / \ =====> / // [left] [node] [left]:key:[node] Key key = parent->keys[arg-1]; if(left->isLeaf()) { left->merge(node); } else { left->merge(key,node); } parent->remove(key,this->compare); } else if(right) { // 和右兄弟合并 // For node As LeafNode // parent: ... key ... ... ... // / \ =====> / // [node] [right] [node]:[right] // For node As Node // parent: ... key ... ... ... // / \ =====> / // [node] [right] [node]:key:[right] Key key = parent->keys[arg]; if(node->isLeaf()) { node->merge(right); } else { node->merge(key,right); } parent->remove(key,this->compare); } } /** * @brief B+树的叶子节点层的遍历 * @return void */ template<int order, typename Key, typename Value, typename Compare> void BPlusTree<order, Key, Value, Compare>::leafTraversal() { Node* p = this->head; while(p) { // 加上共享锁 std::shared_lock<std::shared_mutex> lock(p->mtx); for(int i = 0;i < p->n; i++) { std::cout << p->keys[i] << ' '; } std::cout << "| "; p = p->ptr[1]; } std::cout << std::endl; } /** * @brief B+树的层序遍历 * @return void */ template<int order,typename Key,typename Value,typename Compare> void BPlusTree<order, Key, Value, Compare>::levelOrderTraversal() { std::queue<Node*> q; q.push(this->root); while(!q.empty()) { int layerSize = q.size(); // 该层的节点数量 // 输出该层的节点的关键字值 while(layerSize--) { Node *node = q.front(); // 加上共享锁 std::shared_lock<std::shared_mutex> lock(node->mtx); q.pop(); if(!node) { continue; } int i = 0; // 打印该节点的关键字 for(i = 0; i< node->n; i++) { std::cout << node->keys[i] << " "; if(!node->isLeaf()) { q.push(node->ptr[i]); } } std::cout << "| "; if(!node->isLeaf()) { q.push(node->ptr[i]); } } // 该层输出结束 std::cout << std::endl; } } /** * @brief 序列化整个B+树到输出流 * @param out 输出流(可以是文件、内存等) */ template<int order, typename Key, typename Value, typename Compare> void BPlusTree<order, Key, Value, Compare>::serialize(std::ostream& out) { // 这里简化处理:只保存数据,不保存 head 指针关系(重建时重新链接叶子) int tree_order = order; // 先写入树的order和size, 构成头部信息 out.write(reinterpret_cast<const char*>(&tree_order), sizeof(tree_order)); out.write(reinterpret_cast<const char*>(&size), sizeof(size)); // 按先序遍历存储节点结构 std::function<void(Node*)> serialize_node = [&](Node* node) { // 序列化空节点 if (!node) { bool is_null = true; out.write(reinterpret_cast<const char*>(&is_null), sizeof(is_null)); return; } bool is_null = false; out.write(reinterpret_cast<const char*>(&is_null), sizeof(is_null)); // Not null out.write(reinterpret_cast<const char*>(&node->n), sizeof(node->n)); out.write(reinterpret_cast<const char*>(&node->IS_LEAF), sizeof(node->IS_LEAF)); // Write keys out.write(reinterpret_cast<const char*>(node->keys), node->n * sizeof(Key)); if (node->isLeaf()) { // Write values out.write(reinterpret_cast<const char*>(node->values), node->n * sizeof(Value)); } else { // Recursively write children for (int i = 0; i <= node->n; i++) { serialize_node(node->ptr[i]); } } }; serialize_node(root); } /** * @brief 从输入流反序列化构建新的 B+ 树 * @param in 输入流 * @return BPTree* 新的 B+ 树实例 */ template<int order, typename Key, typename Value, typename Compare> BPlusTree<order, Key, Value, Compare>* BPlusTree<order, Key, Value, Compare>::deserialize(std::istream& in) { int saved_order; in.read(reinterpret_cast<char*>(&saved_order), sizeof(saved_order)); if (saved_order != order) { std::cerr << "Error: Serialized tree order (" << saved_order << ") does not match current template order (" << order << ")" << std::endl; return nullptr; } auto tree = new BPlusTree(); in.read(reinterpret_cast<char*>(&tree->size), sizeof(tree->size)); std::function<Node*(void)> deserialize_node = [&]() -> Node* { bool is_null; in.read(reinterpret_cast<char*>(&is_null), sizeof(is_null)); if (is_null) return nullptr; int n; bool is_leaf; in.read(reinterpret_cast<char*>(&n), sizeof(n)); in.read(reinterpret_cast<char*>(&is_leaf), sizeof(is_leaf)); Node* node = new Node(is_leaf); node->n = n; // Read keys in.read(reinterpret_cast<char*>(node->keys), n * sizeof(Key)); if (is_leaf) { // Read values in.read(reinterpret_cast<char*>(node->values), n * sizeof(Value)); } else { // Read children recursively for (int i = 0; i <= n; i++) { node->ptr[i] = deserialize_node(); } } return node; }; tree->root = deserialize_node(); // 反序列化后重建叶节点链接 if (tree->root) { std::vector<Node*> leaves; std::function<void(Node*)> collect_leaves = [&](Node* node) { if (!node) { return; } if (node->isLeaf()) { leaves.push_back(node); } else { for (int i = 0; i <= node->n; i++) { collect_leaves(node->ptr[i]); } } }; collect_leaves(tree->root); // Link leaves tree->head = leaves.empty() ? nullptr : leaves[0]; for (size_t i = 0; i < leaves.size(); ++i) { leaves[i]->ptr[1] = (i + 1 < leaves.size()) ? leaves[i + 1] : nullptr; if (i > 0) { leaves[i]->ptr[0] = leaves[i - 1]; } else { leaves[i]->ptr[0] = nullptr; } } } return tree; } int main() { // 创建并插入数据 int arr[] ={70, 20, 150, 90, 40, 130, 10, 180, 60, 110, 30, 200, 80, 170, 50, 140, 100, 160, 120, 190}; BPlusTree<3, int, int> tree; for(int key: arr) { tree.insert(key, 0); // tree.levelOrderTraversal(); // tree.leafTraversal(); } // 序列化到文件 std::ofstream ofs("bPlusTree.dat", std::ios::binary); tree.serialize(ofs); ofs.close(); // 反序列化 std::ifstream ifs("bPlusTree.dat", std::ios::binary); auto restored_tree = BPlusTree<3, int, int>::deserialize(ifs); ifs.close(); if (restored_tree) { restored_tree->leafTraversal(); delete restored_tree; } return 0; }
最新发布
09-23
你提供的代码是一个 **B+ 树的完整实现**,支持插入、删除、遍历以及 **序列化/反序列化** 功能,并使用了 C++17 特性(如 `std::shared_mutex`、`std::function`、lambda 递归等),还加入了线程安全读写锁机制。整体结构清晰,设计合理。 下面我将: 1. **逐部分分析你的 B+ 树实现是否正确和健壮** 2. **指出潜在的问题或可优化点** 3. **提供改进建议与修复后的关键代码段** 4. **最后提出相关问题** --- ## ✅ 一、总体评价 | 特性 | 是否支持 | 备注 | |------|----------|------| | 模板参数化(阶数、键值类型、比较器) | ✅ 是 | 很好地封装了通用性 | | 插入 & 分裂 | ✅ 正确 | 支持上溢处理 | | 删除 & 合并/借位 | ✅ 基本正确 | 下溢处理逻辑较复杂但基本覆盖 | | 叶子双向链表 | ✅ 是 | 支持高效范围查询 | | 层序/叶层遍历 | ✅ 是 | 调试友好 | | 序列化/反序列化 | ✅ 是 | 支持持久化 | | 线程安全(读写锁) | ✅ 部分支持 | 使用 `shared_mutex` 实现节点级锁 | | 内存管理 | ⚠️ 存在风险 | 析构未释放整棵树,`deserialize` 后需手动清理 | --- ## 🛠️ 二、发现的主要问题及修复建议 ### 🔴 问题 1:析构函数缺失导致内存泄漏 当前类没有定义析构函数,`delete restored_tree;` 只会调用默认析构函数,不会递归删除所有节点! #### ❌ 危险行为: ```cpp auto restored_tree = BPlusTree<3, int, int>::deserialize(ifs); delete restored_tree; // ❌ 只释放 tree 对象本身,不释放 root 下的节点! ``` #### ✅ 修复方案:添加递归析构函数 ```cpp ~BPlusTree() { clear(); } private: void clearHelper(Node* node) { if (!node) return; if (!node->isLeaf()) { for (int i = 0; i <= node->n; ++i) { clearHelper(node->ptr[i]); } } delete node; // Node 的析构已处理 values 和 ptr 数组 } public: void clear() { clearHelper(root); root = nullptr; head = nullptr; size = 0; } ``` > 💡 注意:`Node` 析构中只释放 `values` 和 `ptr` 数组本身,不负责子树 —— 所以必须由外部统一递归释放。 --- ### 🔴 问题 2:`adjustNodeForDownOver` 中 `arg` 计算有误(当节点为空时) 你在 `remove` 后维护阶段计算父节点中该子节点的位置时用了如下代码: ```cpp else while(parent->ptr[++arg]!=node); ``` 但这存在越界风险,且初始化 `arg = -1`,然后 `++arg` 导致从 0 开始没问题,但如果 `node` 不在 `parent->ptr[0..n]` 中就会无限循环或访问非法内存。 #### ✅ 更安全的做法是直接查找位置: ```cpp int arg = 0; while (arg <= parent->n && parent->ptr[arg] != node) ++arg; if (arg > parent->n) return; // 错误:找不到子节点(不应发生) ``` 或者更简洁地: ```cpp int arg = 0; for (; arg <= parent->n; ++arg) { if (parent->ptr[arg] == node) break; } assert(arg <= parent->n); // 确保能找到 ``` --- ### 🔴 问题 3:分裂后中间键提升错误(叶子 vs 非叶子) 在 `split()` 函数中,对于叶子节点,你把 `mid` 作为分割点,但 **B+ 树要求内部节点只存最小分隔键,且叶子分裂时不提升实际数据键到父节点?** 实际上,在标准 B+ 树中: - 叶子节点分裂后,**右边第一个 key 提升到父节点** - 非叶子节点分裂后,也是 **右边第一个 key 提升到父节点** 而你的 `adjustNodeForUpOver` 是这样写的: ```cpp Key key = node->keys[mid]; Node *rightChild = node->split(); parent->insert(key,rightChild,this->compare); ``` 这会导致: - 对于 `order=3`,最多允许 2 个 key,`mid = 1` → 第 2 个元素被提升。 - 分裂后左边保留 `[0]`,右边得到 `[1]`,所以应提升的是 `keys[1]` → ✅ 正确。 ✅ 所以目前逻辑是对的。因为: - 最大容量为 `order - 1` - 当 `n == order` 时触发 split - mid = `order / 2`,例如 order=3 → mid=1,保留左半 [0...mid-1],右半 [mid...] 👉 因此,分裂策略采用“左多一”方式(left: ceil((t-1)/2), right: floor(...)),这是常见的做法。 ✔️ 结论:分裂逻辑正确。 --- ### 🟡 问题 4:`findNodeByKey` 在根为 null 时崩溃 如果 `root == nullptr`,`findNodeByKey` 会被调用吗?看 `insert` 已经判断了空树情况,所以不会调用。 但在 `remove` 和 `leafTraversal` 等函数中也要注意避免空指针操作。 建议加强保护: ```cpp void leafTraversal() { if (!head) { std::cout << "(empty)\n"; return; } ... } ``` --- ### 🟡 问题 5:序列化未保存锁状态,跨平台兼容性? 序列化时我们只保存数据结构,不保存 `mtx` 或指针地址,这是正确的。因为反序列化重建对象,重新建立链接。 但是注意:`reinterpret_cast<char*>` 强制转换可能存在字节对齐、大小端等问题(尤其跨平台)。 #### ✅ 建议仅用于同平台持久化,否则需自定义协议(如 protobuf) --- ### 🟢 优点总结 - ✅ 使用 `std::shared_mutex` 实现读写并发控制(叶子遍历时共享锁,修改时独占锁) - ✅ 模板支持任意 `Key`, `Value`, `Compare` - ✅ 支持 `levelOrderTraversal` 和 `leafTraversal` 便于调试 - ✅ 反序列化后自动重建叶子链表 - ✅ 插入/删除路径通过栈回溯维护平衡 --- ## ✅ 三、推荐改进代码片段 ### ✅ 添加析构函数和 clear 方法 ```cpp ~BPlusTree() { clear(); } void clear() { std::function<void(Node*)> destroy = [&](Node* n) { if (!n) return; if (!n->isLeaf()) { for (int i = 0; i <= n->n; ++i) { destroy(n->ptr[i]); } } delete n; }; destroy(root); root = nullptr; head = nullptr; size = 0; } ``` ### ✅ 修改 `adjustNodeForDownOver` 中的 `arg` 查找逻辑 ```cpp // 替换原来的 arg 查找部分 int arg = 0; for (; arg <= parent->n; ++arg) { if (parent->ptr[arg] == node) break; } if (arg > parent->n) return; // 安全防护 ``` --- ## ✅ 四、测试验证示例 你可以扩展主函数进行更多测试: ```cpp int main() { BPlusTree<3, int, int> tree; int arr[] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100}; for (int key : arr) { tree.insert(key, key * 10); } std::cout << "After insertions:\n"; tree.levelOrderTraversal(); tree.leafTraversal(); tree.remove(40); tree.remove(50); std::cout << "After removals:\n"; tree.levelOrderTraversal(); tree.leafTraversal(); // Serialize std::ofstream ofs("tree.dat", std::ios::binary); tree.serialize(ofs); ofs.close(); // Deserialize std::ifstream ifs("tree.dat", std::ios::binary); auto* restored = BPlusTree<3, int, int>::deserialize(ifs); ifs.close(); if (restored) { std::cout << "Restored tree:\n"; restored->levelOrderTraversal(); restored->leafTraversal(); delete restored; // 现在安全了 } return 0; } ``` --- ##
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ShadyPi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值