map和set的模拟实现
一.红黑树的改造
在上面学习map和set的使用时我们就了解了map和set的底层都是红黑树但是我们之前实现的红黑树还不够让它作为底层所以我们需要对它进行改造。
enum Color//利用枚举
{
Red,
Black
};
template<class T>
struct RBTreeNode
{
RBTreeNode(const T& date)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _date(date)
{}
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
T _date;
Color _col;//结点颜色
};
template<class K,class T,class KeyOft>
class RBTree
{
public:
typedef RBTreeNode<T> node;
pair<iterator,bool> Insert(const T& date)
{
KeyOft kt;
//先判断树是否为空
if (_root == nullptr)
{
_root = new node(date);
return make_pair(iterator(_root),true);
}
//不为空则根据树的左小右大来寻找插入的位置
node* cur = _root;
node* parent = _root;
while (cur)
{
//大则往右
if (kt(cur->_date) < kt(date))
{
parent = cur;
cur = cur->_right;
}
//小则往左
else if (kt(cur->_date) > kt(date))
{
parent = cur;
cur = cur->_left;
}
//相同返回
else
{
return make_pair(iterator(cur),false);
}
}
//插入
cur = new node(date);
node* newnode = cur;
//判断是父亲的左孩子还是右孩子
if (kt(cur->_date) < kt(parent->_date))
{
parent->_left = cur;
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
cur->_col = Red;
while (parent && parent->_col == Red)
{
//记录爷爷结点
node* parentParent = parent->_parent;
//1.父亲结点是爷爷结点的左孩子
if (parent == parentParent->_left)
{
//记录叔叔结点
node* uncle = parentParent->_right;
//1.1cur是父亲结点的左孩子
if (cur == parent->_left)
{
//1.1.1父亲结点和叔叔结点都为红色
if (uncle && uncle->_col == Red)
{
//变色
parent->_col = uncle->_col = Black;
parentParent->_col = Red;
//向上调整
cur = parentParent;
parent = cur->_parent;
}
//1.1.2父亲结点为红色,叔叔结点不存在或者叔叔结点为黑色
else
{
//进行一个右单旋
RotateR(parentParent);
//变色
parent->_col = Black;
parentParent->_col = Red;
//不需要向上调整了
break;
}
}
//1.2cur是父亲结点的右孩子
else if (cur == parent->_right)
{
//1.2.1父亲结点和叔叔结点都为红色
if (uncle && uncle->_col == Red)
{
//变色
parent->_col = uncle->_col = Black;
parentParent->_col = Red;
//向上调整
cur = parentParent;
parent = cur->_parent;
}
//1.2.2父亲结点为红色,叔叔结点为黑色或不存在
else
{
//先对父亲结点进行一个左单旋
RotateL(parent);
//再对爷爷结点进行一个右单旋
RotateR(parentParent);
//变色
cur->_col = Black;
parentParent->_col = Red;
//不需要向上调整
break;
}
}
}
//2.父亲结点是爷爷结点的右孩子
else if (parent == parentParent->_right)
{
//记录叔叔结点
node* uncle = parentParent->_left;
//1.1cur是父亲结点的右孩子
if (cur == parent->_right)
{
//1.1.1父亲结点和叔叔结点都为红色
if (uncle && uncle->_col == Red)
{
//变色
parent->_col = uncle->_col = Black;
parentParent->_col = Red;
//向上调整
cur = parentParent;
parent = cur->_parent;
}
//1.1.2父亲结点为红色,叔叔结点不存在或者叔叔结点为黑色
else
{
//进行一个左单旋
RotateL(parentParent);
//变色
parent->_col = Black;
parentParent->_col = Red;
//不需要向上调整了
break;
}
}
//1.2cur是父亲结点的左孩子
else if (cur == parent->_left)
{
//1.2.1父亲结点和叔叔结点都为红色
if (uncle && uncle->_col == Red)
{
//变色
parent->_col = uncle->_col = Black;
parentParent->_col = Red;
//向上调整
cur = parentParent;
parent = cur->_parent;
}
//1.2.2父亲结点为红色,叔叔结点为黑色或不存在
else
{
//先对父亲结点进行一个右单旋
RotateR(parent);
//再对爷爷结点进行一个左单旋
RotateL(parentParent);
//变色
cur->_col = Black;
parentParent->_col = Red;
//不需要向上调整
break;
}
}
}
}
_root->_col = Black;
return make_pair(iterator(newnode),true);
}
//左单旋
void RotateL(node* parent)
{
KeyOft kt;
node* subR = parent->_right;
node* subRL = subR->_left;
//提前保存这棵树的父亲结点
node* parentParent = parent->_parent;
//旋转
parent->_right = subRL;
subR->_left = parent;
//判断subRL是否为空
if (subRL)
{
subRL->_parent = parent;
}
parent->_parent = subR;
//判断旋转后subR是否为整棵树的根
if (parent == _root)
{
//如果是根则将root赋值为subR
//并将subR的父亲置空
_root = subR;
subR->_parent = nullptr;
}
else
{
//如果不是则判断是其父树的左子树还是右子树
if (kt(subR->_date) < kt(parentParent->_date))
{
parentParent->_left = subR;
}
else
{
parentParent->_right = subR;
}
subR->_parent = parentParent;
}
}
//右单旋
void RotateR(node* parent)
{
KeyOft kt;
node* subL = parent->_left;
node* subLR = subL->_right;
//提前保存这棵树的父亲结点
node* parentParent = parent->_parent;
//旋转
parent->_left = subLR;
subL->_right = parent;
//判断subRL是否为空
if (subLR)
{
subLR->_parent = parent;
}
parent->_parent = subL;
//判断旋转后subL是否为整棵树的根
if (parent == _root)
{
//如果是根则将root赋值为subL
//并将subL的父亲置空
_root = subL;
subL->_parent = nullptr;
}
else
{
//如果不是则判断是其父树的左子树还是右子树
if (kt(subL->_date) < kt(parentParent->_date))
{
parentParent->_left = subL;
}
else
{
parentParent->_right = subL;
}
subL->_parent = parentParent;
}
}
private:
node* _root = nullptr;
};
1.1更改参数
在开始改造时我们可以思考一个问题,map和set的底层都是红黑树那么他们俩底层的红黑树是否为一棵红黑树?如果是又是如何区别map的key/value结构和set的key结构的呢?
答:map和set底层的红黑树肯定是同一棵但是做了一定的处理使其可以区分map和set传的参数。
在我们实现的红黑树中模板参数有两个:K和V。对应了map的key/value结构但是set其实只需要一个K即可。那么我们是否可以将这两个参数中的V改成T。
PS:只是改了个名字,用V也是可以的只是V我们就对应value,方便理解而已
这样参数T可以根据是map还是set实例化为pair<K,V>和K。这样就可以满足在同一棵树上同时实现map和set。
//template<class K,class V>
template<class T>//更改后
struct RBTreeNode
{
RBTreeNode(const T& date)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _date(date)
{}
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
T _date;//成员变量同时进行修改
Color _col;//结点颜色
};
template<class K,class T>//更改的参数
class RBTree
{
public:
typedef RBTreeNode<T> node;
bool Insert(const T& date)//所有涉及到的都需要更改
{
//先判断树是否为空
if (_root == nullptr)
{
_root = new node(date);
return make_pair(iterator(_root),true);
}
//不为空则根据树的左小右大来寻找插入的位置
node* cur = _root;
node* parent = _root;
while (cur)
{
//大则往右
if (cur->_date < date)
{
parent = cur;
cur = cur->_right;
}
//小则往左
else if (cur->_date > date)
{
parent = cur;
cur = cur->_left;
}
//相同返回
else
{
return false;
}
}
//插入
cur = new node(date);
//判断是父亲的左孩子还是右孩子
if (cur->_date < parent->_date)
{
parent->_left = cur;
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
cur->_col = Red;
//省略下面的调整过程
}
private:
node* _root = nullptr;
};
在更改了参数后我们就可以用同一棵红黑树分别实例化出map和set了。
1.2仿函数的使用
但是新的问题出现了,在插入的时候我们需要将插入节点的大小与当前节点进行比较,当实例化出set时还好解决而实例化出map时参数变为了pair<T1,T2>这该如何进行比较呢?
在查阅了pair的手册后我们发现pair也可以进行比较,它会先对比T1的大小如果相同则再对比T2的大小。可是这就和我们key/value结构发生了冲突,所以我们需要一个方式可以区分set和map并返回我们需要的参数。即仿函数
//my_set.h
#pragma once
#include "RBTree.h"
namespace ly
{
template<class K>
class set
{
public:
struct SetKeyOft//set的仿函数
{
const K& operator()(const K& k)
{
return k;//返回K
}
};
private:
RBTree<K, K, SetKeyOft> _t;
};
}
//my_map.h
#pragma once
#include "RBTree.h"
namespace ly
{
template<class K,class V>
class map
{
public:
struct MapKeyOft//map的仿函数
{
const K& operator()(const pair<K,V>& kv)
{
return kv.first;//返回K
}
};
private:
RBTree<K, pair<K, V>, MapKeyOft> _t;
};
}
template<class K,class T,class KeyOft>//第三个参数即仿函数
class RBTree
{
public:
typedef RBTreeNode<T> node;
bool Insert(const T& date)
{
KeyOft kt;//实例化仿函数
if (_root == nullptr)
{
_root = new node(date);
return make_pair(iterator(_root),true);
}
node* cur = _root;
node* parent = _root;
while (cur)
{
//大则往右
if (kt(cur->_date) < kt(date))//利用仿函数进行比较
{
parent = cur;
cur = cur->_right;
}
//小则往左
else if (kt(cur->_date) > kt(date))
{
parent = cur;
cur = cur->_left;
}
//相同返回
else
{
return false;
}
}
//插入
cur = new node(date);
node* newnode = cur;
//判断是父亲的左孩子还是右孩子
if (kt(cur->_date) < kt(parent->_date))//利用仿函数进行比较
{
parent->_left = cur;
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
cur->_col = Red;
//省略下面的调整过程,需要比较大小时都需要利用仿函数
}
private:
node* _root = nullptr;
};
二.迭代器
在对红黑树进行改造后我们就可以将迭代器实现了。
template<class T>
class _Treeiterator
{
public:
typedef RBTreeNode<T> node;
typedef _Treeiterator<T> Self;
_Treeiterator(node* node)
:_node(node)
{}
T& operator*()
{
return _node->_date;
}
T* operator->()
{
return &_node->_date;
}
bool operator!=(const Self s)
{
return _node != s._node;
}
bool operator==(const Self s)
{
return _node == s._node;
}
private:
node* _node;
};
2.1operator++和operator–
简单的迭代器实现后我们需要考虑一件事情,现在我们的容器不似list那种序列式的而是树状的。所以我们应该如何进行++和–呢?
因为容器是梳妆时我们想要按顺序打印出值就需要按中序来打印即左子树->根->右子树。
Self& operator++()
{
//右子树不为空
//找右子树的最左节点
if (_node->_right != nullptr)
{
node* cur = _node->_right;
while (cur->_left)
{
cur = cur->_left;
}
_node = cur;
}
//右子树为空
//找上面的某个节点是父节点的左孩子或父节点为空
else
{
node* cur = _node;
node* parent = cur->_parent;
while (parent && cur == parent->_right)
{
cur = parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
//--则与++相反
Self& operator--()
{
//左子树不为空
//找左子树的最右节点
if (_node->_left != nullptr)
{
node* cur = _node->_left;
while (cur->_right)
{
cur = cur->_right;
}
_node = cur;
}
//左子树为空
//找上面的某个节点是父节点的右孩子或父节点为空
else
{
node* cur = _node;
node* parent = _node->_parent;
while (parent && cur == parent->_left)
{
cur = parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
2.2迭代器封装
在将迭代器大致完成后我们就需要将其进行封装使map和set可以通过调用接口来实现功能。
//my_set.h
#pragma once
#include "RBTree.h"
namespace ly
{
template<class K>
class set
{
public:
struct SetKeyOft
{
const K& operator()(const K& k)
{
return k;
}
};
// 这个类模板还没有实例化所以编译器无法区分这个iterator是一个类型还是一个变量
//通过typename告诉编译器这是一个类型
typedef typename RBTree<K, K, SetKeyOft>::iterator iterator;
pair<iterator, bool> Insert(const K& k)
{
return _t.Insert(k);
}
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
private:
RBTree<K, K, SetKeyOft> _t;
};
}
//my_map.h
#pragma once
#include "RBTree.h"
namespace ly
{
template<class K,class V>
class map
{
public:
struct MapKeyOft
{
const K& operator()(const pair<K,V>& kv)
{
return kv.first;
}
};
typedef typename RBTree<K, pair<K, V>, MapKeyOft>::iterator iterator;
pair<iterator, bool> Insert(const pair<K, V>& kv)
{
return _t.Insert(kv);
}
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
V& operator[](const K& key)
{
pair<iterator, bool> ret = Insert(make_pair(key,V()));
return ret.first->second;
}
private:
RBTree<K, pair<K, V>, MapKeyOft> _t;
};
}
enum Color//利用枚举
{
Red,
Black
};
template<class T>
struct RBTreeNode
{
RBTreeNode(const T& date)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _date(date)
{}
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
T _date;
Color _col;//结点颜色
};
template<class T>
class _Treeiterator
{
public:
typedef RBTreeNode<T> node;
typedef _Treeiterator<T> Self;
_Treeiterator(node* node)
:_node(node)
{}
T& operator*()
{
return _node->_date;
}
T* operator->()
{
return &_node->_date;
}
Self& operator++()
{
//右子树不为空
//找右子树的最左节点
if (_node->_right != nullptr)
{
node* cur = _node->_right;
while (cur->_left)
{
cur = cur->_left;
}
_node = cur;
}
//右子树为空
//找上面的某个节点是父节点的左孩子或父节点为空
else
{
node* cur = _node;
node* parent = cur->_parent;
while (parent && cur == parent->_right)
{
cur = parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
Self& operator--()
{
//左子树不为空
//找左子树的最右节点
if (_node->_left != nullptr)
{
node* cur = _node->_left;
while (cur->_right)
{
cur = cur->_right;
}
_node = cur;
}
//左子树为空
//找上面的某个节点是父节点的右孩子或父节点为空
else
{
node* cur = _node;
node* parent = _node->_parent;
while (parent && cur == parent->_left)
{
cur = parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
bool operator!=(const Self s)
{
return _node != s._node;
}
bool operator==(const Self s)
{
return _node == s._node;
}
private:
node* _node;
};
template<class K,class T,class KeyOft>
class RBTree
{
public:
typedef RBTreeNode<T> node;
typedef _Treeiterator<T> iterator;
iterator begin()
{
node* node = _root;
while (node && node->_left)
{
node = node->_left;
}
return iterator(node);
}
iterator end()
{
return iterator(nullptr);
}
pair<iterator,bool> Insert(const T& date)
{
KeyOft kt;
//先判断树是否为空
if (_root == nullptr)
{
_root = new node(date);
return make_pair(iterator(_root),true);
}
//不为空则根据树的左小右大来寻找插入的位置
node* cur = _root;
node* parent = _root;
while (cur)
{
//大则往右
if (kt(cur->_date) < kt(date))
{
parent = cur;
cur = cur->_right;
}
//小则往左
else if (kt(cur->_date) > kt(date))
{
parent = cur;
cur = cur->_left;
}
//相同返回
else
{
return make_pair(iterator(cur),false);
}
}
//插入
cur = new node(date);
node* newnode = cur;
//判断是父亲的左孩子还是右孩子
if (kt(cur->_date) < kt(parent->_date))
{
parent->_left = cur;
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
cur->_col = Red;
//省略调整过程
_root->_col = Black;
return make_pair(iterator(newnode),true);
}
private:
node* _root = nullptr;
}
2.3const迭代器
在完成了迭代器后我们发现了两个问题。
- set的迭代器可以修改
- map中pair的第一个参数K可以修改
所以我们需要利用const迭代器来完善。
首先我们需要将迭代器进行改变使其可以变成const迭代器并在红黑树中加入const迭代器的接口函数。
template<class T,class Ref,class Ptr>
class _Treeiterator
{
public:
typedef RBTreeNode<T> node;
typedef _Treeiterator<T,Ref,Ptr> Self;
private:
node* _node;
};
template<class K,class T,class KeyOft>
class RBTree
{
public:
typedef RBTreeNode<T> node;
typedef _Treeiterator<T,T&,T*> iterator;
typedef _Treeiterator<T,const T&,const T*> const_iterator;
iterator begin()
{
node* node = _root;
while (node && node->_left)
{
node = node->_left;
}
return iterator(node);
}
iterator end()
{
return iterator(nullptr);
}
const_iterator begin()const
{
node* node = _root;
while (node && node->_left)
{
node = node->_left;
}
return iterator(node);
}
const_iterator end()const
{
return iterator(nullptr);
}
private:
node* _root = nullptr;
}
2.3.1set的const迭代器
在了解使用set时我们得知set的key是无法进行修改的,所以无论是普通迭代器还是const迭代器我们都需要将其设计成无法被修改的。
所以我们大可以将const迭代器和普通迭代器设计成一个迭代器。
#pragma once
#include "RBTree.h"
namespace ly
{
template<class K>
class set
{
public:
struct SetKeyOft
{
const K& operator()(const K& k)
{
return k;
}
};
typedef typename RBTree<K, K, SetKeyOft>::const_iterator iterator;
typedef typename RBTree<K, K, SetKeyOft>::const_iterator const_iterator;
pair<iterator, bool> Insert(const K& k)
{
return _t.Insert(k);
}
//对应其他的容器来说普通对象调用普通迭代器
//const对象调用const迭代器
//而对于set来说无论是普通对象还是const对象都无法修改
//所以都可以调用const迭代器
iterator begin()const
{
return _t.begin();
}
iterator end()const
{
return _t.end();
}
private:
RBTree<K, K, SetKeyOft> _t;
};
}
但是当我们运行代码时我们会发现一个问题。
为什么set的插入又出现问题了呢?
//pair<iterator,bool> Insert(const T& date)
pair<node*,bool> Insert(const T& date)
{
KeyOft kt;
//先判断树是否为空
if (_root == nullptr)
{
_root = new node(date);
//return make_pair(iterator(_root),true);
return make_pair(_root,true);
}
//不为空则根据树的左小右大来寻找插入的位置
node* cur = _root;
node* parent = _root;
while (cur)
{
//大则往右
if (kt(cur->_date) < kt(date))
{
parent = cur;
cur = cur->_right;
}
//小则往左
else if (kt(cur->_date) > kt(date))
{
parent = cur;
cur = cur->_left;
}
//相同返回
else
{
//return make_pair(iterator(cur),false);
return make_pair(cur,false);
}
}
//插入
cur = new node(date);
node* newnode = cur;
//判断是父亲的左孩子还是右孩子
if (kt(cur->_date) < kt(parent->_date))
{
parent->_left = cur;
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
cur->_col = Red;
while (parent && parent->_col == Red)
{
//记录爷爷结点
node* parentParent = parent->_parent;
//1.父亲结点是爷爷结点的左孩子
if (parent == parentParent->_left)
{
//记录叔叔结点
node* uncle = parentParent->_right;
//1.1cur是父亲结点的左孩子
if (cur == parent->_left)
{
//1.1.1父亲结点和叔叔结点都为红色
if (uncle && uncle->_col == Red)
{
//变色
parent->_col = uncle->_col = Black;
parentParent->_col = Red;
//向上调整
cur = parentParent;
parent = cur->_parent;
}
//1.1.2父亲结点为红色,叔叔结点不存在或者叔叔结点为黑色
else
{
//进行一个右单旋
RotateR(parentParent);
//变色
parent->_col = Black;
parentParent->_col = Red;
//不需要向上调整了
break;
}
}
//1.2cur是父亲结点的右孩子
else if (cur == parent->_right)
{
//1.2.1父亲结点和叔叔结点都为红色
if (uncle && uncle->_col == Red)
{
//变色
parent->_col = uncle->_col = Black;
parentParent->_col = Red;
//向上调整
cur = parentParent;
parent = cur->_parent;
}
//1.2.2父亲结点为红色,叔叔结点为黑色或不存在
else
{
//先对父亲结点进行一个左单旋
RotateL(parent);
//再对爷爷结点进行一个右单旋
RotateR(parentParent);
//变色
cur->_col = Black;
parentParent->_col = Red;
//不需要向上调整
break;
}
}
}
//2.父亲结点是爷爷结点的右孩子
else if (parent == parentParent->_right)
{
//记录叔叔结点
node* uncle = parentParent->_left;
//1.1cur是父亲结点的右孩子
if (cur == parent->_right)
{
//1.1.1父亲结点和叔叔结点都为红色
if (uncle && uncle->_col == Red)
{
//变色
parent->_col = uncle->_col = Black;
parentParent->_col = Red;
//向上调整
cur = parentParent;
parent = cur->_parent;
}
//1.1.2父亲结点为红色,叔叔结点不存在或者叔叔结点为黑色
else
{
//进行一个左单旋
RotateL(parentParent);
//变色
parent->_col = Black;
parentParent->_col = Red;
//不需要向上调整了
break;
}
}
//1.2cur是父亲结点的左孩子
else if (cur == parent->_left)
{
//1.2.1父亲结点和叔叔结点都为红色
if (uncle && uncle->_col == Red)
{
//变色
parent->_col = uncle->_col = Black;
parentParent->_col = Red;
//向上调整
cur = parentParent;
parent = cur->_parent;
}
//1.2.2父亲结点为红色,叔叔结点为黑色或不存在
else
{
//先对父亲结点进行一个右单旋
RotateR(parent);
//再对爷爷结点进行一个左单旋
RotateL(parentParent);
//变色
cur->_col = Black;
parentParent->_col = Red;
//不需要向上调整
break;
}
}
}
}
_root->_col = Black;
//return make_pair(iterator(newnode),true);
return make_pair(newnode,true);
}
2.3.2map的迭代器
map的迭代器就不像set一样,map只允许修改value不允许修改key。那么怎么做才能达到目的呢?
我们只需要将map传给红黑树的参数进行修改
即将RBTree<K, pair<K, V>, MapKeyOft>
改成RBTree<K, pair<const K,V>, MapKeyOft>同时加入const迭代器即可。
#pragma once
#include "RBTree.h"
namespace ly
{
template<class K,class V>
class map
{
public:
struct MapKeyOft
{
const K& operator()(const pair<K,V>& kv)
{
return kv.first;
}
};
typedef typename RBTree<K, pair<const K, V>, MapKeyOft>::iterator iterator;
typedef typename RBTree<K, pair<const K, V>, MapKeyOft>::const_iterator const_iterator;
pair<iterator, bool> Insert(const pair<K, V>& kv)
{
return _t.Insert(kv);
}
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
V& operator[](const K& key)
{
pair<iterator, bool> ret = Insert(make_pair(key,V()));
return ret.first->second;
}
private:
RBTree<K, pair<const K, V>, MapKeyOft> _t;
};
}
这样map和set就完成了不能修改的目的了
三.总结
在学习完map和set的使用以及它们的底层AVL树和红黑树后我们就又将map和set的模拟实现完成了。
在下一篇文章我们就要学习利用哈希思想从而实现的unordered_map和unordered_set了同时我们也要学习他们的模拟实现了。