咱学完栈和队列之后,又了解到了vector和list,更深入的了解到了它们各自的优势,那么有没有可能结合它们的优点摒弃弱点呢,其实是有人这么试过的,不过咱还在学vector和list就证明他可能没成功,不过并不影响我们理解它。
deque
deque的结构大概是这样的,它外部是一个指针数组,一般称为中控数组,每个位置存一个指针,的地址,指针指向一块相同大小的空间。而他把指针又做了一些处理,first指向数据的起始位置,last指向数据的结束位置,cur指向当前数据位置,node则指向它在中控数组的位置,这样我们想访问某一个位置只需要 / 就可以知道它在中控数组哪个位置,再 % 就可以知道它的具体位置。
而插入数据则分为头插尾插,如果是尾插,那么我们的cur指向最后一个数据,如果它不等于last,那么代表没满,直接插入即可,如果满了就扩容。而头插,上图是满了的情况,实际情况是从最后开始往前插的,只要cur不等于first,那就可以一直插入。这也是为什么中控数组从中间开始逐渐往两边,头插如果满了则node-- ,就可以往前,尾插如果满了node++,就可以往后。如果满了中控数组无非扩容一下,而中控数组的扩容代价很低,拷贝点地址罢了,同时头插尾插效率也很高。它头插的底层其实是你要插入的位置 n + (cur - first) 这样很巧妙的计算出了它的位置。
不过上面的优点说完,它一定有致命缺陷才导致他无法替代vector和list。那就是如果中间插入,那么涉及到移动数据以及扩容的问题,如果扩容,有两种方法解决,一种是直接增加数组的长度,但是这样会导致中控数组里的各个数组大小不同意,就不能用 / 和 % 快速找到位置了。而另一种方法就是插入位置之后的数据都往后移,这两种方法无疑是效率很低的,而它的头插尾插效率又很高,所以我们在特殊情况下才使用deque。
优先级队列 priority_queue
当初我们学排序的时候应该感受到了一个堆排序的优秀,不是最强,但是速度很稳定,但是我们使用的时候又分向上调整和向下调整的建堆,这让我们很苦恼,有时候写完以后因为某些需求又要改,实在麻烦,所以产生了一个叫仿函数的东西。
仿函数
template <class T>
class greater
{
public:
bool operator()(const T& x, const T& y)
{
return x > y;
}
};
template <class T>
class less
{
public:
bool operator()(const T& x, const T& y)
{
return x < y;
}
};
仿函数其实就是看着调用像一个函数,但是实际上它是个类,我们只是重载了函数调用里的()而已。
我们想建哪个堆,判断条件用哪个就好。
void Adjustup(int child)
{
compar com;
int parent = (child - 1) / 2;
while (child > 0)
{
if (com(_con[parent] , _con[child]))//用起来是不是很像函数调用
//
{
swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void Adjustdown(int parent)
{
size_t chiled = parent * 2 + 1;
compar com;
while (child < _con.size())
{
if (child + 1 < _con.size() && com(_con[child] , _con[child + 1]))
{
++child;
}
if (com(_con[parent] , _con[child]))
{
swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
}
}
这样我们给个我们自己设定的判断条件就可以灵活变通了(如果只是比大小,系统有自带的less和greater)。
template <class T,class Container = vector<T>, class compar = less<T>>
//这里传less或者greater就可以灵活改变我们要的排序方式,比我们直接改><要方便和有智慧的多
那么讲到这里,想必应该看到优先级队列的默认类型是vector了,它使用vector来存取建好堆的数据,再用堆的方式插入删除。
void push(const T& x)
{
//先尾插 再向下调整建堆
con.push_back(x);
Adjustup(_con.size() - 1);
}
void pop()
{
//先交换,再删堆顶
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
//删完再调整一下就好
Adjustdown(0);
}
当然它作为用vector的,必不可少的还是得有。
const T& top()
{
return _con[0];
}
size_t size()const
{
return _con.size();
}
size_t empty() const
{
return _con.empty();
}