C++知识点 – stack和queue的模拟实现
文章目录
STL中的stack和queue都是适配器,这是一种设计模式,该模式是将一个类的接口转换成用户希望的另一个接口。
类模板中适配器的缺省参数给的是deque,这是一种双端开口的队列,支持任意位置插入删除,也支持随机访问,适合做stack和queue的容器参数。
一、stack
1.整体结构
代码如下:
template<class T, class Container = deque<T>>
class Stack
{
public:
private:
Container _con;
};
只有一个成员变量,类型为Container,是一个类容器,默认类型是deque。stack的容器模板类型一定要支持尾插和尾删,所以vector和list同样适配。
2.成员函数
代码如下:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_back();
}
T& top()
{
return _con.back();
}
const T& top() const
{
return _con.back();
}
bool empty() const
{
return _con.empty();
}
size_t size() const
{
return _con.size();
}
3.测试
用vector做stack的容器
代码如下:
void TestStack1()
{
lmx::stack<int, vector<int>> s;
s.push(0);
s.push(1);
s.push(2);
s.push(3);
s.push(4);
while (!s.empty())
{
cout << s.top() << ' ';
s.pop();
}
cout << endl;
}
同理,第二个模板参数传list或者不传,都可以实现栈。
二、queue
1.整体结构
代码如下:
template<class T, class Container = deque<T>>
class queue
{
public:
private:
Container _con;
};
queue与stack类似,都只有一个成员变量,第二个模板参数的类型缺省值也都是deque。
由于队列要支持先进先出,模板容器必须支持尾插和头删,而vector不支持头删,因此就不适配了。
2.成员函数
代码如下:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_front();
}
T& bcak()
{
return _con.back();
}
T& front()
{
return _con.front();
}
const T& bcak() const
{
return _con.back();
}
const T& front() const
{
return _con.front();
}
bool empty()
{
return _con.empty();
}
size_t size()
{
return _con.size();
}
3.测试
使用list构成的queue:
代码如下:
void TestQueue1()
{
lmx::queue<int, list<int>> q;
q.push(0);
q.push(1);
q.push(2);
q.push(3);
q.push(4);
while (!q.empty())
{
cout << q.front() << ' ';
q.pop();
}
cout << endl;
}
三、priority_queue
priority_queue是优先级队列,是queue头文件中的第二个类模板,其结构类似堆(完全二叉树),与队列一样都是容器适配器。
代码如下:
void TestQueue2()
{
int a[] = { 1,2,3,4,9,5,6,3 };
priority_queue<int> pq(a, a + sizeof(a) / sizeof(int));
while (!pq.empty())
{
cout << pq.top() << ' ';
pq.pop();
}
cout << endl;
}
1.整体结构
优先级队列的结构类似于堆,是一颗完全二叉树。
代码如下:
template<class T, class Container = vector<T>>
class priority_queue
{
public:
private:
Container _con;
};
其默认的容器类型是vector,默认建的是大堆,就是任意父节点一定大于其两个子节点。
2.向上调整与向下调整算法
向上调整算法适用于尾插数据时,调整插入数据的位置。尾插数据后,将该数据与父节点对比,若大于父节点,则与父节点交换,否则不交换,一直判断到该数据小于某一父节点或者该数据到堆顶为止。
代码如下:
void adjust_up(size_t child)
{
size_t parent = (child - 1) / 2;
while (child > 0)
{
if (_con[child] > _con[parent])
{
std::swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
向下调整算法用于删除堆顶数据,删除时,将容器中首尾两个数据交换,然后尾删,就删除了堆顶数据,之后从现在的堆顶开始调整,比较堆顶与两个子节点中最大的节点的大小,若小于子节点,则交换数据,否则不交换。直到堆顶数据交换到堆的最后一层或者出现不用交换的数据为止。
向下调整算法需要左右子树都为堆,才能继续。
void adjust_down(size_t parent)
{
size_t child = parent * 2 + 1;//左孩子
while (child < _con.size())
{
if (_con[child + 1] > _con[child])//选出左右孩子中大的那一个
{
child++;
}
if (_con[child] > _con[parent])
{
std::swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
3.push、pop、top
代码如下:
void push(const T& x)
{
_con.push_back(x);
adjust_up(_con.size() - 1);
}
void pop()
{
std::swap(_con[0], _con[_con.szie() - 1]);
_con.pop_back();
adjust_down(0);
}
const T& top()
{
return _con[0];
}
4.构造函数
自己写一个迭代器区间的构造函数,先插入数据,然后建堆,使用向下调整算法建堆的时间复杂度更低。
向下调整算法建堆的流程为:由于向下调整算法需要该子树下面的子树都为堆,因此我们先找到最后一个元素的父节点,以该节点为父节点进行向下调整,保证最后一个子树为堆,然后依次调整前面的子树,最终使整颗二叉树都是堆。时间复杂度为O(N)。
代码如下:
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last)
{
while (first != last)
{
_con.push_back(*first);
first++;
}
//建堆
for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
{
adjust_down(i);
}
}
//自己写了构造函数后,编译器就不会生成默认构造了,所以还需要自己写一个默认构造。
priority_queue()
{}
5.加入仿函数控制大小优先级
仿函数是一个类模板,其中成员函数重载了()。
代码如下:
namespace lmx
{
template<class T>
class less
{
public:
bool operator()(const T& l, const T& r)
{
return l < r;
}
};
template<class T>
class greater
{
public:
bool operator()(const T& l, const T& r)
{
return l > r;
}
};
}
int main()
{
lmx::less<int> lsFunc;
cout << lsFunc(1, 2) << endl;
cout << lsFunc.operator()(1, 2) << endl;//等价于下面的表达式
return 0;
}
lsFunc不是函数,而是类对象,但可以像函数一样使用,本质上是()重载。
在优先级队列的模板参数中加入仿函数,来控制建立大堆还是小堆:
template<class T, class Container = vector<T>, class Compar = std::less<T>>
class priority_queue
{
public:
private:
Container _con;
};
Compare是进行比较的仿函数,缺省值为less,less代表建大堆,greater代表建小堆。
同时,仿函数引入后,向上调整和向下调整算法中的比较环节都需要改动:
less->建大堆:parent < child;
greater->建小堆:parent > child;
void adjust_up(size_t child)
{
Compare com;
size_t parent = (child - 1) / 2;
while (child > 0)
{
/*if (_con[child] > _con[parent])*/
//if (_con[parent] < _con[child])
if(com(_con[parent], _con[child]))
{
std::swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void adjust_down(size_t parent)
{
Compare com;
size_t child = parent * 2 + 1;//左孩子
while (child < _con.size())
{
//if (child + 1 < _con.size() && _con[child + 1] > _con[child])//选出左右孩子中大的那一个
//if (child + 1 < _con.size() && _con[child] < _con[child + 1])
if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
{
child++;
}
//if (_con[child] > _con[parent])
//if (_con[parent] < _con[child])
if (com(_con[parent], _con[child]))
{
std::swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
6.测试
代码如下:
void TestQueue2()
{
int a[] = { 1,2,3,4,9,5,6,3 };
lmx::priority_queue<int> pq1(a, a + sizeof(a) / sizeof(int));
lmx::priority_queue<int, vector<int>, greater<int>> pq2(a, a + sizeof(a) / sizeof(int));
while (!pq1.empty())
{
cout << pq1.top() << ' ';
pq1.pop();
}
cout << endl;
while (!pq2.empty())
{
cout << pq2.top() << ' ';
pq2.pop();
}
cout << endl;
}