移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——13.mapset(模拟实现)

1.对红黑树进行改造

1.1treenode模板参数改变

之前构建treenode模板参数传的是class k,class v(set为k,k;map是k,v),现在直接用T代替

template<class T>  //这里直接传了T作为模板参数,T可能是pair<k,t>,也可能是k
struct RBTtreenode
{RBTtreenode<T>* _left;RBTtreenode<T>* _right;RBTtreenode<T>* _parent;//pair<K, V> kv;T data;color col;RBTtreenode(const T& _data):_left(nullptr), _right(nullptr), _parent(nullptr), data(_data), col(RED){}
};

 

2.构建红黑树的迭代器 

因为要构建const_iterator(不可修改内容) 和iterator(可修改内容)所以需要三个模板参数

//<T,T&,T*> iterator;//普通迭代器
//<T, const T&, const T*> const_iterator;//指向的东西不能改变

template<class T,class Ref,class Ptr>

 iterator内存的是node*类型的数据!!!!

2.1 重载operator*()   (set)

因为set传模板参数只传K,没有Vdata类型是K,

所以用*直接取得data即可

Ref operator*()
{return _node->data;
}

 2.2 重载operator->()   (map)

因为map模板参数传的是K,pair<const K,T>,data类型是pair<const K,T>

想取到K,则需要传回&data,再用->first取得K

Ptr operator->()
{return &_node->data;
}

 2.3operator++()与operator--()

 这里以operator++()做解释:

分三种情况:

1.如果右子树不为空,则找到右子树的最左节点

2.//如果右子树为空,且cur是parent的右子树,则先parent回溯至parent->_parent,再_node变为parent
3.//如果右子树为空,且cur是parent的左子树,则_node变为parent

 

iterator& operator++()
{if (_node->_right){//如果右子树不为空,则找到右子树的最左节点node* cur = _node->_right;while (cur && cur->_left){cur = cur->_left;}_node = cur;}else{//如果右子树为空,且cur是parent的右子树,则先parent回溯至parent->_parent,再_node变为parent//如果右子树为空,且cur是parent的左子树,则_node变为parentnode* cur = _node;node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return *this;
}

2.4.begin()&&end() 

iterator begin()
{node* flag = root;while (flag&&flag->_left)//flag可能为nullptr{flag = flag->_left;}return iterator(flag);
}iterator end()
{return iterator(nullptr); //end用nullptr去构造!!!!!!!!
}const_iterator begin() const
{node* flag = root;while (flag && flag->_left)//flag可能为nullptr{flag = flag->_left;}return const_iterator(flag);
}const_iterator end() const
{return const_iterator(nullptr); //end用nullptr去构造!!!!!!!!
}

3.set.h封装

https://cplusplus.com/reference/set/set/?kw=set

#include"rbt.h"
namespace zone
{template<class K>class set{public:struct setkeyoft //仿函数,用来取出红黑树节点data中的key{const K& operator()(const K& key){return key;}};//set这里的迭代器本质都是const_iterator,因为k要求无法修改typedef typename RBTtree<K, K, setkeyoft>::const_iterator iterator;//记得要使用typename告诉编译器RBTtree<K, K, setkeyoft>::iterator这个是类型,不是函数typedef typename RBTtree<K, K, setkeyoft>::const_iterator const_iterator;iterator begin()const{return it.begin();}iterator end()const{return it.end();}pair<iterator,bool> insert(const K& key){return it.insert(key);}void inorder(){it.inorder();}private:RBTtree<K,K,setkeyoft> it;};
}

3.1 仿函数setkeyoft

仿函数,用来取出红黑树节点data中的key,用于insert函数!!!!

3.2 iterator和const_iterator

//set这里的迭代器本质都是const_iterator,因为k要求无法修改
        typedef typename RBTtree<K, K, setkeyoft>::const_iterator iterator;
        typedef typename RBTtree<K, K, setkeyoft>::const_iterator const_iterator;

4.map.h封装 

https://cplusplus.com/reference/map/map/?kw=map

#include"rbt.h"
namespace zone
{template<class K,class T>class map{public:struct setkeyoft{const K& operator()(const pair<K, T>& key){return key.first;}};//map这里的迭代器则使用的是iterator,因为k要求无法修改,但v可以修改,所以可以直接初始化时用pair<const K, T>typedef typename RBTtree<K, pair<const K, T>, setkeyoft>::iterator iterator;typedef typename RBTtree<K, pair<const K, T>, setkeyoft>::const_iterator const_iterator;pair<iterator, bool> insert(const pair<K, T>& key){return it.insert(key);}T& operator[](const K& key){pair<iterator, bool>ret = insert(make_pair(key,T()));//insert返回一个pair,first是iterator,second是bool类型return ret.first->second;}iterator begin(){return it.begin();}iterator end(){return it.end();}void inorder(){it.inorder();}private:RBTtree<K,pair<const K,T>, setkeyoft> it;};
}

5.insert函数 !!!!!!!

RBT.h里insert函数的返回值是 pair<node*, bool>

但封装过后的map.h,set.h里

pair<iterator,bool> insert(const K& key)
{return it.insert(key);
}

 返回值是pair<iterator,bool>

可见 pair<node*, bool>pair<iterator(这里的iterator已经重命名了,本质是const_iteratir),bool>并不是同一类型,该如何解决呢?

 

 

1.如果T1和U类型一致,T2和V类型一致,那么就是拷贝构造!!!

2.如果不一致,也可以进行普通构造前提是有可以用first来构建T1的函数!!!!!

回到刚才的问题:

可见 pair<node*, bool>pair<iterator(这里的iterator已经重命名了,本质是const_iteratir),bool>并不是同一类型,该如何解决呢?

bool类型肯定可以用bool类型初始化,

iterator可以用node*进行初始化吗?

答案是可以的

treeiterator(node* it):_node(it)
{}

相当于使用了隐式类型转换

 6.杂谈

 

类比指针:

1.iterator 可修改指向的数据,也可改变自身

2.const iterator  可修改指向的数据,但不可改变自身

3.const_iterator 不可修改指向的数据,但能改变自身

 7.代码全览

RBT.h

#include<iostream>using namespace std;enum color
{RED,BLACK
};  //列举color的各种可能情况template<class T>  //这里直接传了T作为模板参数,T可能是pair<k,t>,也可能是k
struct RBTtreenode
{RBTtreenode<T>* _left;RBTtreenode<T>* _right;RBTtreenode<T>* _parent;//pair<K, V> kv;T data;color col;RBTtreenode(const T& _data):_left(nullptr), _right(nullptr), _parent(nullptr), data(_data), col(RED){}
};//<T,T&,T*> iterator;//普通迭代器
//<T, const T&, const T*> const_iterator;//指向的东西不能改变:const_iterator,本身不能改变:const iterator
template<class T,class Ref,class Ptr>
struct treeiterator
{typedef RBTtreenode<T> node;typedef treeiterator<T,Ref,Ptr> iterator;node* _node;treeiterator(node* it):_node(it){}Ref operator*(){return _node->data;}Ptr operator->(){return &_node->data;}iterator& operator++(){if (_node->_right){//如果右子树不为空,则找到右子树的最左节点node* cur = _node->_right;while (cur && cur->_left){cur = cur->_left;}_node = cur;}else{//如果右子树为空,且cur是parent的右子树,则先parent回溯至parent->_parent,再_node变为parent//如果右子树为空,且cur是parent的左子树,则_node变为parentnode* cur = _node;node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}iterator& operator--()   //和++反着来即可{if (_node->_left){node* cur = _node->_left;while (cur && cur->_right){cur = cur->_right;}_node = cur;}else{node* cur = _node;node* parent = cur->_parent;while (parent && cur == parent->_left){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}bool operator!=(const iterator&s){return _node != s._node;}
};template<class K, class T,class keyoft>
class RBTtree
{
public:typedef treeiterator<T,T&,T*> iterator;typedef treeiterator<T, const T&, const T*> const_iterator;//指向的东西不能改变typedef RBTtreenode<T> node;iterator begin(){node* flag = root;while (flag&&flag->_left)//flag可能为nullptr{flag = flag->_left;}return iterator(flag);}iterator end(){return iterator(nullptr); //end用nullptr去构造!!!!!!!!}const_iterator begin() const{node* flag = root;while (flag && flag->_left)//flag可能为nullptr{flag = flag->_left;}return const_iterator(flag);}const_iterator end() const{return const_iterator(nullptr); //end用nullptr去构造!!!!!!!!}pair<node*, bool> insert(const T& _data)//!!!!!!!!!{if (root == nullptr){root = new node(_data);root->col = BLACK;//规定根必须是黑的return make_pair(root, true);}node* parent = nullptr; //比bst多了一个parentnode* cur = root;keyoft type;//取出data的K类型的数据while (cur){parent = cur;if (type(cur->data) < type(_data)) //这里取出key再进行比较{cur = cur->_right;}else if (type(cur->data) > type(_data)){cur = cur->_left;}else{return make_pair(cur,false);}}cur = new node(_data);cur->col = RED;//因为如果插入黑色的会使很多节点的一条路径上的黑色节点增多(相当于得罪了所有人),而插入红色则有可能只得罪父亲(如果父亲是红色的话)if (type(parent->data) < type(_data)){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;node* newnode = cur;//开始调整while (parent && parent->col == RED)//parent为黑不需要调整,如果cur变成root,parent就不存在退出循环{node* grandparent = parent->_parent;//祖父一定存在,因为只有根节点是没有祖父的,而根节点一定是黑色的if (parent == grandparent->_left){//      g//    p   unode* uncle = grandparent->_right;  //父亲在左则叔叔在右if (uncle && uncle->col == RED)     //情况一.如果叔叔存在且为红色{//变色parent->col = uncle->col = BLACK;grandparent->col = RED;//重置cur,parent,继续向上处理cur = grandparent;//变为祖父parent = cur->_parent;}else //叔叔不存在或为黑色,旋转加变色{//   g//  p// cif (cur == parent->_left)  //情况二.单旋{rotateR(grandparent);parent->col = BLACK;grandparent->col = RED;}//   g//  p//   celse      //情况三.cur==parent->_right,双旋{rotateL(parent);//经历一次左旋后变成情况二!!!!!!!!!!!(cur和parent换位置)rotateR(grandparent);cur->col = BLACK;grandparent->col = RED;}break;//调整一次就结束了,所以经历过旋转后不需要重置cur,parent,grandparent}}else{//      g//    u   p//node* uncle = grandparent->_left;  //父亲在右则叔叔在左if (uncle && uncle->col == RED){parent->col = uncle->col = BLACK;grandparent->col = RED;//cur = grandparent;parent = cur->_parent;}else{//    g//  u   p//        cif (cur == parent->_right){rotateL(grandparent);parent->col = BLACK;grandparent->col = RED;}else{//   g// u   p//    crotateR(parent);rotateL(grandparent);cur->col = BLACK;grandparent->col = RED;}break;//调整一次就结束了,所以经历过旋转后不需要重置cur,parent,grandparent}}}//1.如果parent和uncle都为RED,则可以一起变黑// 2.parent为黑不处理// 3.uncle为黑或不存在,parent为红,旋转+变色root->col = BLACK;//最后以防万一让根变为黑return make_pair(newnode, true);}void rotateL(node* parent)//左旋,(新节点插入到较高右子树的右侧)//   1.右右{node* subr = parent->_right;node* subrl = subr->_left;parent->_right = subrl;subr->_left = parent;node* ppnode = parent->_parent;parent->_parent = subr;if (subrl) //subrl可能为空!!!!!!!{subrl->_parent = parent;}if (parent == root) //即如果parent->_parent==nullptr{root = subr;subr->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subr;}else if (ppnode->_right == parent){ppnode->_right = subr;}subr->_parent = ppnode;}}void rotateR(node* parent)//右旋,(新节点插入到较高左子树的左侧)//   2.左左{node* subl = parent->_left;node* sublr = subl->_right;parent->_left = sublr;if (sublr)               //sublr可能为空!!!!!!!sublr->_parent = parent;node* ppnode = parent->_parent;subl->_right = parent;parent->_parent = subl;if (root == parent){root = subl;subl->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subl;}else if (ppnode->_right == parent){ppnode->_right = subl;}subl->_parent = ppnode;}}void inorder(){_inorder(root);}void _inorder(node* root){keyoft type;if (root == nullptr)return;_inorder(root->_left);cout << type(root->data)<< " ";_inorder(root->_right);}bool check(node* it, int blacknum, int flag){if (it == nullptr){if (blacknum == flag)return true;elsereturn false;}else if (it->col == RED && it->_parent->col == RED)//十分巧妙,因为孩子的情况有很多,但父亲不是红就是黑,所以判断父亲更合适return false;else if (it->col == BLACK)blacknum++;return check(it->_left, blacknum, flag) && check(it->_right, blacknum, flag);}bool isbalance(){return _isbalance(root);}bool _isbalance(node* root){if (root == nullptr)return true;else if (root->col == RED)return false;int blacknum = 0;int flag = 0;node* k = root;while (k){if (k->col == BLACK)flag++;k = k->_left;//这里十分巧妙,因为如果为红黑树,从某一节点到空的所有路径上的黑节点数量是一致的,所以可以先随便选一条路径,算出这一条路径上的黑节点数作为基准值,在由递归去和其他路径比较}return check(root, blacknum, flag);}private:node* root = nullptr;
};

myset.h

#include"rbt.h"
namespace zone
{template<class K>class set{public:struct setkeyoft //仿函数,用来取出红黑树节点data中的key{const K& operator()(const K& key){return key;}};//set这里的迭代器本质都是const_iterator,因为k要求无法修改typedef typename RBTtree<K, K, setkeyoft>::const_iterator iterator;//记得要使用typename告诉编译器RBTtree<K, K, setkeyoft>::iterator这个是类型,不是函数typedef typename RBTtree<K, K, setkeyoft>::const_iterator const_iterator;iterator begin()const{return it.begin();}iterator end()const{return it.end();}pair<iterator,bool> insert(const K& key){return it.insert(key);}void inorder(){it.inorder();}private:RBTtree<K,K,setkeyoft> it;};
}

mymap.h

#include"rbt.h"
namespace zone
{template<class K,class T>class map{public:struct setkeyoft{const K& operator()(const pair<K, T>& key){return key.first;}};//map这里的迭代器则使用的是iterator,因为k要求无法修改,但v可以修改,所以可以直接初始化时用pair<const K, T>typedef typename RBTtree<K, pair<const K, T>, setkeyoft>::iterator iterator;typedef typename RBTtree<K, pair<const K, T>, setkeyoft>::const_iterator const_iterator;pair<iterator, bool> insert(const pair<K, T>& key){return it.insert(key);}T& operator[](const K& key){pair<iterator, bool>ret = insert(make_pair(key,T()));//insert返回一个pair,first是iterator,second是bool类型return ret.first->second;}iterator begin(){return it.begin();}iterator end(){return it.end();}void inorder(){it.inorder();}private:RBTtree<K,pair<const K,T>, setkeyoft> it;};
}

test.cpp

#include<iostream>
#include<vector>
#include<string>using namespace std;#include"myset.h"
#include"mymap.h"void test1()
{zone::set<int> it;it.insert(1);it.insert(3);it.insert(5);it.insert(2);it.insert(4);zone::set<int>::iterator arr = it.begin();while (arr!=it.end() ){cout << *arr << " ";++arr;}//it.inorder();}void test2()
{zone::map<string,string> it;it.insert(make_pair("sort","排序"));it.insert(make_pair("right", "右"));it.insert(make_pair("left", "左"));it.insert(make_pair("middle", "中"));zone::map<string,string>::iterator arr = it.begin();while (arr != it.end()){arr->second += 'x';//map的v可修改cout << arr->first << " ";++arr;}//it.inorder();}void test3()
{string arr[] = { "香蕉","苹果","西瓜","苹果","苹果","西瓜","苹果"};zone::map<string, int> it;for (auto e : arr){it[e]++;}for (auto k : it){++k.second;cout << k.first << ":" << k.second << endl;}
}
int main()
{test3();return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/55481.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【WRF工具】cmip6-to-wrfinterm工具概述:生成WRF中间文件

cmip6-to-wrfinterm工具概述 cmip6-to-wrfinterm工具安装cmip6-to-wrfinterm工具使用快速启动&#xff08;Quick start&#xff09;情景1&#xff1a;MPI-ESM-1-2-HR&#xff08;默认&#xff09;&#xff1a;情景2&#xff1a;BCMM情景3&#xff1a;EC-Earth3 更改使用&#x…

进度条(倒计时)Linux

\r回车(回到当前行开头) \n换行 行缓冲区概念 什么现象&#xff1f; 什么现象&#xff1f;&#xff1f; 什么现象&#xff1f;&#xff1f;&#xff1f; 自己总结&#xff1a; #pragma once 防止头文件被重复包含 倒计时 在main.c中&#xff0c;windows.h是不可以用的&…

大语言模型入门(一)——大语言模型智能助手

一、大语言模型智能助手 2022年末ChatGPT一经推出&#xff0c;一时间不注册个账号用一下都跟不上潮流了。然而&#xff0c;我们要注册OpenAI的账号使用ChatGPT还是一件比较麻烦的事情&#xff08;懂的都懂&#xff09;。好在&#xff0c;国内各大团队非常给力地及时推出了自研的…

计算机网络--TCP、UDP抓包分析实验

计算机网络实验 目录 实验目的 实验环境 实验原理 1、UDP协议 2、TCP协议 实验具体步骤 实验目的 1、掌握使用wireshark工具对UDP协议进行抓包分析的方法&#xff0c;掌握UDP协议的报文格式&#xff0c;掌握UDP协议校验和的计算方法&#xff0c;理解UDP协议的优缺点&am…

Linux云计算 |【第四阶段】RDBMS1-DAY3

主要内容&#xff1a; 子查询&#xff08;单行单列、多行单列、单行多列、多行多列&#xff09;、分页查询limit、联合查询union、插入语句、修改语句、删除语句 一、子查询 子查询就是指的在一个完整的查询语句之中&#xff0c;嵌套若干个不同功能的小查询&#xff0c;从而一…

《OpenCV》—— 指纹验证

用两张指纹图片中的其中一张对其验证 完整代码 import cv2def cv_show(name, img):cv2.imshow(name, img)cv2.waitKey(0)def verification(src, model):sift cv2.SIFT_create()kp1, des1 sift.detectAndCompute(src, None)kp2, des2 sift.detectAndCompute(model, None)fl…

stm32四足机器人(标准库)

项目技术要求 PWM波形的学习 参考文章stm32 TIM输出比较(PWM驱动LED呼吸灯&&PWM驱动舵机&&PWM驱动直流电机)_ttl pwm 驱动激光头区别-CSDN博客 舵机的学习 参考文章 stm32 TIM输出比较(PWM驱动LED呼吸灯&&PWM驱动舵机&&PWM驱动直流电机)…

Pikichu-xss实验案例-通过xss获取cookie

原理图&#xff1a; pikachu提供了一个pkxss后台&#xff1b; 该后台可以把获得的cookie信息显示出来&#xff1b; 查看后端代码cookie.php&#xff1a;就是获取cookie信息&#xff0c;保存起来&#xff0c;然后重定向跳转到目标页面&#xff1b;修改最后从定向的ip&#xff0…

V3D——从单一图像生成 3D 物体

导言 论文地址&#xff1a;https://arxiv.org/abs/2403.06738 源码地址&#xff1a;https://github.com/heheyas/V3D.git 人工智能的最新进展使得自动生成 3D 内容的技术成为可能。虽然这一领域取得了重大进展&#xff0c;但目前的方法仍面临一些挑战。有些方法速度较慢&…

Scrapy 爬虫的大模型支持

使用 Scrapy 时&#xff0c;你可以轻松使用大型语言模型 (LLM) 来自动化或增强你的 Web 解析。 有多种使用 LLM 来帮助进行 Web 抓取的方法。在本指南中&#xff0c;我们将在每个页面上调用一个 LLM&#xff0c;从中抽取我们定义的一组属性&#xff0c;而无需编写任何选择器或…

【ZYNQ 开发】填坑!双核数据采集系统LWIP TCP发送,运行一段时间不再发送且无法ping通的问题解决

问题描述 之所以说是填坑&#xff0c;是因为之前写了一篇关于这个双核数据采集系统的调试记录&#xff0c;问题的具体表现是系统会在运行一段时间后&#xff08;随机不定时&#xff0c;长了可能将近两小时&#xff0c;短则几分钟&#xff09;&#xff0c;突然间就不向电脑发送数…

windows下安装rabbitMQ并开通管理界面和允许远程访问

如题&#xff0c;在windows下安装一个rabbitMQ server&#xff1b;然后用浏览器访问其管理界面&#xff1b;由于rabbitMQ的默认账号guest默认只能本机访问&#xff0c;因此需要设置允许其他机器远程访问。这跟mysql的思路很像&#xff0c;默认只能本地访问&#xff0c;要远程访…

Web和UE5像素流送、通信教程

一、web端配置 首先打开Github地址&#xff1a;https://github.com/EpicGamesExt/PixelStreamingInfrastructure 找到自己虚幻引擎对应版本的项目并下载下来&#xff0c;我这里用的是5.3。 打开项目找到PixelStreamingInfrastructure-master > Frontend > implementat…

Redis介绍及整合Spring

目录 Redis介绍 Spring与Redis集成 Redis介绍 Redis是内存数据库&#xff0c;Key-value型NOSQL数据库&#xff0c;项目上经常将一些不经常变化并且反复查询的数据放入Redis缓存&#xff0c;由于数据放在内存中&#xff0c;所以查询、维护的速度远远快于硬盘方式操作数据&#…

启动服务并登录MySQL9数据库

【图书推荐】《MySQL 9从入门到性能优化&#xff08;视频教学版&#xff09;》-CSDN博客 《MySQL 9从入门到性能优化&#xff08;视频教学版&#xff09;&#xff08;数据库技术丛书&#xff09;》(王英英)【摘要 书评 试读】- 京东图书 (jd.com) Windows平台下安装与配置MyS…

Llama3.2开源:Meta发布1B和3B端侧模型、11B和90B多模态模型

最近这一两周不少互联网公司都已经开始秋招提前批面试了。 不同以往的是&#xff0c;当前职场环境已不再是那个双向奔赴时代了。求职者在变多&#xff0c;HC 在变少&#xff0c;岗位要求还更高了。 最近&#xff0c;我们又陆续整理了很多大厂的面试题&#xff0c;帮助一些球友…

大数据毕业设计选题推荐-民族服饰数据分析系统-Python数据可视化-Hive-Hadoop-Spark

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

栏目二:Echart绘制动态折线图+柱状图

栏目二&#xff1a;Echart绘制动态折线图柱状图 配置了一个ECharts图表&#xff0c;该图表集成了数据区域缩放、双Y轴显示及多种图表类型&#xff08;折线图、柱状图、象形柱图&#xff09;。图表通过X轴数据展示&#xff0c;支持平滑折线展示比率数据并自动添加百分比标识&…

Docker-2.如何保存数据退出

在使用Docker时&#xff0c;我们常常需要修改容器中的文件&#xff0c;并且希望在容器重启后这些修改能够得到保留。 0.简介 使用Docker时有一个需要注意的问题&#xff1a;当你修改了容器中的文件后&#xff0c;重启容器后这些修改将会被重置&#xff0c;深入研究这个问题。 …

企业间图文档发放:如何在保障安全的同时提升效率?

不管是大型企业&#xff0c;还是小型创业公司&#xff0c;不论企业规模大小&#xff0c;每天都会有大量的图文档发放&#xff0c;对内传输协作和对外发送使用&#xff0c;数据的生产也是企业业务生产力的体现之一。 伴随着业务范围的不断扩大&#xff0c;企业与客户、合作伙伴之…