一. 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) {
push_front(x);
return start;
}
else if (position.cur == finish.cur) {
push_back(x);
iterator tmp = finish;
--tmp;
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关于操作符的重载
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);
cur = first;
}
return *
this;
}
self operator++(
int) {
self tmp = *
this;
++*
this;
return tmp;
}
self& operator--() {
if(cur == frist) {
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;
else {
difference_ype node_offset = offset >
0 ?
offset/difference_type(buffer_size())
: -difference_type((-offset-
1)/buffer_size())-
1;
set_node(node + node_offset);
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<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>,
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, ...}
template<
class Vaule,
calss Key,
class HashFunc,
class ExtractKey,
class EquelKey,
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;
vector<node*, Alloc> buckets;
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());
ht.insert_unique(
"111");
ht.insert_unique(
"222");
struct eqstr{
bool operaotr() (
const char *s1,
const char *s1)
const
{
return strcmp(s1, s2)==
0; }
};
下面是计算modulus的代码,最后是使用计算key的哈希值的int的hash值除以hashtable大小的余数来决定的