目次
1.红黑树的迭代器
1.2.迭代器的界说
1.3.operator++()
1.4*,->,!=
2.改造红黑树
类模板
构造
赋值运算符重载
析构函数
查找
begin()/end()
插入
3.map的模拟实现
4.set的模拟实现
总结
1.红黑树的迭代器
(本篇代码基于我写的红黑树的实现这篇博客)
迭代器的利益是可以方便遍历,是数据布局的底层实现与用户透明。假如想要给红黑树增长迭代器,必要思量以下题目:
begin()与end(): STL明白规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树举行中序遍历后, 可以得到一个有序的序列,因此:begin()可以放在红黑树中最末节点(即最左侧节点)的位置,end()放在最大节点(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪块? 能否给成nullptr呢?答案是行不通的,由于对end()位置的迭代器举行--利用,必须要能找末了一个元素,此处就不可,因此最好的方式是将end()放在头结点的位置:
1.2.迭代器的界说
迭代器我们采取类模板的方式来举行,Ref是T的引用,Ptr是T的指针
- typedef __RBTreeIterator<T,const T&,const T*> ConstIterator;
复制代码 Node是我们红黑树的节点,Self是我们迭代器范例本身,构造我们也就选一个红黑树的节点举行拷贝构作育好了。
1.3.operator++()
- //++
- Self operator++()
- {
- if (_node->_right)
- {
- Node* leftMin = _node->_right;
- while (leftMin->_left)
- {
- leftMin = leftMin->_left;
- }
- _node = leftMin;
- }
- else
- {
- Node* cur = _node;
- Node* parent = _node->_parent;
- while (parent && cur == parent->_right)
- {
- cur = parent;
- parent = parent->_parent;
- }
- _node = parent;
- }
- return *this;
- }
复制代码 迭代器的出现是为了我们更方便地去举行增删查改,以是我们要能有规律的查找元素,我们来分析++的逻辑,我们的遍历是按照升序的方式,以是,我们要找到比当前节点大的下一个节点,我们先来看一下当前节点是否有右子树,有的话就往右子树的最小左子树去找,谁人节点就是我们要找的下一个节点。假如我们的当前节点右子树是空,我们就要不绝往上更新,直到为左子树然后它的父亲就是下一个节点了。
1.4*,->,!=
- //*
- Ref operator*()
- {
- return _node->_data;
- }
- //->
- Ptr operator->()
- {
- return &_node->_data;
- }
- //!=
- bool operator!=(const Self& s)
- {
- return _node != s._node;
- }
复制代码 解引用就把数据返归去,->就把数据的所在返归去,!=就是判断迭代器访问的与你指定的是否相称。
2.改造红黑树
迭代器构建出来了,我们也要对红黑树举行一些简朴的改造,我来讲一些新参加的部门。
类模板
- template<class K, class T, class KeyofT> class RBTree { typedef RBTreeNode<T> Node; public: typedef __RBTreeIterator<T, T&, T*> Iterator; typedef __RBTreeIterator<T,const T&,const T*> ConstIterator;
复制代码 我们的类模板新增长了一个范例KeyofT,这是我们map和set要传入的一个仿函数,具体是什么我背面会讲,它的功能是取出key键值。下面,我们重定名红黑树节点,迭代器也重定名了两个,一个是const修饰的,一个是平常的。
构造
- //构造函数
- RBTree() = default;
- //拷贝构造函数
- RBTree(const RBTree<K, T, KeyofT>& t)
- {
- _root = Copy(t._root);
- }
- Node* Copy(Node* root)
- {
- if (root == nullptr)
- {
- return nullptr;
- }
- Node* newroot = new Node(root->_data);
- newroot->_col = root->_col;
- newroot->_left = Copy(root->_left);
- if (newroot->_left)
- newroot->_left->_parent = newroot;
- newroot->_right = Copy(root->_right);
- if (newroot->_right)
- newroot->_right->_parent = newroot;
-
- return newroot;
- }
复制代码 构造方面我们要编写拷贝构造,以是我们在无参构造背面加一个default关键字,让编译器表现默认天生。然后我们的拷贝构造采取递归的方式将每一个节点拷贝下来,我们利用前序遍历的方式将节点拷贝下来就好了。
赋值运算符重载
- //赋值运算符重载
- RBTree<K, T, KeyofT>& operator=(RBTree<K, T, KeyofT> t)
- {
- swap(_root, t._root);
- return *this;
- }
复制代码 赋值运算符重载我们复用拷贝构造函数的代码,我们的参数会自动调用拷贝构造,然后我们就只要将两根节点交换就可以了。
析构函数
- //析构函数
- ~RBTree()
- {
- Destroy(_root);
- _root = nullptr;
- }
- void Destroy(Node* root)
- {
- if (root == nullptr)
- {
- return;
- }
- Destroy(root->_left);
- Destroy(root->_right);
- delete(root);
- root = nullptr;
- }
复制代码 析构我们则是采取后序遍历的方法举行递归删除。
查找
- //Find()
- Iterator Find(const T& data)
- {
- KeyofT kot;
- Node* parent = nullptr;
- Node* cur = _root;
- while (cur)
- {
- if (kot(data) > kot(cur->_data))
- {
- parent = cur;
- cur = cur->_right;
- }
- else if (kot(data) < kot(cur->_data))
- {
- parent = cur;
- cur = cur->_left;
- }
- else
- {
- return Iterator(cur);
- }
- }
- return end();
- }
复制代码 查找我们就利用二叉搜索树的规则去查找就可以了(要查找的节点比当前节点小就往左,大就往右),kot是仿函数范例的变量,它的作用是返回key值,由于我们插入的时间是按key值来举行插入的。
begin()/end()
- //begin()
- ConstIterator begin() const
- {
- Node* leftMin = _root;
- while (leftMin && leftMin->_left)
- {
- leftMin = leftMin->_left;
- }
- return ConstIterator(leftMin);
- }
- //end()
- ConstIterator end() const
- {
- return ConstIterator(nullptr);
- }
- //begin()
- Iterator begin()
- {
- Node* leftMin = _root;
- while (leftMin && leftMin->_left)
- {
- leftMin = leftMin->_left;
- }
- return Iterator(leftMin);
- }
- //end()
- Iterator end()
- {
- return Iterator(nullptr);
- }
复制代码 begin和end我们分别计划了const修饰的宁静常的两组,平常的begin我们就找最小的节点,也就是最左边,end我们就是一个空节点,我们返回的时间都是复用迭代器的拷贝构造,const修饰的版本思绪也是千篇一律的,只不外加上了const修饰。
插入
插入大抵的思绪还是没有变的,我就夸大一下改变的部门。
我们先是将插入函数的返回值范例给改变了,将它改成了pair布局体范例,这是为了我们后续对map和set举行封装,然后我们的返回都要用make_pair将数据变革成pair布局体的方式举行返回,然后我们还要创建一个KeyofYT范例的对象,它的作用我前面已经说过了,是利用仿函数来返回key值举行巨细比力。别的的代码就跟我红黑树那一篇文章的代码千篇一律了。
红黑树方面的代码的改进就以上这些
3.map的模拟实现
map的底层布局就是红黑树,因此在map中直接封装一棵红黑树,然后将其接口包装下即可
- #pragma once
- #include"_RBtree.h"
- namespace Mybit
- {
- template<class K,class V>
- class map
- {
- struct KeyofT
- {
- const K& operator()(const pair<K,V>& kv)
- {
- return kv.first;
- }
- };
- public:
- typedef typename RBTree<K, pair<const K, V>, KeyofT>::Iterator iterator;
- typedef typename RBTree<K,pair<const K, V>, KeyofT>::ConstIterator const_iterator;
- iterator begin()
- {
- return _t.begin();
- }
- iterator end()
- {
- return _t.end();
- }
- const_iterator begin() const
- {
- return _t.begin();
- }
- const_iterator end() const
- {
- return _t.end();
- }
- pair<iterator, bool> insert(const pair<K, V>& kv)
- {
- return _t.Insert(kv);
- }
- iterator find(const K& key)
- {
- return _t.Find(make_pair(key, V()));
- }
- V& operator[](const K& key)
- {
- pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));
- return ret.first->second;
- }
- private:
- RBTree<K, pair<const K, V>, KeyofT> _t;
- };
复制代码 由于map是key-value模子,以是我们的类模板设置了两个范例,分别是K,和V,在map类内里我们终于看到了KeyOfT这个布局体,这个布局体内里我们就计划了一个括号重载,这就是我们的仿函数的用法,你可以看到它返回的是我们的键值,接下来我们对红黑树的两个迭代器范例再次举行重定名,如许我们在测试主函数内里利用的时间格式就跟利用标准库的时间是千篇一律的。接下来还是我们的两组begin,end,我们直接调用红黑树的两组begin和end就可以了,接下来是insert插入,我们还是调用红黑树的插入,find也是云云,末了是我们的方括号重载。
要弄懂方括号重载我们得先知道方括号的作用,方括号我们一样寻常都是把想要让步调员看到的数据给取出来,在map中我们想看到的大多数时间都不是key值,而是value值,value值就在我们的pair布局体中的second变量里;或则说这个地区的数据是空的,那么我们还可以把数据添加进去。因此,我们的方括号重载也是如许去计划的,先添加(假如已经存在了它就会添加失败,直接把原来的数据赋值给ret,不清晰的话,各人可以看红黑树插入的代码就会清晰了),然后我们再返回second数据就可以了。
这就是我们map的简朴封装,我们再来总结一点,封装封装,都是封装给别的人看的,目标是让其在利用的时间可以或许更加规范,更加简朴,以是你会发现,我们这里举行封装的时间,他们的接口名称,返回值范例险些都是一样的。
- //利用【】简单计数
- void test_map2()
- {
- string arr[] = { "字符串", "数组", "指针", "列表", "字典", "字典", "字典",
- "字符串", "字符串", "字符串", "数组","数组","数组", "数组","数组" };
- map<string, int> countMap;
- for (auto& e : arr)
- {
- countMap[e]++;
- }
- for (auto& kv : countMap)
- {
- cout << kv.first << ":" << kv.second << endl;
- }
- cout << endl;
- map<string, int>::iterator it = countMap.find("字符串");
- cout << (*it).first << endl;
- }
复制代码 上述代码就是我们简朴的一个计数测试代码,都是根本的语法,而且各人会发现我们的利用跟标准库是千篇一律的。给各人看看运行效果。
由于我们的计数是重零开始的以是它会比我们预想的少一个,但是假如必要的话我们可以自行将他们添加上去。
4.set的模拟实现
set的底层为红黑树,因此只需在set内部封装一棵红黑树,即可将该容器实现出来(具体实现可参考map)。
- #pragma once
- #include"_RBtree.h"
- namespace Mybit
- {
- template<class K>
- class set
- {
- struct KeyofT
- {
- const K& operator()(const K& key)
- {
- return key;
- }
- };
- public:
- typedef typename RBTree<K, const K, KeyofT>::Iterator iterator;
- typedef typename RBTree<K,const K, KeyofT>::ConstIterator const_iterator;
- iterator begin()
- {
- return _t.begin();
- }
- iterator end()
- {
- return _t.end();
- }
- const_iterator begin() const
- {
- return _t.begin();
- }
- const_iterator end() const
- {
- return _t.end();
- }
- pair<iterator,bool> insert(const K& key)
- {
- return _t.Insert(key);
- }
- iterator find(const K& key)
- {
- return _t.Find(key);
- }
- private:
- RBTree<K,const K,KeyofT> _t;
- };
复制代码 看完了map的封装再来看set的封装就会发现,封装思绪就是千篇一律的,唯一有几点改动是由于set的模子是key模子,以是它的仿函数返回有点不一样,由于它不必要我们的pair布局体,【】在此时也不是很必要了,我们直接测试看看。
- void test_set()
- {
- set<int> s;
- s.insert(2);
- s.insert(5);
- s.insert(3);
- s.insert(4);
- s.insert(1);
- set<int>::iterator it = s.begin();
- cout << *it << endl;
- while (it != s.end())
- {
- cout << *it << " ";
- ++it;
- }
- cout << endl;
- set<int> copy = s;
- for (auto e : copy)
- {
- cout << e << " ";
- }
- cout << endl;
- cout << *(s.find(1)) << endl;
- }
复制代码
总结
我们的map和set的简朴封装到这里就竣事了,我们要弄清晰红黑树的迭代器的重要功能的实现头脑,还要红黑树的一些修改,最告急的尚有map和set的封装头脑,弄清晰这些,各人在一样平常生存中对map和set的利用就会更加得心应手。
我们实现map和set的简朴封装并不是为了说再造一个跟库里的一样的,这没须要,我们只必要相识重要功能的实现原理就充足了,如许当你再看到map和set的时间就不会看它是一团迷雾了。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金 |