数据结构--【栈与队列】笔记

栈的应用【实验题】

使用栈实现后缀表达式计算,其中,在后缀表达式中,输入的数字为整数,且为正数,数字、符号之间用空格隔开,整个后缀表达式用“#”表示结束。其中,整个后缀表达式长度不超过200,每个数字位数不超过10。

提示:读取数据的过程中,可以利用栈处理每个数字。

输入样例:

11 2 3 + * #(注:对应的中缀表达式是11*(2+3))

6 2 3 + * 5 / 7 - #(注:对应的中缀表达式是6*(2+3)/5-7)

输出样例:

55

-1

#include<iostream>
#include<string>
using namespace std;
struct stack
{
	int a[300];
	int index = -1;
	void instack(int x)  //入栈操作
	{
		a[++index] = x;
	}
	void calculate(char k)  //k是运算符
	{
		int sum = 0;
		int k1, k2;
		k1 = showtop(); outstack();
		k2 = showtop(); outstack();
		if (k == '+')
		{
			sum = k1 + k2;
		}
		else if (k == '-')
		{
			sum = k2 - k1;
		}
		else if (k == '*')
		{
			sum = k1 * k2;
		}
		else if (k == '/')
		{
			sum = k2 / k1;
		}
		instack(sum);
	}
	void outstack() //出栈
	{
		index--;
	}
	int showtop()  //显示栈顶字符
	{
		return a[index];
	}
};
int main()
{
	stack f;
	string b;
	while (cin >> b && b != "#")
	{
		if (b == "+" || b == "-" || b == "*" || b == "/")
		{
			f.calculate(b[0]);
		}
		else
		{
			int num = stoi(b);
			f.instack(num);
		}
	}
	cout << f.showtop() << endl;
	return 0;
}

整体还是很简单的,主要操作步骤如下

1.写一个栈的结构,这里面设置栈结构(a[]数组),index索引指向目前栈顶,并包含入栈、出栈、显示栈顶字符操作

2.输入字符

3.写运算calculate函数

要注意的问题是cin可以隔断输入的string b,此时比如单个输入的"11"就是要给字符串b

string b;
	while (cin >> b && b != "#")
	{
		if (b == "+" || b == "-" || b == "*" || b == "/")
		{
			f.calculate(b[0]);
		}
		else
		{
			int num = stoi(b);
			f.instack(num);
		}
	}

同时calculate()也可以用switch写,更简洁一些

 void calculate(char k) {
        int right = a[index--]; // 右操作数(栈顶)
        int left = a[index--];  // 左操作数(次栈顶)
        int sum = 0;
        switch(k) {
            case '+': sum = left + right; break;
            case '-': sum = left - right; break;
            case '*': sum = left * right; break;
            case '/': sum = left / right; break; // 修正为 left / right
        }
        a[++index] = sum; // 结果入栈
    }

单调栈

这是种特殊的栈结构,其元素按照单调递增或单调递减的顺序排列

普通暴力方法是 O(n²) ,这个可以优化至 O(n)。

典型例题:

P5788 【模板】单调栈 - 洛谷

题目描述

给出项数为 n 的整数数列 a1…n​。

定义函数 f(i) 代表数列中第 i 个元素之后第一个大于 ai​ 的元素的下标,即 f(i)=mini<j≤n,aj​>ai​​{j}。若不存在,则 f(i)=0。

试求出 f(1…n)。

输入格式

第一行一个正整数 n。

第二行 n 个正整数 a1…n​。

输出格式

一行 n 个整数表示 f(1),f(2),…,f(n) 的值。

输入输出样例

输入 

5
1 4 2 3 5

输出 

2 5 4 5 0

说明/提示

【数据规模与约定】

对于 30% 的数据,n≤100;

对于 60% 的数据,n≤5×103 ;

对于 100% 的数据,1≤n≤3×106,1≤ai​≤109。

整体做法就是,用栈来存储索引值,先从前往后遍历数列,遇到第 i 个元素 x ,让它和栈顶元素比较

  • 如果栈顶元素小于它,就把栈顶元素踢出去,索引值 i 成为栈顶,此时栈顶索引对应的那个第一个比它大的索引就是 i 。
  • 如果栈顶元素大于等于它,保存原来栈顶那个元素,把 i 直接加进来。

这样就形成了一个单调递减的栈,遍历每一个元素,后面的元素如果比它前面那个小就会进去待命,反正前面那个大数的结果不是后面那个小数,它只会在遇到比它大的数的时候才会找到答案,此时前面那堆小的答案也是那个新遇到的大数,所以就叽里咕噜全跑出栈来了。

还有一个比较形象的例子,这个题就好像是很多人同时向右看,求看到第一个比自己高的人的那个位置

为什么可以将复杂度降到 O(n)?

我想这是一个一边遍历数组一边比大小的过程,因为在遍历数组的同时本身就形成了一个递减的顺序,所以能够同时遍历数组 + 比大小,高

#include<iostream>
using namespace std;
int res[3000000]; //盛放答案  res[1]的值是第一个大于首元素的元素索引
int st[3000000];  //盛放索引,索引对应的值从大到小排列
int num[3000000]; //盛放原先的数列
int p = 0;  //指针,首元素是1
int main()
{
	int n,x;
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		cin >> x;
		num[i] = x;
	}
	for (int i = 1; i <= n; i++)
	{
		while (p && num[i] > num[st[p]])
//num[st[p]]:表示当前st栈中栈顶元素(索引)对应的那个数值
		{
			int j = st[p];  //设一个整数j存索引值
			p--;
			res[j] = i;//该索引值第一个比它大的索引是i
		}
		p++;
		st[p] = i;  //不管num[i]是大是小都要入栈
	}
	for (int i = 1; i <= n; i++)
		cout << res[i] << " ";
	return 0;
}

出栈顺序判断

P4387 【深基15.习9】验证栈序列 - 洛谷

题目描述

给出两个序列 pushed 和 poped 两个序列,其取值从 1 到 n(n≤100000)。已知入栈序列是 pushed,如果出栈序列有可能是 poped,则输出 Yes,否则输出 No。为了防止骗分,每个测试点有多组数据,不超过 5 组。

输入格式

第一行一个整数 q,询问次数。

接下来 q 个询问,对于每个询问:

第一行一个整数 n 表示序列长度;

第二行 n 个整数表示入栈序列;

第三行 n 个整数表示出栈序列;

输出格式

对于每个询问输出答案。

输入输出样例

输入 

2
5
1 2 3 4 5
5 4 3 2 1
4
1 2 3 4
2 4 1 3

输出 

Yes
No

其实是一个模拟出栈过程的题 

#include<iostream>
#include<string.h>
using namespace std;
int push[100000];
int pop[100000];
int q, n;
struct stack 
{
     int st[100000];
	 int ttop = -1;  //top指向当前栈顶元素
	 void popp() { ttop--; }
	 void pushh(int x) { ttop++; st[ttop] = x; }
	 bool isempty() { return ttop == -1 ? 1 : 0; }
	 int top() { return st[ttop]; }
};
bool check()   //模拟出栈过程
{
	int cnt = 0;  //表示pop[]的索引
	stack s;
	for (int i = 0; i < n; i++)
	{
		s.pushh(push[i]);  //入栈
		while (s.top() == pop[cnt])  //当栈顶元素等于pop[cnt]的时候出栈
		{
			cnt++;    //pop[]顺便往后移
			s.popp();   //出栈
			if (s.isempty())   
//如果出完栈以后栈空了,就直接退出,因为原来那个s.top()不会判断栈有没有空,会出现错误
			{
				break;
			}
		}
	}
	if (s.isempty()) return true;
	else return false;
	while (!s.isempty())  //清空栈
	{
		s.popp();
	}

}
int main()
{
	for (cin>>q; q; q--)
	{
		cin >> n;
		for (int i = 0; i < n; i++)  cin >> push[i];
		for (int i = 0; i < n; i++)  cin >> pop[i];
		if (check()) cout << "Yes" << endl;
		else cout << "No" << endl;
	}
	return 0;
}

队列

滑动窗口

题目:设计算法解决下面的问题,要求算法时间复杂度尽可能低。

用一个长度为k的窗长度为n整数数列上从左往右(其中k<n每次滑动一个单位,出每次每个里面所包含的数的最大值。例如:当数列为[1, 3, -1, -3, 5, 3, 6, 7],窗口大小k=3,可以得出,

窗口位置

窗口内最

[1 3 -1] -3 5 3 6 7

3

1 [3 -1 -3] 5 3 6 7

3

1 3 [-1 -3 5] 3 6 7

5

1 3 -1 [-3 5 3] 6 7

5

1 3 -1 -3 [5 3 6] 7

6

1 3 -1 -3 5 [3 6 7]

7

输入数据格式:

第一行,一个整数,表示测试样例的数量,例如3表示3组测试样例

往后每两行对应一组测试样例,其中

1行,两个整数,分别表示整数数列的长度n和窗口大小k

2行,n整数,表示一个整数数列

样例输入:

3(共3组测试样例)

8 31组测试样例的nk

1 3 -1 -3 5 3 6 71组测试样例的整数数列)

9 4(第2组测试样例的nk

1 2 3 4 5 6 7 8 9(第2组测试样例的整数数列)

8 3(第3组测试样例的nk

8 7 6 5 4 3 2 1(第3组测试样例的整数数列)

输出样例:

3 3 5 5 6 71组测试样例的输出结果

4 5 6 7 8 9(第2组测试样例的输出结果)

8 7 6 5 4 3(第2组测试样例的输出结果)

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <iostream>

using namespace std;
int nums[200];
//定义测试样例个数,数列长度,窗口大小
int  n = 0, k = 0;
int result[100000];
int cnt = 0;
struct deque
{
	int front;
	int rear;
	int queue[200];
	deque(int x)
	{
		front = 0;
		rear = 0;
		for (int i = 0; i < 200; i++)
		{
			queue[i] = 0;
		}
	}
	bool empty()  //是否为空
	{
		return front == rear;
	}
	void push_back(int x)  //后端入列
	{
		queue[rear] = x;
		rear++;
	}
	void pop_back()  //后端出列
	{
		//queue[rear] = 0;
		rear--;
	}
	void pop_front()   //前端出列
	{
		//queue[front] = 0;
		front++;
	}
	int frontt()   //返回队首元素
	{
		return queue[front];
	}
	int rearr()  //返回队尾元素
	{
		return queue[rear];
	}
	void print()
	{
		for (int i = front; i < rear; i++)
		{
			cout << queue[i] << " ";
		}
		cout << endl;
	}
};
void check(int k,int* nums)
{
	deque dq(0);
	deque result(0);
	
	for (int i = 0; i < n; i++)
	{
		while (!dq.empty()&&dq.frontt()<= i-k-1)
		{
			dq.pop_front();
		}
		while (!dq.empty() && nums[i] >= nums[dq.rearr()])
		{
			dq.pop_back();
		}
		dq.push_back(i);
		if (i >= k - 1)
		{
			result.push_back(nums[dq.frontt()]);
		}
	}
	result.print();
}

int main()
{
	//输入重定向
	//FILE* instream;
	//instream = freopen("input.txt", "r", stdin);
	//int count = 0;

	//if (scanf("%d", &count) != 1)			//输入测试样例组数
	//	return 1;
	int count;
	cin >> count;
	for (int i = 0; i < count; i++)		//测试count组测试样例
	{
		cin >> n >> k;
		for (int i = 0; i < n; i++)
		{
 			cin >> nums[i];
		}
		check(k, nums);
	}
	//fclose(instream);
	return 0;
}

用双端队列 qu 存储 nums[ ] 中的索引,用 result[ ]存储结果

遍历nums[ ]中所有元素
{
1. 移除超出窗口范围的元素(条件:当 qu 队首的元素小于窗口范围的时候)
2. 引进新元素,维持单调队列
如果这个新元素在 nums 中大于 qu 队尾的元素的时候,把 qu 队尾的元素踢出去
不管新元素是大是小都把它添加进队列中
3.当窗口形成的时候开始把结果记录进 result 中(条件:当前遍历的元素 i > k-1 时)
}         

STL 双端队列的库函数

 需要包含头文件 <deque> 

声明: deque<int> dq

deque()默认构造函数,创建一个空的 deque 容器。
front()返回第一个元素的引用。
back()返回最后一个元素的引用。
begin()返回指向第一个元素的迭代器。
end()返回指向末尾元素后一位置的迭代器。
rbegin()返回指向最后一个元素的逆向迭代器。
rend()返回指向第一个元素之前位置的逆向迭代器。
empty()检查容器是否为空。
size()返回容器中的元素个数。
max_size()返回容器可容纳的最大元素个数。
clear()清除容器中的所有元素。
insert(iterator pos, const T& value)在 pos 位置插入 value 元素。
erase(iterator pos)移除 pos 位置的元素。
push_back(const T& value)在容器末尾添加 value 元素。
pop_back()移除容器末尾的元素。
push_front(const T& value)在容器前端添加 value 元素。
pop_front()移除容器前端的元素。
resize(size_type count)调整容器大小为 count,多出部分用默认值填充。
swap(deque& other)交换两个 deque 容器的内容。
get_allocator()返回一个用于构造双端队列的分配器对象的副本。

队列易犯的错误:

1.手写队列,在返回队尾元素的时候注意是 return queue[ rear-1 ] ,因为 rear 是指向队尾元素的下一个空元素的 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值