栈、共享栈、单调栈

栈介绍

         栈可以理解为是一种受约束的线性表或链表,他们都是线性存储的,但是栈必须满足后进先出。与单链表类似,栈可以使用数组模拟和链式存储,数组模拟通常以数组下标0位栈底,而链式则以头结点作为栈顶。如下所示,左边为数组模拟栈的逻辑结构,右边为链式存储栈的逻辑结构,由于栈只进行头部操作,因此是否使用头结点效果一样。

         栈的一切操作都与top指针有关。如下图1所示,当创建栈时初始化该数据结构,将top指针指向-1的位置如图2~6所示,分别表示使用栈进行入栈出栈操作,这两种操作都是让指针进行上下移动来完成此项操作的。

  数组模拟栈代码

        在进栈和出栈时,两者运算顺序是不同的,进栈时是先移动指针再入栈,否则会导致数组在下标为-1出赋值。初始化可以将top设置为为0,但对应的后序代码进栈出栈以及判断都需要配套。

#include <iostream>
using namespace std;

const int N = 3;

typedef struct Stack {
	int sta[N];
	int top;
}Stack;

void Init_Stack(Stack &s)	//初始化
{
	s.top = -1;
}

bool Push_Stack(Stack& s, int e)	//入栈
{
	if (s.top < N - 1)
	{
		s.sta[++ s.top] = e;
		return true;
	}
	return false;
}

bool Empty_Stack(Stack& s)
{
	if (s.top == -1)	return true;
	else return false;
}

bool Pop_Stack(Stack& s, int &res)	//出栈
{
	if (!Empty_Stack(s))
	{
		res = s.sta[s.top];
		s.top--;
		return true;
	}
	return false;
}

void Print_Stack(Stack& s)
{
	if (Empty_Stack(s))	return;
	for (int i = 0; i <= s.top; i++)	cout << s.sta[i] << ' ';
	puts("");
}

int main()
{
	Stack s;
	Init_Stack(s);

	int op, res;
	while (true)
	{
		cin >> op;
		if (op == 1)
		{
			cin >> res;
			bool flag = Push_Stack(s, res);
			cout << flag << endl;
		}
		else if (op == 2)
		{
			bool flag = Pop_Stack(s, res);
			if (flag == true)
				cout << res << endl;
		}
		else if (op == 3)
		{
			Print_Stack(s);
		}
		else break;
	}

	return 0;
}

栈的应用

递归函数

        栈的应用最广泛的就是函数的调用,递归函数是系统实现的栈不需要手写。如下代码时实现斐波那契数列的递归函数。

int a[100]

int dp(int n)
{
    if (n == 1 || n == 2)    return 1;
    else return dp(n-1) + dp(n-2);
}

        递归的思路与人类的思维方式相反,对于人而言第n个数是在n-1和n-2两个数已知的情况下退出的,而递归即使不知道n-1是多少可以找n-2与n-3……直到找到确定的数。因此同样的代码,如果写成循环更适合人脑去理解。

int a[100]
a[1]=a[2]=1;
for(int i=3;i<n;i++)
    a[i]=a[i-1]+a[i-2];

 DFS深度优先搜索

        深度优先搜索是基于栈来实现的一种算法,其基本思想是一条路走到黑,从起点出发以一种方式直到找到结果或无法继续找结果才返回。DFS经典的处理问题:全排列、树的深度、找迷宫的出口、N皇后问题……以全排列为例,如下图所示,从根出发的第n层表示全排列后的第n个数字,层层往下去掉已选过的数字即可。

        代码如下,开一个额外的数组q来记录哪些数字已被使用,递归玩需要还原。同样的数组p也是采用栈的思想来模拟的,当栈满了表示已经到叶子数即可输出。这里k是树的层数,而m是用来记录每层中的其中一个分支的。

#include<iostream>
#include<cstring>
using namespace std;
const int N=7;
int a[N],n,p[N],m=0;
bool q[N];
void dfs(int k)
{
    if(k==n)
    {
        for(int i=0;i<n;i++)    cout<<p[i]<<' ';
        cout<<endl;
    }
    else
    {
        for(int i=0;i<n;i++)
        {
            if(!q[i])
            {
                p[m++]=a[i];
                q[i]=1;
                dfs(k+1);
                q[i]=0;
                m--;
            }
        }
    }
}
int main()
{
    cin>>n;
    memset(q,0,sizeof(q));
    for(int i=0;i<n;i++)    a[i]=i+1;
    dfs(0);
}

同样的迷宫问题也类似,定义运动的四个方向,起点相当于是四叉树的根,每一个节点下方都有四个方向,沿着一条路一直走下去即可。同时对边界进行判断是否出了迷宫的边界。 

进制转换

        在认为推到10进制转2进制时,让数值一直除二取余最后再余数倒读。除二取余的过程就是不断的进栈,余数倒读就是不断的出栈。

#include <stack>
#include <iostream>

using namespace std;
stack<int> s;

void conversion(int n)
{
    while (n)
    {
        s.push(n % 2);
        n /= 2;
    }

    while (s.size())
    {
        cout << s.top() << endl;
        s.pop();
    }
}

括号匹配的检验

        使用栈在进栈之前进行判断,栈顶元素是否与进栈元素能匹配成一组括号,能则出栈;不能则入栈。

行编辑程序

        个人理解为程序输入的缓冲区,输入数据为进栈,删除为出栈,回车表示遍历整个栈。

表达式求值

        需要两个栈,一个记录符号,一个记录数据,就是用代码来实现平时的四则运算,可以参考下面的题。本题来自acwing,代码参考资料yxc,如果是课程题目需要付费才能提交代码一下链接仅供尝试

3302. 表达式求值 - AcWing题库

3302. 表达式求值

给定一个表达式,其中运算符仅包含 +,-,*,/(加 减 乘 整除),可能包含括号,请你求出表达式的最终值。

注意:

  • 数据保证给定的表达式合法。
  • 题目保证符号 - 只作为减号出现,不会作为负号出现,例如,-1+2,(2+2)*(-(1+1)+2) 之类表达式均不会出现。
  • 题目保证表达式中所有数字均为正整数。
  • 题目保证表达式在中间计算过程以及结果中,均不超过 231−1231−1。
  • 题目中的整除是指向 00 取整,也就是说对于大于 00 的结果向下取整,例如 5/3=15/3=1,对于小于 00 的结果向上取整,例如 5/(1−4)=−15/(1−4)=−1。
  • C++和Java中的整除默认是向零取整;Python中的整除//默认向下取整,因此Python的eval()函数中的整除也是向下取整,在本题中不能直接使用。

输入格式

共一行,为给定表达式。

输出格式

共一行,为表达式的结果。

数据范围

表达式的长度不超过 105105。

输入样例

(2+2)*(1+1)

输出样例:

8
#include<iostream>
#include<unordered_map>
#include<stack>
#include<string>
using namespace std;
stack<int>num;
stack<char>op;
unordered_map<char, int>Hash{ {'+',1},{'-',1},{'*',2},{'/',2} };
void eval()
{
    int res;
    int a = num.top();
    num.pop();
    int b = num.top();
    num.pop();
    char c=op.top();
    op.pop();
    if (c == '+')   res = b + a;
    else if (c == '-')   res = b - a;
    else if (c == '*')   res = b * a;
    else if (c == '/')   res = b / a;
    num.push(res);
}
int main()
{
    string str;
    cin >> str;
    for (int i = 0; i<str.size(); i++)
    {
        if (isdigit(str[i]))    //存入数字
        {
            int res = 0;
            while (i < str.size() && isdigit(str[i]))
            {
                res = res * 10 + str[i] - '0';
                i++;
            }
            num.push(res);
            i--;
        }
        else if (str[i] == '(')    op.push(str[i]);
        else if (str[i] == ')')    
        {
            while (op.top() != '(')    //括号不匹配一直计算到匹配的括号为止
                eval();
            op.pop();
        }
        else
        {
            while (op.size()&&op.top()!='('&&Hash[op.top()]>=Hash[str[i]])
                eval();
            op.push(str[i]);
        }
    }
    while (op.size())    eval();    //可能存在不含括号的结果或()+()
    cout << num.top() << endl;

    return 0;
}

共享栈

        共享栈将数组的最大最小值作为两个栈的栈底,让两个栈顶向中间延伸。0号1号栈为空:top0=-1,top1=MaxSize;栈满:top1-top0=1。共享栈提高了空间利用率。

单调栈

         单调栈是让每一个进栈的元素是栈里面最大或最小的一个元素。这样可以快速寻找每一个元素其左边第一个比它小或大的元素。如下案例:找每一元素左边第一个比它小的元素。

输入样例:

5
3 4 2 7 5

输出样例:

-1 3 -1 2 2

        由于要找第一个比它小的数,因此只需要让栈顶元素是最大数即可,如果发现入栈的数小于栈顶元素就出栈至比它小即可,如果发现没有元素可以出栈了那么就是-1同时入栈。这样就可以保证栈里面的元素是恒单调的。

        根据样例如下图所示,蓝色为栈里面的元素,灰色是出栈的元素。当2入栈时所有元素都比它大所有都出栈,当5进栈时7比它大就让他出栈,最后栈里面就存在2、5两个数。

         下面代码中index表示该栈的指针,判断栈不为空且入栈元素不满足单调就持续出栈;只要判断出栈完毕后栈的状态来分支输出,最后将新元素入栈,如果要找右侧第一个比自己大或小的数只需要将数据倒读即可。代码参考自yxc

#include<iostream>

using namespace std;

const int N=100010;

int a[N],index;

int main()
{
    int n; 
    cin>>n;
    
    while(n--)
    {
        int x;  cin>>x;
        
        while(index&&x<=a[index-1])  index--;
        
        if(index)  cout<<a[index-1]<<' ';
        else        cout<<-1<<' ';
        
        a[index++]=x;
    }
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值