目录
1、stack的介绍和使用
1、stack是一种容器适配器,专门用在具有后进先出操作的上下文换进中,其删除只能从容器的一端进行元素的插入与提取操作。
2、stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供了一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出。
3、stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类因该支持以下操作:
empty:判空操作
back:获取尾部元素操作
push_back:尾部插入元素操作
pop_back:尾部删除元素操作
4、标准容器vector、deque、list均符合这些要求,默认情况下,如果没有为stack指定特定的底层容器,默认情况下使用deque。
1.1、stack的使用
stack():构造空的栈
top():返回栈顶元素的引用
最小栈问题
class MinStack {
public:
MinStack()
{}
void push(int val)
{
_st.push(val); //插入的元素先入正常栈
if(_minst.empty()||val <= _minst.top()) //如果说最小栈为空或者插入的元素小于栈的栈顶的元素,就插入到最小栈中
_minst.push(val);
}
void pop()
{
//因为删除元素的时候都是从栈顶开始删的,所以要删就要先判断正常栈的元素是不是等于最小栈的元素,如果等于就要删除最小栈栈顶的元素
if(_st.top() == _minst.top())
{
_minst.pop();
}
_st.pop();
}
int top()
{
return _st.top();
}
int getMin()
{
return _minst.top();
}
private:
stack<int> _st; //一个正常栈
stack<int> _minst;//一个最小栈
};
栈的压入与弹出
//输入两个整数序列,第一个序列表示栈的压入顺序
//请判断第二个序列是否可能为该栈的弹出顺序。
//假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序
//序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。
class Solution {
public:
bool IsPopOrder(vector<int>& pushV, vector<int>& popV)
{
//出栈序列和入栈序列保存在数组里面
stack<int> st; //再创建一个栈实现入站与出战操作
int pushi = 0,popi = 0;
while(pushi < pushV.size())
{
st.push(pushV[pushi++]); //先将数据入栈
if(st.top() != popV[popi]) //将栈顶元素和pop的数组进行比较,如果说不匹配的话就继续入栈
{
continue;//不匹配
}
else
{
while(!st.empty() && st.top() == popV[popi]) //直到将栈中的元素删为空就要结束,要继续再入栈了
{
st.pop(); //只要匹配就对栈中的元素进行删除
++popi; //再将popV中的指针向后挪动 继续遍历 看是否还有相等的
}
}
}
return st.empty();
}
};
逆波兰表达式求值
2 + 1 * 3 : 中缀表达式
2 1 3 * + : 后缀表达式 (操作数顺序不变,操作符按优先级重排)
1、操作数入栈
2、操作符,取出栈顶元素两个进行运算,运算结果继续入栈
取出来的数是右操作数 (-与/ 有影响)
最后运算的结果就在栈里面
class Solution {
public:
int evalRPN(vector<string>& tokens)
{
//创建一个栈里面只存放数字,是将这个字符串数组中的不是运算的符的字符转化成整数压入栈中
stack<int> st;
for(auto& str : tokens)//使用范围for(简化迭代容器元素的语法结构)直接对这个字符数组进行遍历
{
if(str == "+" || str == "-" || str == "*" || str == "/")
{
遇到运算符就取栈顶的两个元素进行运算
int right = st.top(); //右操作数是栈顶元素
st.pop(); //将此元素删除
int left = st.top(); //左操作数是紧接着的栈顶元素
st.pop(); //取出后再删除此元素
switch(str[0]) //再判断这个符号
{
case '+':
st.push(left+right); //进行计算再将计算结果压入栈中
break;
case '-':
st.push(left-right);
break;
case '*':
st.push(left*right);
break;
case '/':
st.push(left/right);
break;
}
}
else
{
st.push(stoi(str));//如果不是运算符就将其转化为整数,并压入栈中
}
}
return st.top();
}
};
2、queue的介绍和使用
使用队列对树进行层序遍历
1、队列是一种容器适配器,专门用于再FIFO(先进先出)中操作,其中从容器一端插入元素,另一端提取元素。
2、队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素,元素从队尾入队列,从对头出队列。
3. 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作 :empty :检测队列是否为空size :返回队列中有效元素的个数front :返回队头元素的引用back :返回队尾元素的引用push_back :在队列尾部入队列pop_front :在队列头部出队列4、 标准容器类 deque(双向队列) 和 list 满足了这些要求。默认情况下,如果没有为 queue 实例化指定容器类,则使用标 准容器 deque 。
2.1、queue的使用
queue(): 构造空的队列empty() : 检测队列是否为空,是返回 true ,否则返回 falsesize() : 返回队列中有效元素的个数front(): 返回队头元素的引用back(): 返回队尾元素的引用push(): 在队尾将元素 val 入队列pop() : 将队头元素出队列
给你二叉树的根节点 root
,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
示例一: 输入:root = [3,9,20,null,null,15,7] 输出:[[3],[9,20],[15,7]]
示例 2:
输入:root = [1] 输出:[[1]]
示例 3:
输入:root = [] 输出:[]
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root)
{
vrctor<vector<int>> vv; //创建一个二维数组来存储要返回的结果
queue<TreeNode*> q; //创建一个队列,存储树的节点
int levelSize = 0;
if(root) //如果根节点不为空,将根节点入队列
{
q.push(root);
levelSize = 1; //将本层的节点个数设置为1
}
while(!q.empty())
{
//一层一层出
vector<int> v;
for(int i = 0; i < levelSize; ++i)
{
TreeNode* front = q.front(); //取队列头部节点
q.pop(); //弹出队列q的头部节点 //弹出头部节点后,front自动指向兄弟节点
v.push_back(front->val);//将根节点的值存入一维数组,保存的是每一层的节点的值
if(front->left) //如果左孩子不为空
q.push(front->left);//入队列
if(front->right) //右孩子不为空
q.push(front->right);//入队列
}
//在while循环外将根节点入队列,在循环中第一步先将指针指向根节点,先对队列进行pop
//弹出一个节点
//弹出后将其入到一维数组中
//再去判断指针指向的这个节点的左右孩子
//如果存在将其入队列
//至此循环结束
//将一维数组入二维数组
//此时的队列已经是树的第二层的节点,q.size()得到levelSize
vv.push_back(v);
levelSize = q.size();
}
return vv;
}
};
//总结:::
//出一个节点入它的左右孩子,出了之后指针又会指向上一个被出的节点的兄弟节点,
//重复和上一个一样的操作。
//这样判断停止呢? 答案是:我们已经提前跟新了levelSize,这是for循环的限制条件,
//如果说后面还有下一层的节点的值,也不会再去遍历它。
3、priority_queue(优先级队列)的介绍和使用
1. 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。2. 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素 ( 优先队列中位于顶部的元素) 。3. 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类, queue 提供一组特定的成员函数来访问其元素。元素从特定容器的“ 尾部 ” 弹出,其称为优先队列的顶部。4. 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:empty() :检测容器是否为空size() :返回容器中有效元素个数front() :返回容器中第一个元素的引用push_back() :在容器尾部插入元素pop_back() :删除容器尾部元素5. 标准容器类 vector 和 deque 满足这些需求。默认情况下,如果没有为特定的 priority_queue 类实例化指定容器类,则使用vector 。6. 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heap、 push_heap 和 pop_heap 来自动完成此操作。
3.1、priority_queue的使用
优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以使用priority_queue.注意:默认情况下priority_queue是大堆。
priority_queue()/priority_queue(first, last): 构造一个空的优先级队列empty( ): 检测优先级队列是否为空,是返回 true ,否则返回 falsetop( ) : 返回优先级队列中最大 ( 最小元素 ) ,即堆顶元素push(x) : 在优先级队列中插入元素 xpop (): 删除优先级队列中最大 ( 最小 ) 元素,即堆顶元素
#include<vector>
#include<queue>
#include<functional> //greater算法的头文件
void TestPriorityQueue()
{
//默认情况下,创建的是大堆,其底层按照小于号比较
vector<int> v{3,2,7,6,0,1,9,5,8};
priority_queue<int> q1;
for(auto& e : v)
q1.push(e);
cout << q1.top() << endl;
//如果要创建小堆,将第三个模板参数换成greater比较方式
priority_queue< int, vector<int>, greater<int> >
q2(v.begin(),v.end());
cout << q2.top() <<endl;
}
2、如果priority_queue中放自定义类型的数据,用户需要在自定义类型中提供>或者<的重载
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
bool operator<(const Date& d)const
{
return (_year < d._year) ||
(_year == d._year && _month < d._month) ||
(_year == d._year && _month == d._month && _day < d._day);
}
bool operator>(const Date& d)const
{
return (_year > d._year) ||
(_year == d._year && _month > d._month) ||
(_year == d._year && _month == d._month && _day > d._day);
}
friend ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
private:
int _year;
int _month;
int _day;
};
void TestPriorityQueue()
{
// 大堆,需要用户在自定义类型中提供<的重载
priority_queue<Date> q1;
q1.push(Date(2018, 10, 29));
q1.push(Date(2018, 10, 28));
q1.push(Date(2018, 10, 30));
cout << q1.top() << endl;
// 如果要创建小堆,需要用户提供>的重载
//创建小堆的时候要注意参数<Date, vector<Date>, greater<Date>>
priority_queue<Date, vector<Date>, greater<Date>> q2;
q2.push(Date(2018, 10, 29));
q2.push(Date(2018, 10, 28));
q2.push(Date(2018, 10, 30));
cout << q2.top() << endl;
}
数组中第k大元素
给定整数数组 nums
和整数 k
,请返回数组中第 k
个最大的元素。
请注意,你需要找的是数组排序后的第 k
个最大的元素,而不是第 k
个不同的元素。
你必须设计并实现时间复杂度为 O(n)
的算法解决此问题。
示例 1:
输入: [3,2,1,5,6,4],
k = 2
输出: 5
//法一:
class Solution {
public:
int findKthLargest(vector<int>& nums, int k)
{
//在数据量小的情况下,pop k次就可以找到第k大
//建大堆
时间复杂度k*logN
priority_queue<int> pq(nums.begin(),nums.end());//通过begin和end将需要遍历的数初始化入优先队列中
while(--k)
{
pq.pop();
}
return pq.top();
}
};
//法二:
class Solution
{
public:
int findKthLargest(vector<int>& nums, int k)
{
sort(nums.begin(), nums.end(), greater<int>() );//这里是函数模板带括号,传过去的是一个对象
return nums[k-1];
}
};
//法三:
class Solution
{
public:
int findKthLargest(vector<int>& nums, int k)
{
//建一个k个元素的小堆
priority_queue<int, vector<int>, greater<int>> //这里是类模板不带括号 传入的是参数,参数是类型,不能加括号
pq(nums.begin(), nums.begin()+k);
//时间复杂度(N-k)*logk
for(int i = k; i < nums.size(); ++i)
{
if(nums[i] > pq.top())
{
pq.pop();
pq.push(nums[i]);//将 nums[i] 插入堆中,保持堆的大小为 k,因为小顶堆中最小的元素总是堆顶元素,所以这样可以确保堆中始终保存的是当前找到的前 k 大的元素。
}
}
return pq.top();
}
};
4、介绍一下仿函数
指的是行为类似函数的对象。仿函数是一个类或一个结构体,通过operator()函数使对象可以像普通函数一样被调用。
- 常用于算法中,例如STL中的排序、查找等操作,允许传递自定义的行为。
#include <iostream>
using namespace std;
// 定义一个仿函数类
class Adder {
public:
// 构造函数
Adder(int x) : value(x) {}
// 重载函数调用操作符
int operator()(int x) const {
return value + x;
}
private:
int value;
};
int main() {
Adder addFive(5); // 创建一个仿函数对象
cout << addFive(10) << endl; // 调用仿函数,结果是 15
return 0;
}
创建 Adder
对象 addFive
并传递参数 10
,它的行为类似于 addFive(10)
调用 operator()
函数。
仿函数提供了一种将函数逻辑封装到对象中的方式,能够存储状态并实现复杂的行为,适用于各种需要自定义操作的场景。
5、容器适配器
适配器是一种设计模式,是将一种接口转化为用户希望的另一种接口。
虽然 stack 和 queue 中也可以存放元素,但在 STL 中并没有将其划分在容器的行列,而是将其称为 容器适配 器 ,这是因为 stack 和队列只是对其他容器的接口进行了包装, STL 中 stack(栈) 和 queue(对列) 默认使用 deque(双端队列)。
![](https://i-blog.csdnimg.cn/direct/3bf8350b4c364e5ea16ed872b53e4e41.png)
可以在头尾两端进行插入和删除操作,且时间复杂度为O(1) ,与 vector 比较,头插效率高,不需要搬移元素;与 list 比较,空间利用率比较高。deque 并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际 deque 类似于一个动态的二维 数组.