栈与队列学习

实现方式与基本操作:

        栈的特点:先进后出。

        队列的特点:先进先出。

        根据这两个特点实现的,提供接口的数据结构都是栈和队列。所以其实算是一个抽象概念,不是说就是库函数里面的stack与queue.

        库函数中的栈与队列:

        库函数提供push 和 pop 等等接口,所有元素必须符合先进后出规则,所以栈不提供走访功能,也不提供迭代器(iterator)。 不像是set 或者map 提供迭代器iterator来遍历所有元素。所以STL中栈与队列往往不被归类为容器,而被归类为container adapter(容器适配器)。

        底层实现:我们常用的SGI STL,如果没有指定底层实现的话,默认是以deque为缺省情况下栈的底层结构。deque是一个双向队列,只要封住一段,只开通另一端就可以实现栈的逻辑了。我们也可以指定vector为栈的底层实现或者list为队列的底层实现,初始化语句如下:

std::stack<int, std::vector<int> > third;  // 使用vector为底层容器的栈
std::queue<int, std::list<int>> third; // 定义以list为底层容器的队列

  常用接口:

        栈 stack:

操作含义
stk.push(ele)元素ele入栈,增加元素 O(1) 
s.pop()移除栈顶元素 O(1)
s.top()取栈顶元素,但不删除 O(1)
s.empty()判断当前栈是否为空,若为空返回1 O(1)
s.size()返回栈内元素的个数 O(1)

        队列 queue :

操作含义
que.front()返回队首元素大小 O(1)
que.back()返回队尾元素大小 O(1)
que.push()队尾添加元素入队O(1)
que.pop()队首元素出队 O(1)
que.size()返回队列大小 O(1)
que.empty()判断当前队列是否为空,若为空返回1 O(1)

        双端队列 deque:

                支持在两端插入与删除

操作含义
push_back(x) / push_front(x)把x插入队尾后/队首  O(1)
back() / front()返回队尾 / 队首元素 O(1)
pop_back() / pop_front()删除队尾 / 队首元素 O(1)
erase(iterator it)删除迭代器指向的某个元素
erase(iterator first,iterator last)删除[first,last)中的元素
empty()判断是否为空 O(1)
size()返回deque的元素数量 O(1)
clear()清空deque
begin()起始迭代器
end()末尾迭代器

                注意点:deque是可以排序的

        特殊的队列:priority_queue:

                本质上是一个大顶/小顶堆。

操作含义
top()返回当前堆中的最大/小值
push()向堆中加入元素
pop()堆顶(队首)元素出队
size()返回堆的大小
empty()判断是否为空

        注意点:

        ①  没有clear()

        ②  优先队列只能通过top()访问队首元素(优先级最高的元素)

        模板  :

template <class T, class Container = vector<T>,

                              class Compare = less<typename Container::value_type> >

class priority_queue;

        参数解释 :

        class T:T是优先队列中存储的元素的类型。

        class Container = vector<T>:Container是优先队列底层使用的存储结构,可以看出来,默认采用vector。

        class Compare = less<typename Container::value_type> :Compar是定义优先队列中元素的比较方式的类。

    关于第三个参数:
        ① 常见的参数:

         less<int> 这样创建的是大根堆,greater<int> 这样创建的是小根堆

priority_queue<int, vector<int>> q1; // 默认大根堆, 即每次取出的元素是队列中的最大值
priority_queue<int, vector<int>, less<int>> q2; // 大根堆
priority_queue<int, vector<int>, greater<int>> q3; // 小根堆, 每次取出的元素是队列中的最小值

        这里要注意:sort的排序中 less是 升序,小的在前面。 greater是降序,大的在前面。这里的less是大数优先级高,大数先输出,greatrer是小数优先级高,小数在前面。

        只需要记住参数含义跟sort是相反即可。

       ② priority_queue 中特殊的排序:

        pair,二元组           

排序规则:
        默认先对pairfirst进行降序排序,然后再对second降序排序
        对first先排序,大的排在前面,如果first元素相同,再对second元素排序,保持大的在前面。

#include <iostream>
#include<algorithm>
#include <queue>
#define  pii pair<int,int>
#define x first
#define y second
using namespace std;


int main() {
	
	priority_queue<pii, vector<pii>> q;
	q.push({ 7,8 });
	q.push({ 7,9 });
	q.push({ 9,8 });
	while (q.size())
	{
		cout << q.top().x << " " << q.top().y << endl;
		q.pop();
	}
	return 0;
}
③ 自定义排序写法:

        一定要用一个结构体或者是类去重载符号。为什么呢?因为人家参数里面就是一个class,所以自己写一个函数是不行的。但是sort可以自己写函数。

        如果现在我们相对pair中的排序按照second优先做降序:

#include <iostream>
#include<algorithm>
#include <queue>
#define  pii pair<int,int>
#define x first
#define y second
using namespace std;


struct cmp1
{
	bool operator()(const pii a, const pii b)
	{
		return a.y < b.y;
	}
};

int main() {
	
	priority_queue<pii, vector<pii>,cmp1> q;
	q.push({ 7,8 });
	q.push({ 7,9 });
	q.push({ 9,10 });
	while (q.size())
	{
		cout << q.top().x << " " << q.top().y << endl;
		q.pop();
	}
	return 0;
}

数组实现栈与队列 

        栈:

        定义 int stk[10000] , tt = 0,tt为指针,模拟栈的操作。

操作含义
stk[++tt] = k栈中加入一个数k
tt--栈中出去一个数
stk[k]取栈顶元素
tt == 0判断栈是否为空,返回1为空
tt表示栈内元素个数
int stk[10000], tt;  //tt初始化为0,则空栈条件就是tt==0
int main()
{
	//入栈
	int k;
	cin >> k;
	stk[++tt] = k;
	//出栈
	//tt--;
	//判断是否为空栈
	!tt ? "空" : "不空";
	for (int i = 0; i < 5; i++)
		stk[++tt] = i;
	while (tt)
	{
		cout << stk[tt] << endl;
		tt--;
	}
	return 0;
}

        队列:

        定义 int q[100000], hh = 0, tt = -1  q[]表示一个队列,hh是队首指针,tt是队尾指针。

操作含义
q[hh]队头元素大小
q[tt]队尾元素大小
q[++tt] = k往队列里加入一个k
hh++

队尾元素出队

tt - hh +1队中元素个数
hh<=tt判断是否队列为空
//数组模拟
//空队列 tt<hh
int q[1000], hh, tt = -1; // hh为头指针,tt 为尾指针
int main()
{
	int x;
	//入队列 
	/*q[++tt] = x;*/
	//出队列 
	/*hh++;*/
	for (int i = 0; i < 6; i++)
		q[++tt] = i;

	while (hh <= tt)
	{
		cout << q[hh] << endl;;
		hh++;
	}
	return 0;
}

        数组模拟栈与队列就是模拟头尾指针的移动。只要记住栈stk的栈顶指针tt从0开始开始,queue的队列头指针tt从-1开始,hh从0开始即可。(从其他值开始也可以,看个人习惯)

        练习题:

       力扣 232.用栈实现队列  

         232. 用栈实现队列

        两个栈就可以模拟队列的操作:一个进栈,一个出栈。

        入队操作:直接把元素放入进栈就可以了。

        出队操作:如果出栈中没有元素,则需要把进栈的元素全部压入出栈中,然后弹出出栈的一个元素。如果栈中还有元素,则直接弹出原来的元素就行了。

        返回栈顶元素:把出栈的元素弹出再弹入即可。

        code:

class MyQueue {
public:
    stack<int> stin,stout;
    MyQueue() {

    }
    
    void push(int x) {
        stin.push(x);
    }

    int pop() {
        if(!stout.size())
        {
            while(stin.size())
            {
                stout.push(stin.top());
                stin.pop();
            }
        }
        int res = stout.top();
        stout.pop();  
        return res;
    }
    
    int peek() {
        int res = this ->pop();
        stout.push(res);
        return res;
    }
    
    bool empty() {
        return stin.empty()&& stout.empty();
    }
};

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue* obj = new MyQueue();
 * obj->push(x);
 * int param_2 = obj->pop();
 * int param_3 = obj->peek();
 * bool param_4 = obj->empty();
 */
       力扣 225. 用队列实现栈

        225. 用队列实现栈

        题目说两个队列实现栈,其实一个队列就可以实现栈。

        入栈:把元素直接加入队列即可。

        出栈:如果队列不空的话,将队首元素弹出再弹入,重复size-1次即可。其实就是把队尾元素从队首弹出。

        code:

class MyStack {
public:
    queue<int> que;
    MyStack() {

    }
    
    void push(int x) {
        que.push(x);
    }
    
    int pop() {
        int n = que.size();
        while(--n) // n--做n次运算,--n做n-1次运算
        {
            int res = que.front();
            que.pop();
            que.push(res);
        }
        int res = que.front();
        que.pop();
        return res;
    }
    
    int top() {
        return que.back();
    }
    
    bool empty() {
        return que.empty();
    }
};

/**
 * Your MyStack object will be instantiated and called as such:
 * MyStack* obj = new MyStack();
 * obj->push(x);
 * int param_2 = obj->pop();
 * int param_3 = obj->top();
 * bool param_4 = obj->empty();
 */
力扣 20. 有效的括号

        20. 有效的括号

        不合法的情况一共有三种:

        1. 左括号多了 2.右括号多了  3.当前括号与前一个括号不匹配

        那该如何匹配呢?用栈记录。如何匹配左右括号?用哈希表比较好写一点。

        code:

class Solution {
public:
    stack<char> st;
    map<char,char> mp{{'[',']'},{'{','}'},{'(',')'}};
    bool isValid(string s) {
        for(auto i:s)
        {
            if( i == '('||i == '{'||i=='[')   
                st.push(i);
            else
            {
                if(!st.size()) return 0;
                if(mp[st.top()] == i)
                    st.pop();
                else return 0;
            }
        }
       return st.size() == 0;
    }
};
力扣1047. 删除字符串中的所有相邻重复项

        1047. 删除字符串中的所有相邻重复项

        匹配的问题用要想到用栈去解决

        code:

class Solution {
public:
    stack<char> sk;
    string removeDuplicates(string s) {
        for(auto i:s)
        {
            if(!sk.size()) sk.push(i);
            else
            {
                char ch = sk.top();
                if(ch == i)
                    sk.pop();
                else   sk.push(i);
            }
        }   
        string ans;
        while(sk.size())
        {
            ans += sk.top();
            sk.pop();
        }
        reverse(ans.begin(),ans.end());
        return ans;
    }   
};
力扣150. 逆波兰表达式求值

150. 逆波兰表达式求值

        逆波兰表达式的意思就是说 将运算符写在操作数之后

        (a+b)*c-(a+b)/e的后缀表达式为 ab+c*ab+e/-

        遇到数字就加入栈,遇到字符就把栈中前两个字符拿出来运算完再放进去,最后的答案就是结果了。

        code:  stoll表示string转换成longlong类型的整数。常见的是stoi

class Solution {
public:
    stack<long long> stk;
    int evalRPN(vector<string>& tokens) {
        for(int i = 0;i<tokens.size();i++)
        {
            if(tokens[i] =="+"||tokens[i] =="-"||tokens[i] =="*"||tokens[i] =="/")
            {
                long long x,y;
                x = stk.top();
                stk.pop();
                y = stk.top();
                stk.pop();
                if(tokens[i] == "+") stk.push(x+y);
                if(tokens[i] == "-") stk.push(y-x);
                if(tokens[i] == "*") stk.push(x*y);
                if(tokens[i] == "/") stk.push(y/x);
            }
            else stk.push(stoll(tokens[i]));
        }
        return stk.top();
    }
};
力扣239. 滑动窗口最大值

        经典来了。用队列来实现滑动窗口,这个队列的特点是:

        1. 是单调的队列:队列中的元素都是递增或者是递减的。

        2. 队列不是单向的,而是双端队列(deque),需要再两端进栈或者是出栈。

        为什么不用大顶堆去做?堆的首元素不就是最大值。因为这个窗口是移动的,而大顶堆每次只能弹出最大值,我们无法移除其他数值,这样就造成大顶堆维护的不是滑动窗口里面的数值了。所以不能用大顶堆。

        所以我们实现的是单调队列。

        对于队头方向:默认前提是队中元素已经是单调的了。每次出队列的判断:当前队列的长度超过了k。

        对于队尾方向:当前进队的数如果比最后的元素大,需要从队尾弹出所有比进队元素小的数。

        收集答案:如果数列长度为n = 10,窗口的大小规定为k = 3,最终的答案会是8个,所有一共会用 n - k + 1个答案。

        队列中记录的是数列的下标,为什么用下标?因为下标是从0开始严格单调递增的。我们可以利用下标去进行一些判断。

        code:   

        用deque的去实现:

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        deque<int> q;
        int n = nums.size();
        vector<int> ans;
        for(int i = 0;i<n;i++)
        {
            while(q.size() && i-q.front()+1 > k ) q.pop_front();
            while(q.size() && nums[i] >= nums[q.back()]) q.pop_back();
            q.push_back(i);
            if(i >= k - 1) ans.push_back(nums[q.front()]);
        }
        return ans;
    }
};

        用数组去实现:

class Solution {
public:
    vector<int> ans ;
    int que[100005], hh = 0, tt = -1; //hh为队头,tt为队尾
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        for(int i = 0;i<nums.size();i++)
        {
            while(hh<=tt && i-que[hh]+1 > k) hh++; // 判断队头存的下标
            while(hh<=tt && nums[que[tt]] <= nums[i]) tt--; //判断队尾的下标
            que[++tt] = i; // 插入到队尾
            if(i >= k - 1) ans.push_back(nums[que[hh]]); // 队头元素合法
        }
        return ans;
    }
};

        注意: 不管是栈还是队列,弹出的时候都要判断下是否已经空了。空的栈和队列是不能弹出的。

        力扣 347.前 K 个高频元素

        347. 前 K 个高频元素

        自然会想到用哈希表去记录每个元素出现的频次,但是有个问题,利用map我们可以很轻松的从key找到value,但是无法从value找到key。

        其实这就是个排序的问题。如果我们存完数据按照value去排序就好了。

        一种方法是利用小顶堆。如果堆中元素不等于k,我们就一直pop,最后留下的k个元素就是我们要的答案。

        code:   注意 priority_queue或者是map的cmp要写一个类或者是结构体。

class Solution {
public:
    class mycompare
    {
    public:
        bool operator()(pair<int,int>& a,pair<int,int>& b)
        {
            return a.second > b.second ;
        }    
    };
    unordered_map<int,int> mp;
    vector<int> topKFrequent(vector<int>& nums, int k) {
        for(auto i:nums) mp[i]++;
        priority_queue<pair<int,int>,vector<pair<int,int>>,mycompare> que;
        
        for(auto it = mp.begin();it!=mp.end();it++)
        {
            que.push(*it);
            if(que.size() > k)
                que.pop();
        }
        vector<int> ans;
        while(que.size())
        {
            int x = que.top().first;
            que.pop();
            ans.push_back(x);
        }
        return ans;
    }
};

        如果不想用priority的话,可以用vector的sort,按照second的降序去对pair排序即可。

        code:    sort 的 cmp是用函数实现的。

class Solution {
public:
    unordered_map<int,int> mp;
    vector<int> ans;
    vector<pair<int,int>> t;
    static bool cmp( pair<int,int> lhs, pair<int,int> rhs)
    {
        return lhs.second >= rhs.second;
    }
    vector<int> topKFrequent(vector<int>& nums, int k) {
        for(int i :nums) mp[i]++;
        for(auto it = mp.begin();it!=mp.end();it++)
            t.push_back(*it);
        sort(t.begin(),t.end(),cmp);
        for(int i = 0,j = 0;i<t.size();i++)
        {
            ans.push_back(t[i].first);
            j++;
            if(j == k) break;
        }
        return ans;
    }
};

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值