目录
一、stack和queue的介绍和常用接口
二、常用接口
三、stack和queue的模拟实现
适配器
deque
四、priority_queue
priority_queue的模拟实现
1. stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。
2. stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出。
3. stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:
empty:判空操作
back:获取尾部元素操作
push_back:尾部插入元素操作
pop_back:尾部删除元素操作
4. 标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器,默认情况下使用deque。(因为deque既支持随机访问又在插入时复杂度不高,即兼顾list和vector的优点)
stack
queue
都是老生常谈了。
栈的接口和vector很像,完全可以用vector实现栈
#include
namespace bao
{
template
class stack
{
public:
stack() {}
void push(const T& x)
{
_c.push_back(x);
}
void pop()
{
_c.pop_back();
}
T& top()
{
return _c.back();
}
const T& top()const
{
return _c.back();
}
size_t size()const
{
return _c.size();
}
bool empty()const
{
return _c.empty();
}
private:
vector _c;
};
}
namespace bao
{
template
class queue
{
public:
queue()
{}
void push(const T& x)
{
_c.push_back(x);
}
void pop()
{
_c.pop_front();
}
T& back()
{
return _c.back();
}
const T& back()const
{
return _c.back();
}
T& front()
{
return _c.front();
}
const T& front()const
{
return _c.front();
}
size_t size()const
{
return _c.size();
}
bool empty()const
{
return _c.empty();
}
private:
list _c;
};
}
其实,对于这些stack和queue的构造,我们可以发现模板参数中有一个container参数
他们的默认底层容器都是用deque实现的,这里我们上面使用的分别是vector和list ,其实这就说明stack和queue是作为适配器实现的。
适配器的核心,其实就是两个字 :复用
因此,我们试着用库中的构造方法来实现stack和queue,可以这样写
template >
class stack
{
public:
stack()
{}
void push(const T& x)
{
_c.push_back(x);
}
void pop()
{
_c.pop_back();
}
T& top()
{
return _c.back();
}
const T& top()const
{
return _c.back();
}
size_t size()const
{
return _c.size();
}
bool empty()const
{
return _c.empty();
}
private:
Container _c;
};
template >
class queue
{
public:
queue()
{}
void push(const T& x)
{
_c.push_back(x);
}
void pop()
{
_c.pop_front();
}
T& back()
{
return _c.back();
}
const T& back()const
{
return _c.back();
}
T& front()
{
return _c.front();
}
const T& front()const
{
return _c.front();
}
size_t size()const
{
return _c.size();
}
bool empty()const
{
return _c.empty();
}
private:
Container _c;
};
从库中我们又知道 STL中stack和queue默认使用deque实现,上面我们也提到了:
“deque兼具随机访问(vector的优点)和高效率插入删除(list的优点)”
那么deque到底是什么样子的呢?它既然这么牛逼,为什么不用它作为一个容器呢?
前面我们已经学过 list是由多个节点互相用指针连接 vector则是连续的空间
那么deque为了兼顾二者的特点,选择了用指针连接多个连续得空间这个数据结构(就像一个动态二维数组)
为了维护他的“整体连续”的假象,deque的迭代器就相对于其他的迭代器来的更复杂了
其中cur 是指向对应元素
first和last指向当前分顺序表的头和尾
node则对应中控器指针数组的一个成员来对应这个分组
总的来看,deque这种双端数据结构有利有弊,
一方面,它在插入和删除时不需要挪动元素,只需要新开一个分组即可,效率比vector高很多
另一方面,它支持随机访问,空间利用率很高
但是deque有个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下
而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构:
优先级队列 (堆)
template
T和Container我们已经很熟悉了,分别是类模板参数的数据类型和容器类型,那么这个Compare又是什么呢?
这里就又引出一个概念 :仿函数
其实就是类中重载operator( )
例:
namespace bao
{
template
class less
{
public:
bool operator()(const T& left, const T& right)
{
return left < right;
}
};
}
int main()
{
bao::less test;
test(1, 2);//为真
return 0;
}
其实还是为了突出 复用
上面我们已经介绍了,priority_queue是个堆,那么建堆时向上调整和删除数据时向下调整势必会用到大于小于的比较,这时我们直接用一个仿函数来替代,就可以一劳永逸。
但是要注意:priority_queue的仿函数只能通过> 和 < 来判断,所以如果用自定义类型的数据建堆的话,要先重载一下 < 和 > 操作符。
//priority_queue模拟实现
namespace bao
{
template ,class cmp = less>
class my_priority_queue
{
//cmp仿函数
template
struct less
{
bool operator()(const T& left, const T& right)
{
return left < right;
}
};
template
struct greater
{
bool operator()(const T& left, const T& right)
{
return left > right;
}
};
//构造函数
my_priority_queue()
:c()
{}
//拷贝构造函数
template
my_priority_queue(Iterator first, Iterator last)
: c(first, last)
{
int count = c.size();
int root = (cout - 2) / 2;
for (; root >= 0; root--)
adjust_down(root);
}
//析构
~my_priority_queue
{
~c();
}
//adjust up
void adjust_up(size_t x)
{
int child = x;
int parent = (child - 1) / 2;
while (parent>0)
{
if (cmp()(c[child],c[parent]))
{
swap(c[child], c[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
return;
}
}
//adjust down
void adjust_down(size_t x)
{
size_t parent = x;
size_t child = parent * 2 + 1;
while (child < c.size())
{
// 找以parent为根的较大的孩子
if (child + 1 < c.size() && Compare()(c[child], c[child + 1]))
child += 1;
// 检测双亲是否满足情况
if (Compare()(c[parent], c[child]))
{
swap(c[child], c[parent]);
parent = child;
child = parent * 2 + 1;
}
else
return;
}
}
//size
size_t size()const
{
return c.size();
}
//empty
bool empty()
{
return c.empty();
}
//注意堆顶元素是不能修改的,因为如果修改了堆顶元素会破坏堆的特性
const T& top()const
{
return c.front();
}
//push
void push(const T& data)
{
c.push_back(data);
adjust_up(c.size() - 1);
}
//pop
void pop()
{
if (empty())
return;
//堆尾删 先交换第一个和最后一个 然后删除 然后向下调整
swap(c.front(), c.back());
c.pop_back();
adjust_down(0);
}
private:
Container c;
};
}