set和map(三)

简介: set和map

迭代器的设计


红黑树的迭代器属于双向迭代器,各种操作都类似于list,较为特殊的就是++和--,这两个操作完全依据红黑树的节点的排列法则


先处理++,根据红黑树的中序遍历:左子树-根-右子树

如果右子树不为空,可直接到右子树中寻找键值最小的节点便是根节点++之后的节点


a3d63d7329c0ad131b5f0318d9aa8acd_6909ad99e0f04ed580ed140199729639.png


如果右子树不存在,并且此时节点还是父节点的右节点,向上找到的祖先便是++之后的节点;如果此时节点是父节点的左节点,那么父节点便是++之后的节点


4b966bd688b7a42318b141eb3308190d_edef4da09d304e6aa66b98e4ae1cee4d.png


--操作于此相同


这里还需要注意一点的是红黑树的迭代器中存在着构造函数

当普通迭代器调用时,权限缩小,此时是拷贝构造;当const迭代器调用时,此时是构造函数


_RBTreeIterator(const iterator& s)
  :_node(s._node)
{}


迭代器的完整代码如下


template<class T>
struct _RBTreeIterator
{
  typedef RBTreenode<T> node;
  typedef _RBTreeIterator<T, Ref, Ptr> Self;
  typedef _RBTreeIterator<T, T&, T*> iterator;
  node* _node;
  _RBTreeIterator(node* node)
  :_node(node)
  {}
  _RBTreeIterator(const iterator& s)
  :_node(s._node)
  {}
  Ref operator*()
  {
  return _node->_data;
  }
  Ptr operator->()
  {
  return &_node->_data;
  }
  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;
  }
  bool operator!=(const Self& s) const
  {
  return _node != s._node;
  }
  bool operator==(const Self& s) const
  {
  return _node == s._node;
  }
};


结构


红黑树本质是二叉搜索树,每个节点存储的数据是键值对pair<key,value>,键值key唯一标识节点不可修改,实质value保存信息可以修改;


1979588bde5e244bb4eba4db6ce468e4_de3c5f17c7f04758a7a174dff627ecfd.png


STL规定,begin()和end()代表一段前闭后开的区间,对红黑树进行中序遍历之后,可以得到有序序列,因此:begin()可以放在红黑树中最小节点的位置,end()可以放在最大节点的下一个位置(这里直接设置为空指针)


begin()


iterator begin()
{
  node* left = _root;
  while (left && left->_left)
  {
  left = left->_left;
  }
  return iterator(left);
}


end()



         


插入

插入操作的返回值是键值对pair


pair<iterator,bool> insert (const value_type& val);


红黑树是在二叉树搜索树的基础上加上其平衡条件,因此红黑树的插入可分两步:


按照二叉搜索树的规则插入新节点


检测新节点插入之后,红黑树的性质是否被破坏

新增节点必须是红色的,如果双亲结点的颜色是黑色,则没有违反红黑树规则,不需要调整;如果双亲结点是红色,此时就违反了双亲结点的规则3不能有连续的红色节点,需要进行调整;根据叔叔节点的情况,调整的情景也分为3种:

约定:新增节点为cur,父节点为p,祖父节点为g,叔叔节点为u


cur为红,p为红,g为黑,u存在且为红


c3c01795ac1d1d10de65508257c71327_4101468440f948e9b177c27d5fe2dac1.png


调整操作:将父节点 p和叔叔节点 u改为黑色,祖父节点 g改为红色;将祖父节点 u作为新增节点,继续向上调整


cur为红,p为红,g为黑,u不存在/存在且为黑


db47f0c6faf0eba11fcdbe2b71d1ce3a_6f01e7134bc5421f8143ae218679e4c0.png


调整操作:如果父节点 p是祖父节点 g的左节点,新增节点cur是父节点p的左节点,进行左单旋;相反,父节点 p是祖父节点 g的右节点,新增节点cur是父节点p的右节点,进行左单旋


以父节点 p为轴点进行左单旋;将父节点 p改为黑色,祖父节点 g改为红色;父节点 p作为新增节点继续向上调整


cur为红,p为红,g为黑,u不存在/存在且为黑


f4c721fad3c404ee02ecba62b6a79dd9_02e7e77ba41f4023be904207b6bd9d06.png


调整操作:如果父节点p是祖父节点g的左节点,新增节点cur是父节点p的右节点,则以父节点p为轴心进行左单旋;如果父节点p是祖父节点g的右节点,新增节点cur是父节点p的左节点,则以父节点p为轴心进行右单旋

以父节点p为轴心进行左单旋,转换成情景二进行调整


pair<iterator, bool>insert(const pair<K, V>& kv)
{
  if (_root == nullptr)
  {
  _root = new node(data);
  _root->_col = BLACK;
  return make_pair(iterator(_root), true);
  }
  node* parent = nullptr;
  node*newnode=node(cur);
  node* cur = _root;
  while (cur)
  {
  if (cur->_kv.first < kv.first)
  {
    parent = cur;
    cur = cur->_right;
  }
  else if (cur->_kv.first > kv.first)
  {
    parent = cur;
    cur = cur->_left;
  }
  else
  {
    return make_pair(iterator(cur),false);
  }
  }
  cur = new node(kv);
  node* newnode = cur;
  cur->_col = RED;
  if (parent->_kv.first < kv.first)
  {
  parent->_right = cur;
  cur->_parent = parent;
  }
  else
  {
  parent->_left = cur;
  cur->_parent = parent;
  }
  while (parent && parent->_col == RED)
  {
  node* grandfather = parent->_parent;
  if (parent == grandfather->_left)
  {
    node* uncle = grandfather->_right;
    //情景1,叔叔存在且为红
    if (uncle && uncle->_col == RED)
    {
    parent->_col == uncle->_col = BLACK;
    grandfather->_col = RED;
    cur = grandfather;
    parent = cur->_parent;
    }
    else
    {
    if (cur == parent->_left)
    {
      //情景2
      RotateR(grandfather);
      parent->_col = BLACK;
      grandfather->_col = RED;
    }
    else
    {
      //情景3
      RotateL(parent);
      RotateR(grandfather);
      cur->_col = BLACK;
      grandfather->_col = RED;
    }
    break;
    }
  }
  else
  {
    node* uncle = grandfather->_left;
    if (uncle && uncle->_col == RED)
    {
    parent->_col = uncle->_col = BLACK;
    grandfather->_col = RED;
    cur = grandfather;
    parent = cur->_parent;
    }
    else
    {
    if (cur == parent->_right)
    {
      RotateL(grandfather);
      parent->_col = BLACK;
      grandfather->_col = RED;
    }
    else
    {
      RotateR(parent);
      RotateL(grandfather);
      cur->_col = BLACK;
      grandfather->_col = RED;
    }
    break;
    }
  }
  }
  _root->_col = BLACK;
  return make_pair(iterator(newnode), true);
}


红黑树模拟实现setmap


通过红黑树来模拟实现这两种容器,首先要解决的一个问题就是:set的节点中存储的数据是key(也是value),而map中存储的是键值对pair<key,value>;所以在红黑树中先要修改节点中存储数据的类型,使得当模板根据不同的类型,实现不同的容器


ce99b551741729dd6978765f02ddad76_e7e15afaf4d94289b9d8a5f016b50864.png


节点改造如下


enum Color
{
  RED,
  BLACK,
};
template<class T>
struct RBTreenode
{
  T _data;//数据类型
  RBTreenode<T>* _left;
  RBTreenode<T>* _right;
  RBTreenode<T>* _parent;
  Color _col;
  RBTreenode(const T& data)
  :_data(data)
  ,_left(nullptr)
  ,_right(nullptr)
  ,_parent(nullptr)
  ,_col(RED)
  {}
};


节点修改之后,还会出现一个问题,在进行插入操作时,会将待插入节点的键值与根节点的键值进行比较,但是由于此时节点中的数据类型是data,如果是模拟实现set那么不会出现任何问题;如果是模拟实现map,编译器也不清楚data是表示键值还是实值,而且键值对pair中的比较大小的函数,并不是只比较键值大小


3ed19cb456fadc0f86aafae798eab33b_d29259813bdb47da96ff6395165765b8.png


所以这时便需要想办法将键值对中的键值取出来;解决办法:在红黑树模板中加上一个模板参数KeyofT,创建仿函数;当实现set是data的是键值也是实值,当实现map时取出键值对pair中的键值即可


改造红黑树如下


//map->RBTree<K,pair<const K,V>,MapKeyofT> _t
//set->RBTree<K,K,SetKeyofT> _t
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*>const_iterator;
  ~RBTree()
  {
  ......
  }
  iterator begin()
  {
  node* left = _root;
  while (left && left->_left)
  {
    left = left->_left;
  }
  return iterator(left);
  }
  iterator end()
  {
  return iterator(nullptr);
  }
  const_iterator begin() const
  {
  node* left = _root;
  while (left && left->_left)
  {
    left = left->left;
  }
  return const_iterator(left);
  }
  const_iterator end() const
  {
  return const_iterator(nullptr);
  }
  pair<iterator, bool>insert(const T& data)
  {
  ......
  return make_pair(iterator(newnode), true);
  }
private:
  node* _node = nullptr;
};


模拟实现map


template<class K,class V>
class map
{
    //获取键值
  struct MapkeyofT
  {
  const K& operator()(const pair<const K, V>& kv)
  {
    return kv.first;
  }
  };
public:
  typedef typename RBTree<K, pair<const K, V>, MapkeyofT>::iterator iterator;
  typedef typename RBTree<K, pair<const K, V>, MapkeyofT>::const_iterator const_iterator;
  iterator begin()
  {
  return _t.begin();
  }
  iterator end()
  {
  return _t.end();
  }
  const_iterator begin()
  {
  return _t.begin();
  }
  const_iterator end()
  {
  return _t.end();
  }
  pair<iterator, bool>insert(const pair<const K, V>& kv)
  {
  return _t.insert(kv);
  }
  V& operator[](const K& key)
  {
  pair<iterator, bool>ret = insert(make_pair(key, V()));
  return ret.first->second;
  }
private:
  RBTree<K, pair<const T, V>, MapkeyofT> _t;
};


模拟实现set


template<class K>
class set
{
  struct SetkeyofT
  {
  const K& operator()(const K& key)
  {
    return key;
  }
  };
public:
  typedef typename RBTree<K, K, SetkeyofT>::const_iterator iterator;
  typedef typename RBTree<K, K, SetkeyofT>::const_iterator const_iterator;
  iterator begin() const
  {
  return _t.begin();
  }
  iterator end() const
  {
  return _t.end();
  }
  pair<iterator, bool>insert(const K& key)
  {
  pair<typename RBTree<K, K, SetkeyofT>::iterator, bool>ret = _t.insert();
  return pair<iterator, bool>(ret.first, ret.second);
  }
private:
  RBTree<K, K, SetkeyofT> _t;
};

目录
相关文章
|
6天前
|
算法
你对Collection中Set、List、Map理解?
你对Collection中Set、List、Map理解?
38 18
你对Collection中Set、List、Map理解?
|
2月前
|
存储 JavaScript 前端开发
Set、Map、WeakSet 和 WeakMap 的区别
在 JavaScript 中,Set 和 Map 用于存储唯一值和键值对,支持多种操作方法,如添加、删除和检查元素。WeakSet 和 WeakMap 则存储弱引用的对象,有助于防止内存泄漏,适合特定场景使用。
|
3月前
|
存储 Java API
【数据结构】map&set详解
本文详细介绍了Java集合框架中的Set系列和Map系列集合。Set系列包括HashSet(哈希表实现,无序且元素唯一)、LinkedHashSet(保持插入顺序的HashSet)、TreeSet(红黑树实现,自动排序)。Map系列为双列集合,键值一一对应,键不可重复,值可重复。文章还介绍了HashMap、LinkedHashMap、TreeMap的具体实现与应用场景,并提供了面试题示例,如随机链表复制、宝石与石头、前K个高频单词等问题的解决方案。
42 6
【数据结构】map&set详解
|
2月前
|
存储 缓存 Java
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
41 1
|
3月前
|
算法
你对Collection中Set、List、Map理解?
你对Collection中Set、List、Map理解?
39 5
|
3月前
|
存储 JavaScript 前端开发
js的map和set |21
js的map和set |21
|
3月前
|
存储 前端开发 API
ES6的Set和Map你都知道吗?一文了解集合和字典在前端中的应用
该文章详细介绍了ES6中Set和Map数据结构的特性和使用方法,并探讨了它们在前端开发中的具体应用,包括如何利用这些数据结构来解决常见的编程问题。
ES6的Set和Map你都知道吗?一文了解集合和字典在前端中的应用
|
4月前
|
存储 安全 Java
java集合框架复习----(4)Map、List、set
这篇文章是Java集合框架的复习总结,重点介绍了Map集合的特点和HashMap的使用,以及Collections工具类的使用示例,同时回顾了List、Set和Map集合的概念和特点,以及Collection工具类的作用。
java集合框架复习----(4)Map、List、set
|
4月前
|
Java
【Java集合类面试二十二】、Map和Set有什么区别?
该CSDN博客文章讨论了Map和Set的区别,但提供的内容摘要并未直接解释这两种集合类型的差异。通常,Map是一种键值对集合,提供通过键快速检索值的能力,而Set是一个不允许重复元素的集合。
|
4月前
|
存储 JavaScript 前端开发
ES6新特性(四): Set 和 Map
ES6新特性(四): Set 和 Map