栈、队列、堆的学习
文章目录
- 栈、队列、堆的学习
- 栈、队列、堆 - 基础知识回顾
- 栈
- 队列
- 堆
- 7道经典题目
- 1. 使用队列实现栈(easy)(栈、队列)
- 2. 使用栈实现队列(easy)(栈、队列)
- 3. 包含min函数的栈(easy)(栈)
- 4. 合法的出栈序列(medium)(栈、队列)
- 5. 简单的计算器(hard)(栈)
- 6. 数组中第K大的数(easy)(堆)
- 7. 寻找中位数(hard)(堆)
栈、队列、堆 - 基础知识回顾
栈
先进后出的线性表
#include <stack>
stack<int> stk;
stk.top() : 取出栈顶元素,返回引用
stk.empty() : 判断栈是否为空,空 返回 true
stk.push(x) : 将x添加到栈
stk.pop() : 弹出栈顶,返回为空
stk.size() : 返回栈存储元素个数
队列
先进先出的线性表
#include <queue>
queue<int> que;
que.front() : 返回队列头部元素
que.back() : 返回队列尾部元素
que.empty() : 判断队列是否为空
que.push(x) : 将x添加到队列
que.pop() : 弹出队列头部元素
que.size() : 返回队列存储元素个数
堆
非线性数据结构,有两个直接后继。可被视为一颗完全二叉树的数组对象,且堆中某个节点的值总是不大于或不小于其父节点的值。其中,根节点最大的称为最大堆,最小的称为最小堆
#include <queue>
std::priority_queue<int> big_heap; //默认构造方法:最大堆
std::priority_queue<int, std::vector<int>, std::greater<int>> small_heap; //最小堆构造方法
std::priority_queue<int, std::vector<int>, std::less<int>> big_heap; //最大堆构造方法
big_heap.empty() : 判断堆是否为空
big_heap.push(x) : 将元素添加到二叉堆
big_heap.pop() : 弹出堆顶元素
big_heap.top() : 返回堆顶元素
big_heap.size() : 返回堆元素个数
7道经典题目
1. 使用队列实现栈(easy)(栈、队列)
题目说明:
设计一个栈,支持基本的栈的操作,栈的内部存储数据结构为队列
目的:更好的了解栈和队列的性质
class MyStack {
private:
std::queue<int> _data;
public:
MyStack(){}
void push(int x){ //思路可参照汉诺塔问题,不过由于队列两头都可以出,所以会简单一点
std::queue<int> temp_queue;
temp_queue.push(x);
while (!_data.empty()){
temp_queue.push(_data.front());
_data.pop();
}
while (temp_queue.empty()){
_data.push(temp_queue.front());
temp_queue.pop();
}
}
void pop(){
_data.pop();
return;
}
int top(){
return _data.front();
}
bool empty(){
return _data.empty();
}
};
2. 使用栈实现队列(easy)(栈、队列)
题目说明:
设计一个队列,支持基本的队列的操作,队列的内部存储数据结构为栈
目的:更好的了解栈和队列的性质
class MyQueue {
private:
std::stack<int> _data;
public:
MyQueue(){}
void push(int x){
std::stack<int> temp_stack;
while (!_data.empty()){
temp_stack.push(_data.top());
_data.pop();
}
_data.push(x);
while (temp_stack.empty()){
_data.push(temp_stack.top());
temp_stack.pop();
}
}
void pop(){
_data.pop();
return;
}
int front(){
return _data.top();
}
int back(){
std::stack<int> temp_stack;
while (!_data.empty()){
temp_stack.push(_data.top());
_data.pop();
}
return temp_stack.top();
}
bool empty(){
return _data.empty();
}
};
3. 包含min函数的栈(easy)(栈)
题目说明:
设计一个栈,支持push、pop、top、getMin(返回栈内最小元素 )等功能,并保证时间复杂度为常数
难点分析:栈的pop操作会导致最小元素动态变化,所以我们需要以动态规划的方式记录最小值:dp(n) = x > dp(n-1) ? dp(n-1) : x;
class MinStack {
private:
std::stack<int> _data;
std::stack<int> _min; //增加空间复杂度来换取时间复杂度
public:
MinStack(){}
void push(int x){
_data.push(x);
if (_min.empty()){
_min.push(x);
}
else{
if(x > min.top()){
x = min.top(); //_data每一次元素都对应_min相应元素,代表截至该层 栈内的最小元素
}
_min.push(x);
}
}
void pop(){
_data.pop();
_min.pop();
}
int top(){
return _data.top();
}
int getMin(){
return _min.top();
}
bool empty(){
return _data.empty();
}
};
4. 合法的出栈序列(medium)(栈、队列)
题目说明:
已知从1至n的数字序列,按顺序入栈,每个数字入栈后可选择立即出栈,也可以在栈中停留,等待后边数字入栈出栈后再出栈。
求某一数字序列是否原始数字序列按上述规则操作后的结果
举例:
[3,2,5,4,1] <-- { [1,2,3,4,5] -> 1进栈 -> 2进栈 -> 3进栈 -> 3出栈 -> 2出栈 -> 4进栈 -> 5进栈 -> 5出栈 -> 4出栈 -> 1出栈 } , 故合法
思路:模拟正常序列向给定序列的变化工程,将i先插入到中间栈中,对比中间栈顶部元素与给定序列顶部元素,相同则不断pop,否则,继续将元素push到中间栈,最后检查中间栈是否为空来判断是否合法
class Solution {
public:
bool check_is_valid_order(queue<int> &order){
stack<int> stk;
int n = order.size();
for(int i = 1; i <= n; i++){
stk.push(i);
while(!stk.empty() && stk.top() == order.front()){
stk.pop();
order.pop();
}
}
if (!stk.empty()){
return false;
}
return true;
}
};
5. 简单的计算器(hard)(栈)
题目说明:
设计一个计算器,输入一个字符串存储的数字表达式,可以计算包括"("、")"、"+"、"-"四种符号的数字表达式,输入的数学表达式字符串保证是合法的。输入表达式中可能存在空格字符
举例:
“(1+1)” = 2
思路:状态机 + 栈
class Solution {
public:
int calculate(string s){
static const int STATE_BEGIN = 0;
static const int STATE_NUMBER = 0;
static const int STATE_OPERATION = 0;
stack<int> number_stack;
stack<char> operation_stack;
int number = 0;
int STATE = STATE_BEGIN;
int compuate_flag = 0;
for(int i = 0; i < s.length(); i++){
if(s[i] == ' ') continue;
switch(STATE){
case STATE_BEGIN:{
if(s[i] >= '0' && s[i] <= '9')
STATE = STATE_NUMBER;
else
STATE = STATE_OPERATION;
i --;
break;
}
case STATE_NUMBER:{
if(s[i] >= '0' && s[i] <= '9')
number = number * 10 + s[i] - '0';
else{
number_stack.push(number);
if(compuate_flag)
compute(number_stack, operation_stack);
number = 0;
i--;
STATE = STATE_OPERATION;
}
break;
}
case STATE_OPERATION:{
if(s[i] == '+' || s[i] == '-'){
operation_stack.push(s[i]);
compuate_flag = 1;
}
else if(s[i] == '('){
STATE = STATE_NUMBER;
compuate_flag = 0;
}
else if(s[i] >= '0' && s[i] <= '9'){
STATE = STATE_NUMBER;
i--;
}
else if(s[i] == ')'){
ccompute(number_stack, operation_stack);
}
break;
}
}
if (number != 0){
number_stack.push(number);
compute(number_stack, operation_stack);
}
if (number == 0 && number_stack.empty()){
return 0;
}
return number_stack.top();
}
}
};
6. 数组中第K大的数(easy)(堆)
题目说明:
已知一个未排序的数组,求这个数组中第K大的数字
class Solution {
public:
int findKthLargest(vector<int>& nums, int k){
priority_queue<int, vector<int>, greater<int>> small_heap;
for(int i = 0; i < nums.size(); i++){
if (i < k){
small_heap.push(nums[i]);
}
else if (nums[i] > small_heap.top()){
small_heap.pop();
small_heap.push(nums[i]);
}
}
return small_heap.top();
}
};
7. 寻找中位数(hard)(堆)
题目说明:
设计一个数据结构,该数据结构动态维护一组数据,且支持以下操作:
添加元素
返回中位数(如果为个数为奇,返回平均值)
思路:
动态维护一个最大堆与一个最小堆,最大堆存储一半数据,最小堆存储一半数据,维持最大堆堆顶比最小堆堆顶小
class MedianFinder {
public:
priority_queue<int, vector<int>, greater<int>> small_queue; //最小堆构造方法
priority_queue<int, vector<int>, less<int>> big_queue; //最大堆构造方法
MedianFinder(){
}
void addNum(int num){
if (big_queue.empty() || num <= big_queue.top()){
big_queue.push(num);
if (big_queue.size() > small_queue.size() + 1){
small_queue.push(big_queue.top());
big_queue.pop();
}
}
else{
small_queue.push(num);
if (big_queue.size() < small_queue.size()){
big_queue.push(small_queue.top());
small_queue.pop();
}
}
}
double findMedian(){
if (big_queue.size() > small_queue.size())
return big_queue.top();
return (big_queue.top() + small_queue.top()) / 2.0;
}
};