【C++进阶】七、使用红黑树对set和map进行封装

简介: 目录前言一、改造红黑树1.1 红黑树迭代器相关1.2 红黑树接口相关二、set代码三、map代码

目录

前言

一、改造红黑树

1.1 红黑树迭代器相关

1.2 红黑树接口相关

二、set代码

三、map代码


前言

       set 是 K模型的容器,map 是 KV模型的容器,但是它们的底层实现都是红黑树实现,即用红黑树可以封装出 set和 map,之前的篇章已经讲过红黑树了,这里就不解释了。

       接下来对红黑树进行改造,用改造后的红黑树封装出 set 和 map

一、改造红黑树

使用的代码是之前篇章红黑树实现的代码,改造后红黑树的框架如下:

#pragma onceenumColour{
RED,
BLACK,
};
template<classT>structRBTreeNode{
//构造函数RBTreeNode(constT&data)
        :_data(data)
        ,_left(nullptr)
        ,_right(nullptr)
        ,_parent(nullptr)
        ,_col(RED)
    {}
//成员变量T_data;
RBTreeNode<T>*_left;
RBTreeNode<T>*_right;
RBTreeNode<T>*_parent;
Colour_col;
};
template<classT, classRef, classPtr>struct__RBTreeIterator{
typedefRBTreeNode<T>Node;
typedef__RBTreeIterator<T, Ref, Ptr>Self;
typedef__RBTreeIterator<T, T&, T*>iterator;
//构造__RBTreeIterator(Node*node)
        :_node(node)
    {}
// 普通迭代器的时候,他是拷贝构造// const迭代器的时候,他是构造,支持用普通迭代器构造const迭代器__RBTreeIterator(constiterator&s)
        :_node(s._node)
    {}
Refoperator*()
    {
return_node->_data;
    }
Ptroperator->()
    {
return&_node->_data;
    }
booloperator!=(constSelf&s)const    {
return_node!=s._node;
    }
booloperator==(constSelf&s)const    {
return_node==s._node;
    }
Self&operator++()
    {
if (_node->_right)
        {
Node*min=_node->_right;
while (min->_left)
            {
min=min->_left;
            }
_node=min;
        }
else        {
Node*cur=_node;
Node*parent=cur->_parent;
while (parent&&cur==parent->_right)
            {
cur=cur->_parent;
parent=parent->_parent;
            }
_node=parent;
        }
return*this;
    }
Self&operator--()
    {
if (_node->_left)
        {
Node*max=_node->_left;
while (max->_right)
            {
max=max->_right;
            }
_node=max;
        }
else        {
Node*cur=_node;
Node*parent=cur->_parent;
while (parent&&cur==parent->_left)
            {
cur=cur->_parent;
parent=parent->_parent;
            }
_node=parent;
        }
return*this;
    }
//成员变量Node*_node;
};
template<classK, classT, classKeyOfT>classRBTree{
typedefRBTreeNode<T>Node;
public:
typedef__RBTreeIterator<T, T&, T*>iterator;
typedef__RBTreeIterator<T, constT&, constT*>const_iterator;
iteratorbegin()
    {
Node*left=_root;
while (left->_left)
        {
left=left->_left;
        }
returniterator(left);// 使用匿名对象构造返回    }
const_iteratorbegin()const    {
Node*left=_root;
while (left->_left)
        {
left=left->_left;
        }
returnconst_iterator(left);
    }
iteratorend()
    {
returniterator(nullptr);
    }
const_iteratorend()const    {
returnconst_iterator(nullptr);
    }
//插入pair<iterator, bool>Insert(constT&data)
    {}
//中序遍历voidInOrder()
    {
_InOrder(_root);
    }
//检查红黑树特性boolIsBalance()
    {}
private:
//检查每条路径的黑色节点是否相等 && 是否出现连续红色节点boolCheck(Node*root, intblackNum, intref)
    {}
void_InOrder(Node*root)
    {}
//左单旋voidRotateL(Node*parent)
    {}
//右单旋voidRotateR(Node*parent)
    {}
private:
Node*_root=nullptr;//缺省值};

代码过多就不贴完了,完整代码链接:

https://gitee.com/Maple_fylqh/code/blob/master/code_c++/Set_and_Map/Set_and_Map/RBTree.h

改造红黑树就是给红黑树增加迭代器相关的,这棵泛型的红黑树可以同时封装出set 和 map

1.1 红黑树迭代器相关

迭代器模板参数在 list 模拟实现已经有过解释,这里不解释了,模板参数 T 是红黑树存放数据的类型,下面解释

template<class T, class Ref, class Ptr>

       上面迭代器类只需提供构造函数即可, 拷贝构造和赋值不需要自己实现,默认生成的即可,当前迭代器赋值给另一个迭代器是不需要深拷贝的,浅拷贝就可以,拷贝构造也是;析构释放空间的事情不归迭代器类管理,迭代器类只需构建迭代器对象即可

上面红黑树迭代器实现的接口有:

//构造__RBTreeIterator(Node*node)
        :_node(node)
    {}
// 普通迭代器的时候,他是拷贝构造// const迭代器的时候,他是构造,支持用普通迭代器构造const迭代器__RBTreeIterator(constiterator&s)
        :_node(s._node)
    {}
//解引用,获取数据Refoperator*(){}
//返回数据的地址Ptroperator->(){}
//比较两个迭代器是否不相等booloperator!=(constSelf&s)const{}
//比较两个迭代器是否相等booloperator==(constSelf&s)const{}
//前置++Self&operator++(){}
//前置--Self&operator--(){}

说明一下,迭代器都是支持普通迭代器构造const迭代器

image.png

实现如下,这里它利用了构造函数和拷贝构造函数的特性,当传入的迭代器是普通迭代器时:参数类型与拷贝构造参数要求一致,它就是拷贝构造;当传入的的迭代器是 const迭代器,参数类型与拷贝构造函数不匹配,类型有差异,它就变成了构造函数

typedef__RBTreeIterator<T, T&, T*>iterator;
// 普通迭代器的时候,他是拷贝构造// const迭代器的时候,他是构造,支持用普通迭代器构造const迭代器__RBTreeIterator(constiterator&s)
    :_node(s._node)
{}

前置++ 和前置-- 是红黑树迭代器实现的重点

前置++

一个结点的正向迭代器进行 ++ 操作后,应该根据红黑树中序遍历的序列找到当前结点的下一个结点

如下图,传入第一个节点是1,迭代器++ 下一个节点是6,再次++ 节点为8,迭代器要达到这种效果才可以满足需求

image.png

思路:

  1. 传入一个节点,如果节点右不为空就先往右边走,找到右边节点最小的节点,该节点就是 ++ 的下一个节点
  2. 传入一个节点,如果节点的右子树为空,去找祖先,迭代依次往上走,找到孩子是父亲左的那个祖先节点 
  3. image.png

代码如下:

Self&operator++()
{
if (_node->_right)//传入节点右不为空,找右边最小节点    {
Node*min=_node->_right;
while (min->_left)
        {
min=min->_left;
        }
_node=min;
    }
else)//传入节点为空    {
Node*cur=_node;
Node*parent=cur->_parent;
while (parent&&cur==parent->_right)
        {
cur=cur->_parent;
parent=parent->_parent;
        }
_node=parent;
    }
return*this;
}

前置--

一个结点的迭代器进行 -- 操作后,就是红黑树中序遍历找到的结点的前一个结点

如下图,比如传入第一个节点是27,迭代器-- 下一个节点是25,再次 -- 节点为22,迭代器要达到这种效果才可以满足需求

image.png

--的思路和 ++ 的思路类似,只不过是反过来找

思路:

  1. 传入一个节点,如果节点左不为空就先往左边走,找到左边节点最大的节点,该节点就是 -- 的下一个节点
  2. 传入一个节点,如果节点的左子树为空,去找祖先,迭代依次往上走,找到孩子是父亲右的那个祖先节点
  3. image.png

代码如下:

Self&operator--()
{
if (_node->_left)//传入节点左不为空就先往左边走,    {
Node*max=_node->_left;
while (max->_right)
        {
max=max->_right;
        }
_node=max;
    }
else//传入节点左为空    {
Node*cur=_node;
Node*parent=cur->_parent;
while (parent&&cur==parent->_left)
        {
cur=cur->_parent;
parent=parent->_parent;
        }
_node=parent;
    }
return*this;
}

其他就不介绍了

1.2 红黑树接口相关

template<classK, classT, classKeyOfT>classRBTree{
typedefRBTreeNode<T>Node;
public:
typedef__RBTreeIterator<T, T&, T*>iterator;
typedef__RBTreeIterator<T, constT&, constT*>const_iterator;
private:
Node*_root=nullptr;//缺省值};

说明一下模板参数,为了实现泛型RBTree:

  1. 红黑树第一个参数是 K(Key),主要用于查找数据;
  2. 红黑树第二个模板参数 T ,可以识别是 map 还是 set,因为 set和 map传给 T的参数不一样,set 传的是 Key,map 传的是键值对 pair;
  3. 红黑树第三个参数是 KeyOfT,这是一个仿函数,由于红黑树中 T 存的可能是K,也可能是pair。那我们插入的时候该怎么比较结点的大小呢,对于set是没有问题的,直接可以用T比较,但是map就不行了,我们要取出pair的 first来进行比较
  4. image.png

二、set代码

#include "RBTree.h"namespacefy{
template<classK>classset    {
structSetKeyOfT        {
constK&operator()(constK&key)
            {
returnkey;
            }
        };
public:
typedeftypenameRBTree<K, K, SetKeyOfT>::const_iteratoriterator;
typedeftypenameRBTree<K, K, SetKeyOfT>::const_iteratorconst_iterator;
iteratorbegin()const        {
return_t.begin();
        }
iteratorend()const        {
return_t.end();
        }
pair<iterator, bool>insert(constK&key)
        {
pair<typenameRBTree<K, K, SetKeyOfT>::iterator, bool>ret=_t.Insert(key);
returnpair<iterator, bool>(ret.first, ret.second);
        }
private:
RBTree<K, K, SetKeyOfT>_t;
    };
voidTestSet()
    {
intarr[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
set<int>s;
for (auto&e : arr)
        {
s.insert(e);
        }
set<int>::iteratorit=s.begin();
while (it!=s.end())
        {
cout<<*it<<" ";
++it;
        }
cout<<endl;
for (auto&e : s)
        {
cout<<e<<" ";
        }
cout<<endl;
    }
}

三、map代码

#include "RBTree.h"namespacefy{
template<classK, classV>classmap    {
structMapKeyOfT        {
constK&operator()(constpair<constK, V>&kv)
            {
returnkv.first;
            }
        };
public:
typedeftypenameRBTree<constK, pair<constK, V>, MapKeyOfT>::iteratoriterator;
typedeftypenameRBTree<constK, pair<constK, V>, MapKeyOfT>::const_iteratorconst_iterator;
iteratorbegin()
        {
return_t.begin();
        }
iteratorend()
        {
return_t.end();
        }
const_iteratorbegin()const        {
return_t.begin();
        }
const_iteratorend()const        {
return_t.end();
        }
pair<iterator, bool>insert(constpair<constK, V>&kv)
        {
return_t.Insert(kv);
        }
V&operator[](constK&key)
        {
pair<iterator, bool>ret=insert(make_pair(key, V()));
returnret.first->second;
        }
private:
RBTree<K, pair<constK, V>, MapKeyOfT>_t;
    };
voidTestMap()
    {
intarr[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
map<int, int>m;
for (auto&e : arr)
        {
m.insert(make_pair(e, e));
        }
map<int, int>::iteratorit=m.begin();
while(it!=m.end())
        {
it->second++;
cout<<it->first<<"->"<<it->second<<endl;
++it;
        }
map<string, int>countMap;
stringstr[] = { "苹果", "西瓜", "香蕉", "草莓", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
for (auto&e : str)
        {
countMap[e]++;
        }
for (auto&kv : countMap)
        {
cout<<kv.first<<":"<<kv.second<<endl;
        }
    }
}

这篇文章写起来逻辑有点不通(难写)dog,主要是用来复习的

----------------我是分割线---------------

文章到这里就结束了,下一篇即将更新

相关文章
|
1月前
|
存储 JavaScript 前端开发
Set、Map、WeakSet 和 WeakMap 的区别
在 JavaScript 中,Set 和 Map 用于存储唯一值和键值对,支持多种操作方法,如添加、删除和检查元素。WeakSet 和 WeakMap 则存储弱引用的对象,有助于防止内存泄漏,适合特定场景使用。
|
2月前
|
存储 Java API
【数据结构】map&set详解
本文详细介绍了Java集合框架中的Set系列和Map系列集合。Set系列包括HashSet(哈希表实现,无序且元素唯一)、LinkedHashSet(保持插入顺序的HashSet)、TreeSet(红黑树实现,自动排序)。Map系列为双列集合,键值一一对应,键不可重复,值可重复。文章还介绍了HashMap、LinkedHashMap、TreeMap的具体实现与应用场景,并提供了面试题示例,如随机链表复制、宝石与石头、前K个高频单词等问题的解决方案。
37 6
【数据结构】map&set详解
|
1月前
|
存储 缓存 Java
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
37 1
|
2月前
|
算法
你对Collection中Set、List、Map理解?
你对Collection中Set、List、Map理解?
36 5
|
2月前
|
存储 JavaScript 前端开发
js的map和set |21
js的map和set |21
|
2月前
|
存储 前端开发 API
ES6的Set和Map你都知道吗?一文了解集合和字典在前端中的应用
该文章详细介绍了ES6中Set和Map数据结构的特性和使用方法,并探讨了它们在前端开发中的具体应用,包括如何利用这些数据结构来解决常见的编程问题。
ES6的Set和Map你都知道吗?一文了解集合和字典在前端中的应用
|
3月前
|
存储 安全 Java
java集合框架复习----(4)Map、List、set
这篇文章是Java集合框架的复习总结,重点介绍了Map集合的特点和HashMap的使用,以及Collections工具类的使用示例,同时回顾了List、Set和Map集合的概念和特点,以及Collection工具类的作用。
java集合框架复习----(4)Map、List、set
|
2月前
|
数据安全/隐私保护 C语言 C++
C++(七)封装
本文档详细介绍了C++封装的概念及其应用。封装通过权限控制对外提供接口并隐藏内部数据,增强代码的安全性和可维护性。文档首先解释了`class`中的权限修饰符(`public`、`private`、`protected`)的作用,并通过示例展示了如何使用封装实现栈结构。接着介绍了构造器和析构器的使用方法,包括初始化列表的引入以及它们在内存管理和对象生命周期中的重要性。最后,通过分文件编程的方式展示了如何将类定义和实现分离,提高代码的模块化和复用性。
|
3月前
|
Java
【Java集合类面试二十二】、Map和Set有什么区别?
该CSDN博客文章讨论了Map和Set的区别,但提供的内容摘要并未直接解释这两种集合类型的差异。通常,Map是一种键值对集合,提供通过键快速检索值的能力,而Set是一个不允许重复元素的集合。
|
3月前
|
存储 Java 索引
下一篇
无影云桌面