297. Serialize and Deserialize Binary Tree

本文介绍了一种将二叉树转化为字符串的方法及其反过程,以便于在网络中传输与重构。提供了两种实现方案:一是利用队列进行层次遍历;二是采用递归的先序遍历。这两种方法均能有效处理包括负数在内的节点数据。

对二叉树转化方便使树在网络中的传输与重构。所以可以用不同的二叉树表达方式与遍历方式。


1、序列化

使用队列

2、反序列

使用队列

可能出现结点数据是负数,需要处理好前缀符号。

方法1:层次遍历

struct TreeNode{
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x): val(x),left(NULL),right(NULL){}
};

const int INF=-1000000;

vector<int> split(string s,char c)
{
    vector<int> vt;
    int ans=0;
    int i;
    int flag=1;
    for(i=0;s[i]!='\0';i++)
    {
        if(isdigit(s[i]))
            ans=ans*10+s[i]-'0';
        else if(s[i]=='-')
            flag=-1;
        else if(s[i]==c||s[i]==']')
        {
            if(!isdigit(s[i-1]))
            {
                if(i-4>=0)
                {
                    string tmp=s.substr(i-4,4);
                    if(tmp=="null")
                        vt.push_back(INF);
                }
            }
            else
            {
                ans=ans*flag;
                vt.push_back(ans);
                ans=0;
                flag=1;
            }
        }
    }
    return vt;
}
class Codec {
public:
    
    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {
        string s="[";
        queue<TreeNode*> q;
        if(root!=NULL)
        {
            q.push(root);
            s+=to_string(root->val);
            s+=",";
        }
        TreeNode* node=NULL;
        while(!q.empty())
        {
            node=q.front();
            q.pop();
            if(node->left!=NULL)
            {
                q.push(node->left);
                s+=to_string(node->left->val);
            }
            else
            {
                s+="null";
            }
            s+=",";
            if(node->right!=NULL)
            {
                q.push(node->right);
                s+=to_string(node->right->val);
            }
            else
            {
                s+="null";
            }
            s+=",";
        }
        int len=s.size();
        s[len-1]=']';
        return s;
    }
    
    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        vector<int> vt;
        queue<TreeNode*> q;
        vt=split(data, ',');
        
        TreeNode *root=NULL;
        
        if(vt.size()>0)
        {
            TreeNode *node=NULL;
            node=(TreeNode*)malloc(sizeof(TreeNode));
            node->val=vt[0];
            node->left=NULL,node->right=NULL;
            
            root=node;
            q.push(root);
        }
        int k=1;
        
        while(k<vt.size())
        {
            TreeNode* node=q.front();
            if(vt[k]==INF)
                node->left=NULL;
            else
            {
                TreeNode *lt=NULL;
                lt=(TreeNode*)malloc(sizeof(TreeNode));
                lt->val=vt[k];
                lt->left=NULL;
                lt->right=NULL;
                
                node->left=lt;
                q.push(lt);
            }
            k++;
            
            if(vt[k]==INF)
                node->right=NULL;
            else
            {
                TreeNode *rt=NULL;
                rt=(TreeNode*)malloc(sizeof(TreeNode));
                rt->val=vt[k];
                rt->left=NULL;
                rt->right=NULL;
                
                node->right=rt;
                q.push(rt);
            }
            k++;
            
            q.pop();
            
        }
        
        return root;
    }
};

方法2:先序遍历

class Codec {
public:
    void serialize(TreeNode* root,string &s)
    {
        if(root==NULL)
        {
            s.append("null,");
            return;
        }
        else
        {
            s.append(to_string(root->val));
            s.append(",");
        }
        serialize(root->left, s);
        serialize(root->right, s);
    }
    
    void deserialize(TreeNode *&root,string &s)
    {
        if(s=="")
            return;
        
        string tmp="";
        
        int pos=s.find(',');
        if(pos!=string::npos)
        {
            tmp=s.substr(0,pos);
            s=s.substr(pos+1);
        }
        else
        {
            tmp=s;
            s="";
        }

        if(atoi(tmp.c_str())||tmp.compare("0")==0)
        {
            root=new TreeNode(atoi(tmp.c_str()));
            deserialize(root->left, s);
            deserialize(root->right,s);
        }
        else
            root=NULL;
    }
    
    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {
        string s="[";
        serialize(root, s);
        int len=s.size();
        s[len-1]=']';
        return s;
    }
    
    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        TreeNode *root=NULL;
        string s="";
        int len=data.size();
        if(len>2)
        {
            len-=2;
            s=data.substr(1,len);
        }

        deserialize(root,s);
        return root;
    }
};


数据集介绍:电力线目标检测数据集 一、基础信息 数据集名称:电力线目标检测数据集 图片数量: 训练集:2898张图片 验证集:263张图片 测试集:138张图片 总计:3299张图片 分类类别: 类别ID: 0(电力线) 标注格式: YOLO格式,包含对象标注信息,适用于目标检测任务。 数据格式:JPEG/PNG图片,来源于空中拍摄或监控视觉。 二、适用场景 电力设施监控与巡检: 数据集支持目标检测任务,帮助构建能够自动识别和定位电力线的AI模型,用于无人机或固定摄像头巡检,提升电力设施维护效率和安全性。 能源与公用事业管理: 集成至能源管理系统中,提供实时电力线检测功能,辅助进行风险 assessment 和预防性维护,优化能源分配。 计算机视觉算法研究: 支持目标检测技术在特定领域的应用研究,促进AI在能源和公用事业行业的创新与发展。 专业培训与教育: 数据集可用于电力行业培训课程,作为工程师和技术人员学习电力线检测与识别的重要资源。 三、数据集优势 标注精准可靠: 每张图片均经过专业标注,确保电力线对象的定位准确,适用于高精度模型训练。 数据多样性丰富: 包含多种环境下的电力线图片,如空中视角,覆盖不同场景条件,提升模型的泛化能力和鲁棒性。 任务适配性强: 标注格式兼容YOLO等主流深度学习框架,便于快速集成和模型开发,支持目标检测任务的直接应用。 实用价值突出: 专注于电力线检测,为智能电网、自动化巡检和能源设施监控提供关键数据支撑,具有较高的行业应用价值。
【弹簧阻尼器】基于卡尔曼滤波弹簧质量阻尼器系统噪声测量实时状态估计研究(Matlab代码实现)内容概要:本文围绕“基于卡尔曼滤波的弹簧质量阻尼器系统噪声测量与实时状态估计”展开研究,利用Matlab代码实现对系统状态的精确估计。重点在于应用卡尔曼滤波技术处理系统中存在的噪声干扰,提升对弹簧质量阻尼器系统动态行为的实时观测能力。文中详细阐述了系统建模、噪声特性分析及卡尔曼滤波算法的设计与实现过程,展示了滤波算法在抑制测量噪声、提高状态估计精度方面的有效性。同时,该研究属于更广泛的信号处理与状态估计技术应用范畴,适用于复杂动态系统的监控与控制。; 适合人群:具备一定控制系统理论基础和Matlab编程经验的高校研究生、科研人员及工程技术人员,尤其适合从事动态系统建模、状态估计与滤波算法研究的相关人员。; 使用场景及目标:①应用于机械、航空航天、自动化等领域中对振动系统状态的高精度实时估计;②为噪声环境下的传感器数据融合与状态预测提供算法支持;③作为卡尔曼滤波算法在实际物理系统中应用的教学与科研案例。; 阅读建议:建议读者结合Matlab代码实践,深入理解系统建模与滤波器设计的关键步骤,关注噪声建模与滤波参数调优对估计性能的影响,并可进一步拓展至扩展卡尔曼滤波(EKF)或无迹卡尔曼滤波(UKF)在非线性系统中的应用。
【顶级EI复现】计及连锁故障传播路径的电力系统 N-k 多阶段双层优化及故障场景筛选模型(Matlab代码实现)内容概要:本文介绍了一个针对电力系统连锁故障传播路径的N-k多阶段双层优化及故障场景筛选模型,旨在提升电力系统在复杂故障条件下的安全性与稳定性。该模型结合了多阶段动态响应与双层优化架构,能够有效模拟连锁故障的传播过程,并通过优化算法筛选出关键故障场景,进而支撑系统风险评估与预防控制策略制定。文中提供了基于Matlab的代码实现,便于科研人员复现顶级EI期刊研究成果,深入理解电力系统可靠性分析的核心算法与建模方法。; 适合人群:具备一定电力系统基础知识和Matlab编程能力的研究生、科研人员及从事电力系统安全分析的工程技术人员,尤其适合致力于高水平论文复现与创新研究的用户。; 使用场景及目标:①复现电力系统连锁故障分析领域的顶级EI论文模型;②开展N-k故障场景筛选、多阶段优化建模、双层规划算法设计等相关课题研究;③提升在电力系统可靠性、韧性评估与预防控制方面的科研能力。; 阅读建议:建议读者结合文档提供的Matlab代码逐模块学习,重点关注双层优化结构与故障传播路径建模的实现细节,同时参考文中提及的YALMIP工具包和网盘资源进行实践调试,以加深对算法逻辑与工程应用的理解。
#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
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值