[Boolan] C++第八周 STL 泛型编程(二)

一. deque

双端队列,支持快速随机访问,在头尾位置插入和删除很快

08_01_deque

    像上图,一般介绍deque是右上角这样的,两端都可以push和pop,并且可以像数组一样支持随机访问,一段连续的内存
    其实他的实现并不是使用一整段连续内存来实现的,毕竟这样做,效率不高,而且扩展性不强
    实际GUN的是使用多个大小相同的内存块,而不是一个连续的内存来实现的,并且使用一个类似索引的map来管理他们,并且在已有的内存块的用尽之后,如果继续向前向后添加元素时,是以一个内存块为单位进行申请的,然后索引添加到map中相应的位置  (上图中的map就是索引,map_size就是索引的大小)
    一个deque的成员除了map,map_size外,还有两个迭代器,iterator start,iterator finish; start是指向第一个内存块首元素, finish是指向最后一个内存块尾元素的下一个元素
    deque并且之所以可以像连续内存一样使用他,主要功劳是deque的iterator进行了大量的操作符重载,也包括随机访问的operator[]

1. iterator

继续看上图中的iterator部分,一个deque的iterator是由 cur, frist, last三个元素指针,一个指向索引的node指针组成
前面说了,deque是使用索引加内存块实现的,它用来标识iterator位置的方式是 元素位于索引中那个内存块中的那个元素
node就是当前内存块的在map的索引
cur是元素在当前内存块的位置
first是当前内存块首元素的地址
last是当前内存块尾元素的下一个元素的地址

通过了解的这些信息,我们来看下如何通过iterator插入一个元素,下列是GUN2.9中,insert的主干
iterator insert(iterator position, const value_type& x)
{
    if(position.cur == start.cur) { //如果安插点是deque的最前端
        push_front(x);
        return start;
    } else if (position.cur == finish.cur) { //如果是deque的尾端
        push_back(x);
        iterator tmp = finish;  //finish是尾端的下一个元素,
        --tmp;               //所以,新插入的元素在finish前面
        return tmp;
    } else {            //如果在中间,在进行操作
        return insert_aux(positon, x);
    }
}
如果插入点是在deque的中间部分,需要考虑插入的点是距离首端近,还是距离尾端近,这样用来判断是移动待插入元素的前段的元素,还是尾端的元素,来给新插入的元素空出位置
template <class T, class Alloc, size_t BufSize>
typename deque<T, Alloc, BufSize>::iterator
deque<T, Alloc, BufSize>::insert_aux(iterator pos, const value_type& x) {
    difference_type index = pos - start;   //按插点之前的元素个数
    value_type x_copy = x;
    if (index < size() / 2) {       //如果安插点之前的元素个数少
        push_front(front());        //把首元素向前拷贝一份
        ...
        copy(fornt2, pos1, front1); //把安插位置到首端的元素向前移动一个
    } else {    //安插点之后的元素个数较少
        push_back(back());
        ...
        copy_backward(pos, back2, back1);
    }
    *pos = x_copy;  //在安插点上放置要安插的元素
    return pos;
}
如何通过iterator模拟一段连续空间,最主要的通过 -, i++, i--,i+=4; i-=8; i[10]等操作来体现出来的,下面我们来说一下deque关于操作符的重载
//首先使用node相减 乘以buffer的大小,获得中间完整buffer有多少
//然后在把cur前边的和last的后边的剪掉,得到的就是距离
difference_type operator-(const self&x) const 
{
    return difference_type(buffer_size())*(node-x.node-1) + 
        (cur-first) + (x.last - x.cur);
}

self& operator++() {
    ++cur;  //切换下一元素
    if (cur == last) {  //如果++之后,超过了尾部元素
        set_node(node+1);   //那就后移一个node,然后cur指向当前的首元素
        cur = first;
    }
    return *this;
}

self operator++(int) {
    self tmp = *this;
    ++*this;
    return tmp;
}

self& operator--() {        
    if(cur == frist) {      //如果已经是当前buffer的首元素了,就向前移动一个node
        set_node(node-1);
        cur = last; //并且指到尾元素
    }
    --cur;
    return *this;
}

self operator--(int) {
    self tmp = *this;
    --*this;
    return tmp;
}

void set_node(map_pointer new_node) {
    node = new _node;
    first = *new_node;
    last = first + difference_type(buffer_size());
}

self& operator+=(difference_type n) {
    difference_type offset = n + (cur - first);
    if(offset >= 0 && offset < difference_type(buffer_size()))
        cur += n;   //如果目标位置在同一buffer内
    else {
        difference_ype node_offset = offset > 0 ? 
                            offset/difference_type(buffer_size())
                            : -difference_type((-offset-1)/buffer_size())-1;
        set_node(node + node_offset);   //切换到正确的buffer
        cur = first + (offset-node_offset + difference_type(buffer_size()));
    }
    return *this;
}

self operator+(difference_type n) const {
    self tmp = *this;
    return tmp += n;
}
...
通过阅读源码;学习到应该尽量把单一功能的封成一个函数,并且如果能够通过已有函数实现的功能,就不要再重新写了,这也方便修改
为了达到上面的目的,就要求再规划函数的时候考虑仔细,提前梳理那些函数,是可以复用的

queue stack

这两个容器都是不同程序的封装deque完成的
template <class T, Sequeue=deque<T>>
class queue {
    ...
protected:
    Sequence c; //底部容器   
public:
    bool empty() const { return c.empty();}
    size_type   size() { return c.size() };
    reference front() {return c.front();}
    const_reference front() const { return c.front();}
    reference back() { return c.back();}
    const_reference back() { return c.back();}
    void push(const value_type &x) { c.push_back(x);}
    void pop() {c.pop_front();}
}


//并且可手动指定queue的底层容器
queue<string, List<string>> c;  //制定底层容器为链表

容器 rb_tree 红黑树

红黑树是平衡二元搜索树中常被使用的一种。
    平衡二元搜索树的特点:排列规则有利于search和insert,并保持适度平衡--没有任何一个节点过深
template <class Key,    
            class Value,    // -->  Key+Data = Value
            class KeyOfValue,   //这个参数说明  怎么从value中获取key
            class Compare,      //这个函数对象,用来比对Key的大小,
            class Allc = alloc> //分配器
class rb_tree {
protected:
    typedef __rb_tree_node<Value> rb_tree_node;
    ...
public:
    typedef rb_tree_node *link_type;
    ...
protected:
    //RB_tree 使用这三个成员变量可以完整表示
    size_type   node_count;     //rb_tree 的大小 (节点的数量)
    link_type   header;
    Compare     key_compare;        //key的大小比较准则; 应该是一个函数类
};
创建一个红黑树需要5个参数   
1. Key的类型
2. Value的类型      是key和data的集合的类型
3. KeyOfValue       是个仿函数,用来Value的类型中获取Key
4. Compare          仿函数,用来比对Key的大小
5. 分配器
下面是用例
rb_tree<int,
        int,        //这个
        identity<int>,  //这个仿函数是直接返回自己
        less<int>,      //比较Int的仿函数
        alloc>
myTree;
//////////////////////
rb_tree有两种插入节点的方式

//不能插入重复的元素,如果插入重复的,不会报错,只是不起作用
myTree.insert_unique(3);    
myTree.insert_unique(4);

//可以插入重复的元素
myTree.inser_equal(5);
myTree.inser_equal(5);

容器 set,multiset

set/multiset 以rb_tre为底层结构,因此有元素自动排序的特性,排序的依据是key;
set/multiset 元素的value和key合一;value就是key  就像上面的rb_tree的例子

我们无法使用set/muliset的iterator改变元素,

set的key不可以重复,是使用rb_tree的insert_unque()
multiset元素的key可以重复,是使用rb_tree的insert_equal()

下面是set的代码,可以看到set的迭代器是const的,所以不能改变
template <class Key,
            class Compare = less<Key>,
            class Alloc=alloc>
class set {
    typedef Key key_type;
    typedef Key value_type;
    typedef Compare key_compare;
    typedef Compare value_compare;
private:
    typedef rb_tree<key_type, value_type, 
                identity<value_type>, key_compare, Alloc> rep_type;
    rep_type    t;
public:
    typedef typename rep_type::const_iterator iterator;
};

map和multimap

map和multimap底层也是使用的rb_tree
只是在value是一个pair的类型 例: make_pair<int, string> 这种就是一个Value
然后Compare的仿函数是取的 make_pair<int, string>的key也就是int类型的值
tempalte <class Key,
            class T,
            class Compare = less<Key>,
            class Alloc = alloc>
class map{
public:
    typedef Key Key_type;
    typedef T   data_type;
    typedef T   mapped_type;
    typedef pair<const Key, T> value_type;
    typedef Compare key_compare;
pirvate:
    typedef rb_tree<key_type, value_type,
            select1st<value_type>, key_compare, Alloc> rep_type;
    rep_type    t;
public:
    typedef typename rep_type::iterator iterator;
};

hashtable 容器

hashtable是一个存储的容器,存储的元素是散列的,没有规律的,查找非常迅速的容器
存储的方式大致为:
    1. 首先把这个元素进行hash,得到一个int/long类型的哈希值
    2. 然后根据当前hashtable的大小,把元素的哈希值进行modulus计算,得到一个在当前hashtable范围内的值,并且把这个元素放到哪里
    3. 如果两个元素之后modulus的值相同,就把元素往后排,使用链表来存储modulus相同的元素
    4. 如果modulus相同的值过多,导致链表的长度超过hashtable,此时再查找一个元素的速度就会变慢,所以当链表的长度超过hashtable时,hashtable的长度会扩充(当前长度的两倍之后的第一个质数)
    5. hashtable扩充之后,重新计算每个元素的modulus的值,然后放入hashtable  
    6. 每次hashtable扩充时的大小不是计算的,而是提起人为规定的,{53, 97, 193, 389, ...}

图 08_02_hashtable

template<class Vaule,   //Value = data+key   和rb_tree的Value一样
        calss Key,      //主键的类型
        class HashFunc, //获取主键hash值的仿函数
        class ExtractKey,   //萃取的仿函数,从Value中获取Key
        class EquelKey,     //判断Key是否相等的仿函数           
        class Alloc=alloc>
    class hashtable {
    public:
        typedef HashFunc    hasher;     
        typedef EquelKey    ky_equal;
        typedef size_t      size_type;

    private:
        hasher  hash;   
        key_equal   equals;
        ExtractKey  get_key;

        typedef __hashtable_node<Value> node;   //hashtable上的node
        vector<node*, Alloc> buckets;   //hashtable 
        size_type   num_elements;
    public:
        size_type bucket_count() const { return buckets.size(); }
    ...
    };

使用样例

hashtable<const char*,
        const char *,
        hash<const char *>,
        identity<const char *>,
        eqstr,
        alloc>
ht(50, hash<const char*>(), eqstr());   //构造函数,制定hashtable的大小,hash仿函数类的对象实例, 对比两个key相等的仿函数类对象实例

ht.insert_unique("111");
ht.insert_unique("222");

////
struct eqstr{
    bool operaotr() (const char *s1, const char *s1) const 
    { return strcmp(s1, s2)==0; }
};

[hash-code]

下面是计算modulus的代码,最后是使用计算key的哈希值的int的hash值除以hashtable大小的余数来决定的
08-04_modulus

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值