既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
2、红黑树的设计
想要兼容map和set,我们依旧需要红黑树的模板有两个类型来控制和满足上层对下层的需求
- 实现代码:
template<class K, class T>
class RBTree
{
typedef RBTreeNode<T> Node;
public:
//.......
private:
Node\* _root;
};
注:这里的Node我们不想让外部直接获取使用,我们typedef在private域中
- 解释:
- 为了兼容set和map,对于第一个参数我们是用来比较的key类型,第二个参数是用来储存数据的类型
- 这里我们对红黑树第二个模板参数进行灵活传参,可能是键值key,也可能是pair<key,value>
- 对于set传入底层红黑树的模板参数就是key和key;对于map传入底层红黑树的模板参数就是key和pair<key,value>
注:对于set来说第二个参数有点多余,但是对于map来说,因为map的接口当中有些是只要求给出键值key用来比较的(如find()和erase()),如果只有一个参数传入pair<key,value>类型,但是只能得到第一个key类型的值,无法获得key的类型(不能实现模板函数)
3、取值仿函数的使用
我们在设计树节点之后达到了对于不同容器存入不同类型的效果,但是真实在实现时在插入或者删除时,我们需要要拿出节点中储存类型的数据进行比较
- 分析:
- 对于set的T本身就是键值key,直接用T进行比较即可,但是对于map的储存类型是pair<Key, Value>我们需要取出pair的first(key值)进行比较
- 这两种的都是取值比较,但是需要的行为是不一样的,由此我们还需要一个仿函数用来灵活取值比较
- 对于不同容器我们需要不同的仿函数类型,由此在红黑树的模板列表中还需要一个模板类型参数,灵活控制传入的仿函数类型
注:仿函数,就是使一个类的使用看上去像一个函数,其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了
- 红黑树框架:
template<class K, class T, class KeyOfT>
class RBTree
{
typedef RBTreeNode<T> Node;
public:
//...
private:
Node\* _root;
};
- map实现框架:
namespace cole
{
template<class K, class V>
class map
{
struct MapOfKey
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
public:
//...
private:
RBTree<K, pair<const K, V>, MapOfKey> _t;
};
}
- set实现框架:
namespace cole
{
template<class K>
class set
{
struct SetOfKey
{
const K& operator()(const K& key)
{
return key;
}
};
public:
//...
private:
RBTree<K, K, SetOfKey> _t;
};
}
- 仿函数使用示例:
Node\* Find(const K& key)
{
KeyOfT kot;
Node\* cur = _root;
while (cur)
{
if (kot(cur->_kv.first) > key)
{
cur = cur->_left;
}
else if (kot(cur->_kv.first) < key)
{
cur = cur->_right;
}
else
{
return cur;
}
}
return nullptr;
}
二、红黑树的迭代器
迭代器本质上是指针的一个封装的类,其底层就是指针;好处是可以方便遍历,是数据结构的底层实现与用户透明
对于string,vector,list等容器,其本身的结构上是比较简单的,迭代器的实现也很简单;但是对于二叉树结构的红黑树来说需要考虑很多的问题
1、begin()与end()
STL明确规定:begin()与end()代表的是一段前闭后开的区间
对红黑树进行中序遍历后,可以得到一个有序的序列,因此begin()可以放在红黑树中最小节点(即最左侧节点)的位置,end()放在最大节点(最右侧节点)的下一个位置即nullptr
- 示图:
2、operator++()与operator–()
找到begin()和end()之后,要遍历红黑树最主要的还是能找到要遍历的下一个节点
红黑树的中序遍历是有序的,也就是说:当一个结点的正向迭代器进行++操作后,应该根据红黑树中序遍历的序列找到当前结点的下一个结点
- 实现++逻辑:
- 对于中遍历到当前节点来说,该节点的左子树应该是已经被遍历过了,所以只需要考虑右子树
- 如果当前结点的右子树不为空,则++操作后应该找到其右子树当中的最左结点
- 如果当前结点的右子树为空,则该子树已经被遍历完了,则++操作后应该在该结点的祖先结点中找到孩子不在父亲右的祖先
- 说明:
- 如果是孩子节点在父亲的左,说明父亲的右节点没有遍历
- 如果孩子在父亲的右,说明以父亲节点为根的子树已经遍历完了,还需要继续向上找
- 如果一直向上遍历到nullptr说明整颗树已经遍历完了(end()返回的就是nullptr)
注:怎么看该节点的右节点遍历过没有,这里需要一个指针记录上一次经过的节点地址,进行比较地址就行了
- 实现++代码:
Self& operator++()
{
if (_node->_right)//右子节点存在
{
//找到右子树中最左节点
Node\* cur = _node->_right;
while (cur->_left)
{
cur = cur->_left;
}
_node = cur;
}
else//右子节点不存在,向上找
{
Node\* cur = _node;//记录走过的节点
Node\* parent = _node->_parent;
while (parent && parent->_right == cur)
{
cur = parent;
parent = parent->_parent;
}
_node = parent;
}
return \*this;
}
注:对于–来说可以参考++的实现,逻辑是完全相反的
- 实现–代码:
Self& operator--()
{
if (_node->_left)//左子节点存在
{
//找左子树中的最右节点
Node\* cur = _node->_left;
while (cur->_right)
{
cur = cur->_right;
}
_node = cur;
}
else//左子节点不存在
{
Node\* cur = _node;
Node\* parent = _node->_parent;
while (parent && parent->_left == cur)
{
cur = parent;
parent = parent->_parent;
}
_node = parent;
}
return \*this;
}
3、正反迭代器的实现
对于正向迭代器与反向迭代器的区别就是:begin()指向的位置不一样;迭代器的++和–的实现相反,但本质上还是差不多的
所以实现正向迭代器后,我们可以直接使用适配器,在正向迭代器的基础上,对其接口进行封装达到反向迭代器的效果
- 正向迭代器实现代码:
template<class T, class Ref, class Ptr>
struct \_TreeIterator
{
//声明类型,便于反向迭代器对类型的提取
typedef Ref reference;
typedef Ptr pointer;
typedef RBTreeNode<T> Node;
typedef _TreeIterator<T, Ref, Ptr> Self;
Node\* _node;
\_TreeIterator(Node\* node)
:\_node(node)
{}
Ref operator\*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
bool operator==(const Self& it)const
{
return _node == it._node;
}
bool operator!= (const Self& it)const
{
return _node != it._node;
}
Self& operator++()
{
if (_node->_right)
{
Node\* cur = _node->_right;
while (cur->_left)
{
cur = cur->_left;
}
_node = cur;
}
else
{
Node\* cur = _node;
Node\* parent = _node->_parent;
while (parent && parent->_right == cur)
{
cur = parent;
parent = parent->_parent;
}
_node = parent;
}
return \*this;
}
Self& operator--()
{
if (_node->_left)
{
Node\* cur = _node->_left;
while (cur->_right)
{
cur = cur->_right;
}
_node = cur;
}
else
{
Node\* cur = _node;
Node\* parent = _node->_parent;
while (parent && parent->_left == cur)
{
cur = parent;
parent = parent->_parent;
}
_node = parent;
}
return \*this;
}
};
- 反向迭代器实现代码:
//适配器构造反向迭代器
template<class Iterator>
struct ReverseIterator
{
//类型未实例化,无法取出里面的类型,此时需要使用typename:告诉编译器等实例化后再到类里面找对应的类型
typedef typename Iterator::reference Ref;
typedef typename Iterator::pointer Ptr;
typedef ReverseIterator<Iterator> Self;
Iterator _it;
ReverseIterator(Iterator it)
:\_it(it)
{}
//在正向迭代器接口上进行封装复用
Ref operator\*()
{
return \*_it;
}
Ptr operator->()
{
return _it.operator->();
}
bool operator==(const Self& it)const
{
return it._it==_it;
}
bool operator!= (const Self& it)const//两个const
{
return _it != it._it;
}
Self& operator++()
{
--_it;
return \*this;
}
Self& operator--()
{
++_it;
return \*this;
}
};
三、map和set的实现
1、红黑树的实现
- 具体实现代码:
//颜色
enum Colour
{
RED,
BLACK,
};
template<class T>
struct RBTreeNode
{
RBTreeNode<T>\* _left;
RBTreeNode<T>\* _right;
RBTreeNode<T>\* _parent;
T _data;//T可以是key也可以是pair<K,V>
Colour _col;
RBTreeNode(const T& data)
:\_left(nullptr)
, \_right(nullptr)
, \_parent(nullptr)
, \_data(data)
, \_col(RED)
{}
};
template<class K, class T, class KeyOfT>
class RBTree
{
typedef RBTreeNode<T> Node;
public:
typedef _TreeIterator<T, T&, T\*> iterator;
typedef _TreeIterator<T,const T&, const T\*> const_iterator;
typedef ReverseIterator<iterator> reverse_iterator;
typedef ReverseIterator<const_iterator> reverse_const_iterator;
RBTree()
:\_root(nullptr)
{}
~RBTree()
{
\_Destory(_root);
}
iterator begin()
{
Node\* cur = _root;
while (cur&&cur->_left)
{
cur = cur->_left;
}
return iterator(cur);
}
reverse_iterator rbegin()
{
Node\* cur = _root;
while (cur&&cur->_right)
{
cur = cur->_right;
}
return reverse\_iterator(iterator(cur));
}
reverse_iterator rend()
{
return reverse\_iterator(iterator(nullptr));
}
iterator end()
{
return iterator(nullptr);
}
Node\* Find(const K& key)
{
KeyOfT kot;
Node\* cur = _root;
while (cur)
{
if (kot(cur->_kv.first) > key)
{
cur = cur->_left;
}
else if (kot(cur->_kv.first) < key)
{
cur = cur->_right;
}
else
{
return cur;
}
}
return nullptr;
}
pair<iterator, bool> Insert(const T& data)
{
//空树的情况
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;
return make\_pair(iterator(_root), true);
}
KeyOfT kot;
//查找位置插入节点
Node\* cur = _root, \* parent = _root;
while (cur)
{
if (kot(cur->_data) > kot(data))
{
parent = cur;
cur = cur->_left;
}
else if (kot(cur->_data) < kot(data))
{
parent = cur;
cur = cur->_right;
}
else
{
return make\_pair(iterator(cur), false);
}
}
//创建链接节点
cur = new Node(data);
Node\* newnode = cur;
if (kot(parent->_data) > kot(data))
{
parent->_left = cur;
}
![img](https://img-blog.csdnimg.cn/img_convert/0706152b38a4fc95d7c808ae00dd9795.png)
![img](https://img-blog.csdnimg.cn/img_convert/e1dd5c69409466d77c244de4811827a9.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**
(kot(cur->_data) > kot(data))
{
parent = cur;
cur = cur->_left;
}
else if (kot(cur->_data) < kot(data))
{
parent = cur;
cur = cur->_right;
}
else
{
return make\_pair(iterator(cur), false);
}
}
//创建链接节点
cur = new Node(data);
Node\* newnode = cur;
if (kot(parent->_data) > kot(data))
{
parent->_left = cur;
}
[外链图片转存中...(img-qpFpsA4J-1715737061189)]
[外链图片转存中...(img-pzEjK5NH-1715737061189)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**