stack && queue && priority_queue 模拟实现
- 一.stack
- 1.概念:
- 2.使用:
- 3.模拟实现:
- 一些题目:
- 1.最小栈:
- 2.栈的压入弹出序列:
- 3.逆波兰表达式求值:
- 二.queue
- 1.概念:
- 2.使用:
- 3.模拟实现:
- 一个题目:
- 1.层序遍历:
- GIF解析
- 三.priority_queue
- 1.概念:
- 2.一个题目:
- 思路一:建堆+堆元素删除
- 思路二:优化
- 3.模拟实现:
- 4.仿函数的应用:
- 1.priority_queue()
- 2.sort
一.stack
1.概念:
1.我们从stack的模板参数中看到第一个是stack中元素的类型。
2.第二个参数是一个有缺省值的参数类型,类型是一个deque类型。
3.deque是一个双端队列的一个容器(有list和vector的所有优点)
4.总结:stack其实不是一个容器,它叫做容器适配器,底层是对他使用的一个容器的接口的再封装。
5.底层的容器要满足以下接口:
push_back()
pop_back()
size()
empty()
back()
2.使用:
void test_1()
{
stack<int> s_1;
stack<int> s_2;
s_1.push(1);
s_1.push(2);
s_1.push(3);
s_1.push(4);
s_1.pop();
cout << s_1.top() <<endl;//3
if (s_1.empty())
cout << "当前栈为空!" << endl;//当前栈不为空
else
cout << "当前栈不为空!" << endl;
s_2 = s_1;
s_2.pop();
s_1.swap(s_2);
while (!s_1.empty())
{
cout << s_1.top() << " ";
s_1.pop();
}
//2 1
cout << endl;
while (!s_2.empty())
{
cout << s_2.top() << " ";
s_2.pop();
}
//3 2 1
cout << endl;
}
1.容器适配器是没有迭代器的,容器适配器不是容器,容器才有迭代器。
2.遍历容器适配器的元素有自己对应的方法。
3.没有给模板传第二个参数所以使用默认的双端队列作为底层容器。
4.使用vector作为底层的容器也是可以的!
3.模拟实现:
1.有了上面的stack基本概念和使用的介绍,我们想要去模拟实现一个stack。
2.有了底层的容器在底层容器的基础之上进行封装是非常方便的。
#include<iostream>
#include<vector>
using namespace std;
namespace sfpy {
//1.默认使用的vector作为我们的底层容器:
template<class T , class Container = vector<T>>
class stack {
//1.需要提供构造函数?
//回答:不需要没有提供构造函数自定义类型回去调用它的默认构造。
public:
size_t size()
{
return vessel.size();
}
bool empty()
{
return vessel.empty();
}
void push(T x)
{
vessel.push_back(x);
}
void pop()
{
vessel.pop_back();
}
T top()
{
return vessel.back();
}
private:
Container vessel;
};
}
一些题目:
最小栈
1.最小栈:
思路一:
1.定义两个栈第一个栈(Stack)用来正常保存数据,第二个栈(StackMin)用来保存较小的数据值。
2.特殊情况:
2-1:马上要push的数据小于等于StackMIn栈顶的值就两个栈都push。
2-1:pop数据需要判断Stack.top()和StackMIn.top()值相等就都pop.
2-1: 不这样处理的话出现连续相同的较小值对于pop来说,如果StackMin只记录较小值一次的话。Stack还有这个数据但是StackMIn中这个值就没有了getmin方法就不对了。
class MinStack {
public:
MinStack() {
}
void push(int val) {
Stack.push(val);
if(StackMin.empty() )
StackMin.push(val);
else
{
int top = StackMin.top();
if(val <= top)
{
StackMin.push(val);
}
}
}
void pop() {
if(Stack.top() == StackMin.top())
StackMin.pop();
Stack.pop();
}
int top() {
return Stack.top();
}
int getMin() {
return StackMin.top();
}
stack<int> Stack;
stack<int> StackMin;
};
2.栈的压入弹出序列:
栈的压入弹出序列
思路一:双指针模拟
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param pushV int整型vector
* @param popV int整型vector
* @return bool布尔型
*/
bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {
int pushi = 0, popi = 0;
stack<int> st;
while (popi < popV.size())
{
//2 1 0
//1 2 0
int tmp = popV[popi];
while (pushi < pushV.size())
{
//1.出栈判断:
if (!st.empty() && st.top() == tmp)
{
st.pop();
popi++;
break;
}
else
{
st.push(pushV[pushi++]);
continue;
}
//2.进入数据:
st.push(pushV[pushi++]);
popi++;
}
if (!st.empty() && pushi == pushV.size())
{
while (!st.empty() && st.top() == popV[popi])
{
st.pop();
popi++;
}
break;
}
}
return st.empty();
}
};
3.逆波兰表达式求值:
逆波兰表达式求值
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> s1;
for(auto& tmp:tokens)
{
//1.字符栈:
if(tmp=="+" || tmp=="-" || tmp=="*" || tmp=="/")
{
int right = s1.top();
s1.pop();
int left = s1.top();
s1.pop();
char ch = tmp[0];
switch(ch)
{
case '+':
s1.push(left+right);
break;
case '-':
s1.push(left-right);
break;
case '*':
s1.push(left*right);
break;
case '/':
s1.push(left/right);
break;
}
}
//2.数值栈:
else
{
s1.push(stoi(tmp));
}
}
return s1.top();
}
};
二.queue
1.概念:
1.我们从queue的模板参数中看到第一个是queue中元素的类型。
2.第二个参数是一个有缺省值的参数类型,类型是一个deque类型。
3.deque是一个双端队列的一个容器(有list和vector的所有优点)
4.总结:queue其实不是一个容器,它叫做容器适配器,底层是对他使用的一个容器的接口的再封装。
5.底层的容器要满足以下接口:
push_back()
pop_front()
size()
empty()
back()
front()
2.使用:
void test_3()
{
queue<int> q_1;
//1.入数据并且获取队尾数据:
for (int i = 0; i < 6; i++)
{
q_1.push(i);
cout << q_1.back() << " ";
}
//0 1 2 3 4 5
cout << endl;
cout << "queue size:" << q_1.size() << endl;
//queue size 5
//2.提取队头数据并且出数据:
while (!q_1.empty())
{
cout << q_1.front() << " ";
q_1.pop();
}
//0 1 2 3 4 5
cout << endl;
}
3.模拟实现:
1.有了上面的stack基本概念和使用的介绍,我们想要去模拟实现一个stack。
2.有了底层的容器在底层容器的基础之上进行封装是非常方便的。
#include<iostream>
#include<list>
using namespace std;
namespace sfpy {
template<class T , class Container = list<T>>
class queue {
public:
//1.不需要实现构造函数,自定义类型会去调用自己的默认构造:
bool empty()
{
return vessel.empty();
}
size_t size()
{
return vessel.size();
}
T front()
{
return vessel.front();
}
T back()
{
return vessel.back();
}
void push(T x)
{
vessel.push_back(x);
}
void pop()
{
vessel.pop_front();
}
private:
Container vessel;
};
}
一个题目:
1.层序遍历:
二叉树的层序遍历
思路一:使用队列模拟每一层数据的遍历:
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> vv;
if(root == nullptr)
return vv;
queue<TreeNode*> s;
s.push(root);
int num = s.size();
vector<int> tmp;
while(!s.empty())
{
TreeNode* top = s.front();
tmp.push_back(top->val);
s.pop();
num--;
if(top->left!=nullptr)
s.push(top->left);
if(top->right!=nullptr)
s.push(top->right);
if(num==0)
{
num = s.size();
vv.push_back(tmp);
tmp.resize(0);
}
}
return vv;
}
};
GIF解析
三.priority_queue
1.概念:
1,priority_queue是一个容器适配器。
2.priority_queue底层是一个顺序表容器,并且同时使用了堆的算法实现了一个堆的结构。
3.默认情况下priority_queue是一个大堆。
2.一个题目:
数组中第k大的元素
思路一:建堆+堆元素删除
1.用所有的元素进行建堆。
2.删除k-1个元素当前的堆顶就是第k大的元素。
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int> heap(nums.begin() , nums.end());
while(--k)
{
heap.pop();
}
return heap.top();
}
};
思路二:优化
1.如果N(nums这个顺序表中的总元素的个数)和k的数值比较接近的话那么时间复杂度度是比较高的。
2.使用topk算法的思路衍生。
3.开始建立k个元素的一个小堆遍历剩下所有数据到小堆中遍历完成后堆顶就是第k大的元素。
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
//1.建立一个小堆:最后一个模板参数换成greater比较方法
priority_queue<int,vector<int>,greater<int>> heap(nums.begin() , nums.begin()+k);
//2.遍历剩下的数据:
for(int i = k ; i<nums.size() ; i++)
{
int n = nums[i];
if(heap.top() < n)
{
heap.pop();
heap.push(n);
}
}
//3.返回堆顶数据:
return heap.top();
}
};
3.模拟实现:
1.priority_queue的底层是一个顺序表。
2.提供了堆的算法实现了一个堆结构。
3.提供两个比较类类中实现对operator()的定义,实现比较函数,提供不同的比较函数,创造不同的堆类型。
4.模板参数提供了一个仿函数的东西,仿函数不是函数是一个模板参数,是一个自定义类型的模板参数,并且类型中重载了operator(),这样在方法中实例化一个对象之后就可以使用通过重载()实现的一个方法。
#include<iostream>
#include<vector>
using namespace std;
namespace sfpy {
template<class T>
class less
{
public:
bool operator()(T x , T b)
{
return x > b;
}
};
template<class T>
class greater
{
public:
bool operator()(T x, T b)
{
return x < b;
}
};
template<class T , class Container = vector<T> , class Compare = less<int>>
class priority_queue {
private:
void adjust_up(int n)
{
Compare com;
int child = n;
int parent = (n - 1) / 2;
while (child > 0)
{
if(com(heap[child], heap[parent]))
swap(heap[child], heap[parent]);
child = parent;
parent = (child - 1) / 2;
}
}
void adjust_down()
{
Compare com;
//1.使用假设法:
int parent = 0;
int child = (parent * 2) + 1;
while (child < heap.size())
{
if (child + 1 < heap.size() && com(heap[child + 1], heap[child]))
child++;
if (com(heap[child], heap[parent]))
swap(heap[child], heap[parent]);
else
break;
parent = child;
child = (parent * 2) + 1;
}
}
public:
//1.push
void push(T x)
{
//1.插入数据到顺序表中:
heap.push_back(x);
//2.走向上调整算法:
adjust_up(heap.size() - 1);
}
//2.pop
void pop()
{
//1.首位交换:
swap(heap[0], heap[heap.size() - 1]);
//2.删除尾数据:
heap.pop_back();
//3.向下调整算法把交换好的值调整到合适的位置。
adjust_down();
}
bool empty() { return heap.empty(); }
size_t size() { return heap.size(); }
T top() { return heap.front(); }
private:
Container heap;
};
}
4.仿函数的应用:
1.仿函数顾名思义不是一个函数仿照函数实现的一个类。
2.这个类提供一个operator()的成员方法去控制大小比较等内容。
3.对于模板参数来说传递类型。
4.对于函数参数来说传递对象。
1.priority_queue()
template<class T>
class less
{
public:
bool operator()(T x , T b)
{
return x > b;
}
};
template<class T>
class greater
{
public:
bool operator()(T x, T b)
{
return x < b;
}
};
template<class T , class Container = vector<T> , class Compare = less<int>>
class priority_queue {
private:
void adjust_up(int n)
{
Compare com;
int child = n;
int parent = (n - 1) / 2;
while (child > 0)
{
if(com(heap[child], heap[parent]))
swap(heap[child], heap[parent]);
child = parent;
parent = (child - 1) / 2;
}
}
void adjust_down()
{
Compare com;
//1.使用假设法:
int parent = 0;
int child = (parent * 2) + 1;
while (child < heap.size())
{
if (child + 1 < heap.size() && com(heap[child + 1], heap[child]))
child++;
if (com(heap[child], heap[parent]))
swap(heap[child], heap[parent]);
else
break;
parent = child;
child = (parent * 2) + 1;
}
}
……………………………………
……………………………………………………
1.通过函数模板参数传递类类型进来,类型有一个默认的类类型实现了堆默认是大堆的一个情况。
2.通过传递模板参数的类型可以实现大小堆的转换。
3.在类中通过实例化一个对象,调用对象方法可以实现不同的比较逻辑。
4.通过不同的比较逻辑实现不同类型的堆。
2.sort
1.这个地方是直接通过函数参数传递一个实参:类对象。
2.在sort函数里面调用类中的operator()方法实现对数据的比较。
3.从而达到一个排升序和降序的效果。