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

来源:互联网 发布:matlab数据归一化 编辑:程序博客网 时间:2024/06/08 12:57

一. 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>::iteratordeque<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的类型中获取Key4. 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 0