【C++】红黑树的封装——同时实现map和set
目录
- 红黑树的完善
- 默认成员函数
- 迭代器的增加
- 红黑树的封装
- 红黑树模板参数的控制
- 仿函数解决取K问题
- 对Key的非法操作
- insert的调整
- map的[]运算符重载
在list模拟实现一文中,介绍了如何使用同一份代码封装出list的普通迭代器和const迭代器。今天学习STL中两个关联式容器map
和set
,其底层就是红黑树,而且是同一棵红黑树。所以今天学习如何用同一棵红黑树封装出map
和set
。
红黑树的完善
默认成员函数
map和set的底层是红黑树,所以先将红黑树重要的四个默认成员函数完善好。这四个成员函数在介绍搜索二叉树时已进行过介绍,这里不再讲解。
public://RBTree() = default;RBTree():_root(nullptr){}RBTree(const RBTree& t)//拷贝构造{_root = Copy(t._root);}RBTree& operator=(RBTree t)//赋值重载{swap(_root, t._root);return *this;}~RBTree(){Destroy(_root);_root = nullptr;}
private:Node* Copy(Node* root){if (root == nullptr){return nullptr;}Node* newnode = new Node(root->_kv);newnode->_left = Copy(root->_left);newnode->_right = Copy(root->_right);return newnode;//返回时才连接}void Destroy(Node* root){if (root == nullptr){return;}Destroy(root->_left);Destroy(root->_right);delete root;}
迭代器的增加
map和set作为关联式容器,迭代器是必不可少的,而map和set都是由同一颗红黑树封装而成,所以,map和set的迭代器其实就是红黑树的迭代器。
map和set的迭代器都是双向迭代器,也就是说都支持++,- -的操作。
红黑树的迭代器的结构和list的是类似的,其成员都只有一个节点类,只有一个构造函数初始化节点。
迭代器:
template<class T, class Ref, class Ptr>
struct RBTreeIterator
{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T,Ref,Ptr> self;Node* _node;//成员RBTreeIterator(Node* node):_node(node){}
};
节点类:
template<class T>
struct RBTreeNode
{T _kv;RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;Color _col;RBTreeNode(const T& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED)//默认为红色{}};
对于自封装的迭代器:解引用,是否相等的比较是少不了的,而这部分操作还是比较容易的,不理解的可参考list迭代器的实现
- 注意此时要访问的的对象是
_kv
Ref operator*(){return _node->_kv;}Ptr operator->(){return &_node->_kv;}bool operator==(const self& s){return _node == s._node;}bool operator!=(const self& s){return _node != s._node;}
真正的难点是红黑是迭代器的遍历:中序遍历红黑树便能得到一个升序序列,而迭代器访问的顺序也是中序:左根右。采用Inorde
来遍历是很简单的,但是它是不可控的,只能一把把红黑树全遍历完。而迭代器必须是一个一个节点访问的,这就增加了它实现的难度。
前置++
对于++,即按中序访问下一个节点,但查找时此时的节点已经为根,所以查找的顺序为根,右;此时关注的点是下一个节点是否为空,由此分为两种情况。
- 右子树不为空;则,右子树的最左节点即为下一个要访问的节点。
- 右子树为空,说明当前子树已经访问完了;沿着到根节点的路径查找祖先节点中左孩子为父亲的节点即为下一个要访问的节点。
- 如果父节点为祖先节点的右孩子,说明递归有一定深度,需要不断向上查找。
self& operator++(){if (_node->_right){Node* leftMost = _node->_right;while (leftMost->_left){leftMost = leftMost->_left;}_node = leftMost;//return self(leftMost);}else//说明当前子树访问完了{Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}//return self(parent);_node = parent;}return *this;}
前置++可以引用返回。
后置++
后置++逻辑与前置是一样的,只不过返回的是++之前的值。所以用ret保留原先的值,再访问下一个节点,最后返回ret即可。此时不能引用返回。
self operator++(int){self ret = *this;if (_node->_right){Node* leftMost = _node->_right;while (leftMost->_left){leftMost = leftMost->_left;}_node = leftMost;}else//说明当前子树访问完了{Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return ret;}
前置- -
红黑树的- -即访问当前节点的前一个节点。此时的查找顺序变为根,左;自身就作为根节点,所以此时只需要注意左节点是否为空。
- 左节点不为空,则按右根左的遍历顺序遍历;访问左子树的最右孩子。
- 左节点为空,说明当前子树已经访问完了;沿着到根节点的路径查找祖先节点中右孩子为父亲的节点即为下一个要访问的节点。
- 如果父节点为祖先节点的左孩子,说明递归有一定深度,需要不断向上查找
self& operator--(){if (_node->_left){Node* rightMost = _node->_left;while (rightMost->_right){rightMost = rightMost->_right;}_node = rightMost;}else//说明当前子树访问完了{Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}
后置- -
返回- -之前的节点。
self operator--(int){Node* ret = *this;if (_node->_left){Node* rightMost = _node->_left;while (rightMost->_right){rightMost = rightMost->_right;}_node = rightMost;}else//说明当前子树访问完了{Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = parent;parent = parent->_parent;}_node = parent;}return ret;}
迭代器实现后,我们需要在红黑树的实现当中进行迭代器类型的typedef。需要注意的是,为了让外部能够使用typedef后的迭代器类型iterator,需要在public区域进行typedef。
然后在红黑树当中实现成员函数begin和end:
- begin函数返回中序序列当中第一个结点的迭代器,即最左结点。
- end函数返回中序序列当中最后一个结点下一个位置的迭代器,这里直接用空指针构造一个迭代器。
实现时红黑树一层命名为与map和set作区分,首字母大写。
template<class K, class T,class KeyOfT>//map时T为pair,set时为K
class RBTree
{typedef RBTreeNode<T> Node;
public:typedef RBTreeIterator<T, T&, T*> Iterator;typedef RBTreeIterator<T, const T&, const T*> Const_Iterator;//map和set的迭代器也是使用红黑树的迭代器,但树的结构有所区别。//红黑树的迭代器Iterator Begin(){Node* leftMost = _root;while (leftMost->_left){leftMost = leftMost->_left;}return leftMost;//此时返回的是节点,而返回类型为迭代器,所以会发生隐式类型转化,调用红黑树的迭代器构造函数构造一个迭代器}Iterator End()//与库里的带头红黑树不同,我们这里不带头,为空即尾{return nullptr;}Const_Iterator Begin()const{Node* leftMost = _root;while (leftMost->_left){leftMost = leftMost->_left;}return leftMost;}Const_Iterator End()const//与库里的带头红黑树不同,我们这里不带头,为空即尾{return nullptr;}private:Node* _root = nullptr;};
库中红黑树的结构实现
库里的红黑树结构中还有一个头节点header,header的parent指针指向_root,_root的parent指针指向header,header的左右指针分别指向红黑树的最小和最大节点。
由于结构不同,所以我们迭代器的实现也有一定缺陷,但不妨碍正常使用,所以就不进行完善了。
红黑树的封装
都知道set
是key
模型,而map
是key_value
模型,如何才能使用同一棵红黑树满足二者的需求呢?这就是封装的魅力。
了解map和set之后,两者的冲突点主要有:
map
是KV
模型,set
的K
模型- 获取
Key
map
和set
储存数据的方式不一样;红黑树的大多数操作都是需要支持用Key
比大小的。
map
和set
的Key
不可修改问题
红黑树模板参数的控制
关于set和map的不同模型问题,先看看库中是如何解决这个问题的。
对于红黑树而言,它是不知道上层是map还是set的,但是这些都不重要;底层红黑树利用泛型的思想,将map和set传过来的参数实例化为模板;这样一来,上层map传对应的参数过来,底层红黑树就将这些参数泛化成模板,就能生成对应数据类型的红黑树了;set也是同理。
如简化版的下图:map和set的底层都是一棵红黑树,其中map和set中的红黑树传入的第一个参数都为K;而map的第二个参数传入的是一个键值对pair,这也正是map的数据类型。set的第二个参数继续传入一个K,作为set的数据类型。也正是这样的设计,能够让一棵红黑树同时封装map和set。
这样一来,你上层传的是pair,底层红黑树实例出来的就是map,传入的为K,则为set。
- 第三个模板参数是解决下一个问题所提供的仿函数。
set中调用的红黑树为什么要传两次K?
-
set中传两次K确实有点多余,但此时是map和set共用一棵红黑树,在map的日常使用中如:find,erase这样的操作,其参数就是一个K类型,所以map中是需要有K的。
-
-
仿函数解决取K问题
所谓取K,就是由于map和set的数据类型不一致,一个是KV的键值对,一个是模板参数K。作为而平衡二叉树的红黑树,Key值的比对是少不了的,如插入,查找等功能都是需要有Key值的比对的。对于set来说,可以直接用传入的K进行比对;而map是pair,需要解引用访问其first才能找到Key,否则直接比对pair不一定是我们想要的比对结果。
所以,为了解决这个问题,继续参考上一个问题的解决方式:在map和set调用红黑树传参时传入一个可以取Key的仿函数。仿函数介绍
map的仿函数:要获取是数据是什么类型,仿函数的参数就是什么类型。
struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};
set的仿函数:set的仿函数看起来有点多此一举,但为了适配map的解决,提供一个仿函数也无妨。
struct SetKeyOfT{const K& operator()(const K& key){return key;}};
这样一来,set容器传入底层红黑树的就是set的仿函数,map容器传入底层红黑树的就是map的仿函数。
有了仿函数,当底层红黑树需要进行两个结点之间键值的比较时,都会通过传入的仿函数来获取相应结点的键值,然后再进行比较,下面以红黑树的查找函数为例。
Iterator Find(const T& data){KeyOfT kot;if (_root == nullptr){return nullptr;}Node* cur = _root;//Node* parent = nullptr;while (cur){if (kot(data) > kot(cur->_kv)){//parent = cur;cur = cur->_right;}else if (kot(data) < kot(cur->_kv)){//parent = cur;cur = cur->_left;}else{return cur;}}return nullptr;}
对Key的非法操作
map
和set
都是不支持修改Key的
map
对此的解决方案是pair
对中的Key用const修饰
map成员的定义:
private:RBTree<K, pair<const K, V>, MapKeyOfT> _rbt;
set将红黑树的第二个参数改为const K
好像也能解决问题,但库里并没有采用这种方法。
简易办法:使用const K禁止被修改。目前还没发现什么大问题,如果发现问题请告知
set成员的定义:
private:RBTree<K,const K, SetKeyOfT> _rbt;
第二种就是采用库里的方法。
set无论普通还是const迭代器都使用红黑树的const迭代器
public://库里的方法:typedef typename RBTree<K, K, SetKeyOfT>::Const_Iterator iterator;typedef typename RBTree<K, K, SetKeyOfT>::Const_Iterator const_iterator;
但是这样就会出现类型转化问题
原因如下:
这里的迭代器不是原生指针的迭代器,而是通过封装而来的,借助模板实现了const和非const版的迭代器;也就导致普通的无法转为const迭代器;通过调试发现问题出现在获取迭代器上,由于set的普通迭代器也由const迭代器封装而来,set在获取普通迭代器时,最底层的红黑树返回的是普通迭代器,但set的普通迭代器实际为const迭代器,此时就发生了类型不匹配的问题。
对此,库中提供了如下解决方案:在红黑树迭代器中提供一个构造函数
在红黑树迭代器中增加一个参数为普通迭代器类型的构造函数。该构造函数取参数中普通迭代器的节点重新构造一个迭代器(const版)。该方法绕过转化,采用构造,生成一个const版的迭代器,自然就没有类型转化的问题。
虽然这个构造函数增加在红黑树的迭代器中,但是map的迭代器不会有影响,这个构造函数只会匹配到set对应的状况。
typedef RBTreeIterator<T, T&, T*> Iterator; //声明类型:普通迭代器RBTreeIterator(const Iterator& it)//类型为普通迭代器,因为就是由于普通迭代器转化为const迭代器这一环出了 问题,这里专门针对这一情况处理:_node(it._node)//此时需要的是一个const迭代器,(由于模板无法转化?)普通转const直接转化不行,那么我们就在返回时利用隐式类型转化{}
具体过程请看下图。
insert的调整
在AVL树红黑树阶段实现的insert的返回值都为bool,在map和set中则是改为返回键值对pair。
以map为例:
函数声明:
pair<iterator,bool> insert (const value_type& val);
可以看到其返回值为pair,pair的第一个成员为一个迭代器指向新插入的元素,或者已存在的等效元素,第二个成员为bool,用来检测插入是否成功;也就是说insert成功的话会返回一个指向新插入元素的迭代器,其bool值为true的pair,如果insert失败则会返回一个指向容器中原有的K的迭代器,其bool值为false的pair。
map的[]运算符重载
map支持[]访问容器中Key值并返回Key对应的Value;set不支持[]重载,因为只有一个Key。
也就是说[]的参数为pair中的第一个成员K,其使用说明如下:
如果K与容器中某个元素的键匹配,则该函数返回K值关联的Value引用。如果K与容器中任何元素的键不匹配,则该函数用该键插入一个新元素,并返回对其映射值的引用。这个新元素会有默认值。
调用[]类似于下面的操作:
(*((this->insert(make_pair(k,mapped_type()))).first)).second
也就是说[]的实现是借助于insert实现的。而这样的话[]的用法就有两种:
- 插入
- 如果K是map中不存在的,那么这就是一个插入操作;由于其返回值为第二个模板参数value的引用,所以可以直接修改。
- 修改
- 如果K是map中已有的值,那么insert将会返回已存在K的迭代器,[]最终返回一个K的引用,相当于支持修改操作。
所以[]重载运算符的实现如下。
//返回value的引用V& operator[](const K& key){pair<iterator, bool> ret = _rbt.Insert({ key,V() });return ret.first->second;//不理解返回second的结合operator->看}
- 返回值类型为第二个参数的引用,所以支持直接修改。
- 关于返回V&为何是返回it.first->second:it 为接受的是insert返回的pair对,其first为指向元素的迭代器,->调用了迭代器的operator->,此时返回的是存储KV的pair,此时的second为V
相关文章:

【C++】红黑树的封装——同时实现map和set
目录 红黑树的完善默认成员函数迭代器的增加 红黑树的封装红黑树模板参数的控制仿函数解决取K问题对Key的非法操作 insert的调整map的[]运算符重载 在list模拟实现一文中,介绍了如何使用同一份代码封装出list的普通迭代器和const迭代器。今天学习STL中两个关联式容器…...

Tableau|一入门
一 什么是BI工具 BI 工具即商业智能(Business Intelligence)工具,是一种用于收集、整理、分析和展示企业数据的软件系统,其主要目的是帮助企业用户更好地理解和利用数据,以支持决策制定。 主要功能: 1.数据…...

Android 12系统源码_输入系统(三)输入事件的加工和分发
前言 上一篇文章我们具体分析了InputManagerService的构造方法和start方法,知道IMS的start方法经过层层调用,最终会触发Navite层InputDispatcher的start方法和InputReader的start方法。InputDispatcher的start方法会启动一个名为InputDispatcher的线程&…...

【Elasticsearch系列廿二】特殊参数
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

Java笔试面试题AI答之设计模式(2)
文章目录 6. 什么是单例模式,以及他解决的问题,应用的环境 ?解决的问题应用的环境实现方式 7. 什么是工厂模式,以及他解决的问题,应用的环境 ?工厂模式简述工厂模式解决的问题工厂模式的应用环境工厂模式的…...

54 循环神经网络RNN_by《李沐:动手学深度学习v2》pytorch版
系列文章目录 文章目录 系列文章目录循环神经网络使用循环神经网络的语言模型困惑度(perplexity)梯度剪裁 循环神经网络 使用循环神经网络的语言模型 输入“你”,更新隐变量,输出“好”。 困惑度(perplexityÿ…...

数据仓库-数据质量规范
一、 数据质量系统概述 1.1 数据质量管理系统1.2 数据质量建设流程1.3 数据质量标准二、 数据质量管理规则 2.1 数据校验规则列表 2.1.1 数据量2.1.2 数据量对比2.1.3 空值检查2.1.4 值域检查2.1.5 规范检查2.1.6 逻辑检查2.1.7 重复数据检查2.1.8 及时性检查...

PostgreSQL 17 发布了!非常稳定的版本
📢📢📢📣📣📣 作者:IT邦德 中国DBA联盟(ACDU)成员,10余年DBA工作经验, Oracle、PostgreSQL ACE CSDN博客专家及B站知名UP主,全网粉丝10万 擅长主流Oracle、My…...

【Python】执行脚本的时,如何指定运行根目录,而不是指定脚本的父级目录
author: jwensh & gpt date: 2024.09.23 python 执行脚本的时,如何指定运行根目录,而不是指定脚本的父级目录 prompt:python 执行脚本的时候,如何指定他的运行根目录,而不是指定脚本的父级目录 在执行 Python 脚…...

JVM(HotSpot):程序计数器(Program Counter Register)
文章目录 一、内存结构图二、案例解读三、工作流程四、特点 一、内存结构图 二、案例解读 我们使用javap对字节码进行反编译,来看下程序计数器怎么体现的。 IDEA写一个简单的Java代码 反编译命令 javap -verbose InitTest.class $ javap -verbose InitTest.clas…...

等保托管怎么样,流程是什么样的?
随着信息技术的快速发展,网络安全问题愈发凸显。为了保护信息系统的安全,国家推出了网络安全等级保护制度(简称“等保”),企业在面对这一制度的同时,也逐渐意识到等保托管的重要性。等保托管旨在通过专业的…...

【HTML】img标签和超链接标签
文章目录 img 标签src 属性alt 属性title 属性width/height 属性border 属性 超链接标签:a表格标签合并单元格 img 标签 img 是一个单标签 src 属性 img 标签必须搭配 src 使用(指定图片的路径) 相对路径: ./xxx.png./img/xxx.…...

智能PPT行业赋能用户画像
智能PPT市场在巨大的需求前景下,已吸引一批不同类型的玩家投入参与竞争。从参与玩家类型来看,不乏各类与PPT创作有关的上下游企业逐步向智能PPT赛道转型进入,也包括顺应生成式AI技术热潮所推出的创业企业玩家。当前,智能PPT赛道发…...

学习C++的第七天!
1.虚函数是在基类中用 virtual 关键字声明的函数,可以在派生类中被重写。纯虚函数是在虚函数的基础上,在基类中被初始化为 0 的函数,含有纯虚函数的类是抽象类,不能被实例化。 2.如果基类的析构函数不是虚函数,当通过…...

Java编程必备:五大高效工具与框架
作为一位Java程序员,在编写Java代码时,通常会使用多种工具和框架来提高开发效率、保证代码质量并简化开发流程。以下是五个常用的Java程序员工具和框架及其简要说明: 1. IntelliJ IDEA 主要功能:IntelliJ IDEA是一个强大的Java集…...

现代桌面UI框架科普及WPF入门1
现代桌面UI框架科普及WPF入门 文章目录 现代桌面UI框架科普及WPF入门桌面应用程序框架介绍过时的UI框架MFC (Microsoft Foundation Class)缺点 经典的UI框架**WinForms****QT****WPF** 未来的UI框架**MAUI****AvaloniaUI** WPF相对于Winform,QT,MFC的独立…...

in和like性能对比
场景: 有个问题表,有个渠道表,问题和渠道的关系是一对多 需要根据渠道查询问题,暂时两种思路 1:问题表荣誉渠道id,多个id拼接 2:设计问题和渠道关联关系表 首先,这两种是常用的设计思路,那么查询谁的速度快 问题表:造10w数据,渠道表造100条数据 结论 实测10次后,发现like耗…...

Redis|基础学习
跟着狂神学习的Redis笔记,详细课程可以移步【狂神说Java】Redis最新超详细版教程通俗易懂 文章目录 NoSQLNoSQL 数据库的主要类型NoSQL 的特点NoSQL 的应用场景 Redis什么是 RedisRedis 能干嘛Windows 以及 Linux 下安装 RedisRedis 基本知识RedisKey的基本命令Redi…...

手把手教你在Linux上构建Electron
开发electron最大的特点就是可以使用web技术来开发跨平台应用,大部分开发都是在windows/mac上开发的electron应用,我使用的是electorn-builder来构建应用,官网提供支持在windows上使用docker来实现Linux版本的构建。可以直接在Linux服务器上完…...

力扣【448-消失的数字】【数组-C语言】
题目:力扣-448 给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。 把数组中存在[1…n]的元素放其元素值-1的位置上,第一个fo…...

面试题:排序算法的稳定性?(文末有福利)
回归面试题! 回答重点 稳定的排序算法:冒泡排序、插入排序、归并排序、计数排序。 不稳定的排序算法:选择排序、快速排序、堆排序、希尔排序。 扩展知识 1)冒泡排序(Bubble Sort) 原理: 冒…...

在Jdk1.8中Collectors和Comparator使用场景
在Jdk1.8中Collectors和Comparator使用场景 Collectors 和 Comparator 是 Java 8 引入的两个非常重要的类,它们在处理集合和流(Streams)时起着重要的作用。以下是这两个类的使用场景以及它们的典型用法。 1. Collectors Collector…...

linux-性能优化命令
top 我们先来说说top命令用法,这个命令对于我们监控linux性能是至关重要的,我们先来看看展示结果。 top - 15:20:23 up 10 min, 2 users, load average: 0.39, 0.53, 0.35 Tasks: 217 total, 1 running, 216 sleeping, 0 stopped, 0 zombie %C…...

基于MT79815G CPE 板子上挂usb3.0的5G 模块,WIFI能跑多少速度呢
关于MT79815G CPE 板子上挂usb3.0的5G 模块,WIFI能跑多少速度的问题,我们以启明智显 ZX7981P智能无线接入型路由器(CPE)挂广合通5G模组为例说明: 一般来说,用 ZX7981P,通过软加速,U…...

R包compareGroups详细用法
compareGroups compareGroups 是一个功能强大的 R 包,专为数据质量控制、数据探索和生成用于出版的单变量或双变量表格而设计。它能够创建各种格式的报表,如纯文本、HTML、LaTeX、PDF、Word 或 Excel 格式,并显示统计数据(均值、…...

如何选择高品质SD卡
如何选择高品质SD卡 SD卡(Secure Digital Memory Card)是一种广泛使用的存储器件,因其快速的数据传输速度、可热插拔的特性以及较大的存储容量,广泛应用于各种场景,例如在便携式设备如智能手机、平板电脑、运动相机等…...

C++学习:模拟priority_queue
一:仿函数 开始模拟前咱先了解一下仿函数。有了它,我们就可以自己传个代码让优先级队列升序还是降序,自己模拟时也不用在需要升序降序时改代码。这是个很有用的东西。 不写模版也可以,但模版能用在更多地方嘛 template <class …...

同程旅行对标拼多多:“形似神不似”
文:互联网江湖 作者:刘致呈 业绩好,并不意味着同程旅行就能高枕无忧了。 最近,媒体曝出:有用户在同程旅行APP上预订酒店,在预订成功并付款后,结果第二天却被酒店告知,没有查到相关…...

HOJ网站开启https访问 申请免费SSL证书 部署证书详细操作指南
https://console.cloud.tencent.com/ 腾讯云用户 登录控制台 右上角搜SSL 点击 SSL证书 进入链接 点申请 免费证书 有效期3个月 (以后每三个月申请一次证书 上传) 如果是腾讯云申请的域名 选 自动DNS验证 自动添加验证记录 如果是其他平台申请域…...

程序设计基础I-实验4 循环结构之for语句
7-1 sdut-C语言实验-AB for Input-Output Practice (Ⅳ) Your task is to Calculate a b. 输入格式: Your task is to Calculate a b. 输出格式: For each pair of input integers a and b you should output the sum of a and b in one line, and with one line of out…...