C++之STLmap和set的模拟实现

Posted 小赵小赵福星高照~

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++之STLmap和set的模拟实现相关的知识,希望对你有一定的参考价值。

模拟实现map和set

文章目录


我们首先来看一下源码当中的set和map的结构:

template <class Key,class Compare = less<Key>, class Alloc = alloc>
class set 
public:
    typedef Key key_type;
    typedef Key value_type;
    typedef rb_tree<key_type,value_type,identity<value_type>,key_compare,Alloc> rep_type;
    rep_type t; //red-black tree representing set
;
template <class Key,class T, class Compare = less<Key>, class Alloc = alloc>
class map 
public:
    typedef Key key_type;
    typedef pair<const Key,T> value_type;
    typedef rb_tree<key_type,value_type,
                    select1st<value_type>,key_compare,Alloc> rep_type;
	rep_type t; //red-black tree representing mapI
;
typedef Key value_type;
typedef rb_tree<key_type,value_type,identity<value_type>,key_compare,Alloc> rep_type
//set的红黑树模板参数
    
typedef pair<const Key,T> value_type;
typedef rb_tree<key_type,value_type,select1st<value_type>,key_compare,Alloc> rep_type;
//map的红黑树模板参数

可以看到set当中的红黑树key_type,value_type这两个模板参数,key_type是Key,而且value_type也被typedef成Key,两个Key是什么意思呢?而map当中的红黑树key_type,value_type这两个模板参数,key_type是Key,而且value_type被typedef成pair<const Key,T>

源码中红黑树的结构

template <class Key,class Value, class KeyOfValue,class Compare,class Alloc = allpc>
class rb_tree 

protected:
	typedef _rb_tree_node<Value> rb_tree_node;
	typedef rb_tree_node* link_type;
protected:
	link_type header;
;
template <class Value>
struct _rb_tree_node : public _rb_tree_node_base

	typedef _rb_tree_node<Value>* link_type;
	Value value_field;
;

可以看到红黑树的模板参数中的第二个参数是Value,通过这个参数就可以实现上层set给红黑树传Key,Value就是Key,上层map给红黑树pair<const K,V>,Value就是pair<const K,V>,红黑树中的数据值Value value_field就能够灵活变换。

template<class K>
class set

public:
    //...
private:
    RBTree<K,K> _t;
;
template<class K,class V>
class map

public:
    //...
private:
    RBTree<K,pair<K,V>> _t;
;

我们发现set底层的红黑树模板参数第一个和第二个一样,那么能不能不要第一个模板参数呢?

不能,对于set省略掉没有问题,因为set传入红黑树的参数一和二是相同的,但是map就不行,因为map如果只传入第二个参数pair,map提供的接口是有些需要Key值的,比如find和erase。

我们将底层红黑树的模板参数写成:

template<class K,class T>
class RBTree;

T来接受map和set传进来了第二个参数,他可能是key也可能是pair

相应的红黑树节点的数据类型我们也需要改成T:

enum Colour

    BLACK,
    RED
;
template<classT>
struct RBTreeNode

    RBTreeNode<T>* _left;
    RBTreeNode<T>* _right;
    RBTreeNode<T>* _parent;
    
    Colour _col;//颜色
    
    T _data;//键值对
    
    //构造函数
    RBTreeNode(const T& data)
        :_left(nullptr),
        :_right(nullptr),
        :_parent(nullptr),
        :col(RED),
        :_data(data)
	
;

T传key是key,传pair是pair

模板参数中的仿函数

我们前面可以看到红黑树源码当中还有一个模板参数仿函数,这个参数是用来比较键值用的,因为第二个参数是T,set传入的是key,map传入的是pair,在比较键值的时候,是set还可以直接比较,当时map时,pair是不支持比较的,所以上层容器需要给底层红黑树提供一个仿函数,用来重载()获取key值:

template<class K, class V>
class map

	//仿函数
	struct MapKeyOfT
	
		const K& operator()(const pair<K, V>& kv) //返回键值对当中的键值Key
		
			return kv.first;
		
	;
public:
private:
	RBTree<K, pair<K, V>, MapKeyOfT> _t;
;

set也需要写仿函数,传进去,因为底层红黑树没办法知道传进去的是set还是map,所以当需要键值进行比较大小时,红黑树都会根据仿函数来进行比较

set的仿函数:

template<class K>
class set

	//仿函数
	struct SetKeyOfT
	
		const K& operator()(const K& key) //返回键值Key
		
			return key;
		
	;
public:
private:
	RBTree<K, pair<K, V>, SetKeyOfT> _t;
;

增加了一个参数仿函数后,红黑树的参数也发生了变化:

template<class K,class T,class KeyOfT>
class RBTree;

相应的之前实现的红黑树的一些有比较键值的地方就需要改变,比如查找函数和插入函数

红黑树的迭代器的模拟实现

红黑树的迭代器的实现其实就是对节点的指针进行了封装

迭代器的基本结构

template<class T,class Ref,class Ptr>
struct RBTreeIterator

	typedef RBTreeNode<T> Node;
    typedef RBTreeIterator<T,Ref,Ptr> Self;//自身类型的typedef
    Node* _node;
    //构造函数
    RBTreeIterator(Node* node = nullptr)
        :_node(node)
    
;

红黑树的迭代器如何实现*呢?因为我们对节点的指针进行了封装,只需要返回节点的数据的引用即可

Ref operator*()

    return _node->_data;

红黑树的迭代器如何实现->呢?因为我们对节点的指针进行了封装,只需要返回节点的数据的指针即可

Ptr operator->()

    return &_node->_data;

相应的!=和==也可以写出来,直接判断两个迭代器所封装的节点是不是同一个即可:

bool operator!=(const Self& s) const

    return _node!=s._node;//指向节点的指针不相同

bool operator==(const Self& s) const

    return _node==s._node;指向节点的指针相同

那么红黑树的迭代器如何实现++呢?

一个节点进行++时,应该根据红黑树中序遍历序列找到当前节点的下一个节点,因为中序遍历是先访问左子树再访问根节点,最后访问右子树。

思路如下:

1、如果it指向节点的右子树不为空,下一个就++到右子树中序第一个节点,也就是右子树的最左节点。

2、如果it指向节点右子树为空,下一个++要访问的节点是,沿着it指向节点到根节点的路径中,遍历寻找孩子是父亲左的那个父亲节点

Self& operator++()
     
        if(_node->_right)
        
            //右子树中序第一个节点,也就是右子树的最左节点
            Node* subLeft = _node->_right;
            while(subLeft->_left)
            
                subLeft = subLeft->_left;
            
            _node = subLeft;
        
        else
        
            //右为空,当前子树已经访问完了,要去找祖先访问,沿着到根节点的路径往上走,
            //找孩子是父亲左的那个父亲节点
            Node* cur = _node;
            Node* parent = cur->_parent;
            while(parent && parent->_right == cur)
            
                cur = parent;
                parent = parent->_parent;
            
            _node = parent;
        
        return *this;
    

那么红黑树的迭代器如何实现–呢?和++的思路反一下就好了:

1、如果当前节点的左子树不为空,则–操作后要找到其左子树当中的最右节点

2、如果当前节点的左子树为空,则–操作后应该在该节点到根节点的路径中找到孩子在父亲右的祖先

Self operator--()

	if (_node->_left) 
	
         //左子树中序第一个节点,也就是左子树的最右节点
		//寻找该结点左子树当中的最右结点
		Node* right = _node->_left;
		while (right->_right)
		
			right = right->_right;
		
		_node = right; 
	
	else //结点的左子树为空
	
		//寻找孩子在父亲右的祖先
		Node* cur = _node;
		Node* parent = cur->_parent;
		while (parent&&cur == parent->_left)
		
			cur = parent;
			parent = parent->_parent;
		
		_node = parent; 
	
	return *this;


最后我们需要在红黑树里面实现begin和end:

typedef RBTreeIterator<T,T&,T*> iterator;
typedef RBTreeIterator<T,const T&,const T*> const_iterator; 
iterator begin()

    Node* left = _root;
    while(left && left->_left)
    
        left = left->_left;
    
    return iterator(left);//返回最左节点的迭代器

iterator end()

    return iterator(nullptr);

我们实现的迭代器和STL当中的是有差别的,我们对end()–理应返回最后一个节点的迭代器,但是上面的实现无法实现此操作,在STL库当中实现红黑树时,他在根节点增加了头节点,它的左指针指向红黑树的最左节点,右指针指向红黑树当中的最右节点,父亲指针指向红黑树的根节点。通过一些逻辑的控制就可以实现上面的操作

红黑树的迭代器的整体代码

template<class T,class Ref,class Ptr>
struct RBTreeIterator

	typedef RBTreeNode<T> Node;
    typedef RBTreeIterator<T,Ref,Ptr> Self;
    Node* _node;
    RBTreeIterator(Node* node = nullptr)
        :_node(node)
    
    Ref operator*()
    
        return _node->data;
    
    Ptr operator->()
    
        return &_node->_data;
    
    bool operator!=(const Self& s) const
    
        return _node!=s._node;
    
    bool operator==(const Self& s) const
    
        return _node==s._node;
    
    Self& operator++()
     
        if(_node->_right)
        
            //右子树中序第一个节点,也就是右子树的最左节点
            Node* subLeft = _node->_right;
            while(subLeft->_left)
            
                subLeft = subLeft->_left;
            
            _node = subLeft;
        
        else
        
            //右为空,当前子树已经访问完了,要去找祖先访问,沿着到根节点的路径往上走,
            //找孩子是父亲左的那个父亲节点
            Node* cur = _node;
            Node* parent = cur->_parent;
            while(parent && parent->_right == cur)
            
                cur = parent;
                parent = parent->_parent;
            
            _node = parent;
        
        return *this;
    
    Self operator--()
    
        if (_node->_left) 
        
             //左子树中序第一个节点,也就是左子树的最右节点
            //寻找该结点左子树当中的最右结点
            Node* right = _node->_left;
            while (right->_right)
            
                right = right->_right;
            
            _node = right; 
        
        else //结点的左子树为空
        
            //寻找孩子在父亲右的祖先
            Node* cur = _node;
            Node* parent = cur->_parent;
            while (parent&&cur == parent->_left)
            
                cur = parent;
                parent = parent->_parent;
            
            _node = parent; 
        
        return *this;
    
    
#pragma once
enum Colour

    BLACK,
    RED
;
template<class T>
struct RBTreeNode

    RBTreeNode<T>* _left;
    RBTreeNode<T>* _right;
    RBTreeNode<T>* _parent;
    Colour _col;
    T _data;
    RBTreeNode(const T& data)
        :_left(nullptr),
        :_right(nullptr),
		:_parent(nullptr),
    	:col(RED),
    	:_data(data)
	
;
template<class T,class Ref,class Ptr>
struct RBTreeIterator

	typedef RBTreeNode<T> Node;
    typedef RBTreeIterator<T,Ref,Ptr> Self;
    Node* _node;
    RBTreeIterator(Node* node = nullptr)
        :_node(node)
        
    Ref operator*()
    
        return _node->data;
    
    Ptr operator->()
    
        return &_node->_data;
    
    bool operator!=(const Self& s) const
    
        return _node!=s._node;
    
    bool operator==(const Self& s) const
    
        return _node==s._node;
    
    Self& operator++()
     
        if(_node->_right)
        
            //右子树中序第一个节点,也就是右子树的最左节点
            Node* subLeft = _node->_right;
            while(subLeft->_left)
            
                subLeft = subLeft->_left;
            
            _node = subLeft;
        
        else
        
            //右为空,当前子树已经访问完了,要去找祖先访问,沿着到根节点的路径往上走,
            //找孩子是父亲左的那个父亲节点
            Node* cur = _node;
            Node* parent = cur->_parent;
            while(parent && parent->_right == cur)
            
                cur = parent;
                parent = parent->_parent;
            
            _node = parent;
        
        return *this;
    
    Self& operator--()
     
        //跟++基本是反过来
        return *this;
    
    
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;
    
    iterator begin()
    
        Node* left = _root;
        while(left && left->_left)
        
            left = left->_left;
        
        return iterator(left);
    
    iterator end()
    
        return iterator(nullptr);
    
    RBTree()
        :_root(nullptr)
    
    
    //查找函数
	iterator Find(const K& key)
	
		KeyOfT kot;
		Node* cur = _root;
		while (cur)
		
			if (key < kot(cur->_data)) //key值小于该结点的值
			
				cur = cur->_left; //在该结点的左子树当中查找
			
			else if (key > kot(cur->_data)) //key值大于该结点的值
			
				cur = cur->_right; //在该结点的右子树当中查找
			
			else //找到了目标结点
			
				return iterator(cur); //返回该结点
			
		
		return end(); //查找失败
	
    
    //插入
    pair<iterator,bool> Insert手撕STLmap和set

初识C++之AVL树

GDOI2017模拟12.9最近公共祖先

stl mapset之红黑树

C ++中的排名树

Set和Map的内部结构