栈和递归
- 栈(stack)
- 例题 火车出入站
- 递归
- 例题 斐波那契数列 及 汉诺塔问题
栈(stack)
栈,是一种满足一定约束的线性数据结构。其约束是:只允许在栈的一端插入或删除元素,这一端被称为 栈顶;相对的,我们把另一端称为 栈底
可以想象一下往子弹夹中装入子弹的情形,正常情况下,只能往子弹夹入口端压入子弹,这一步就好比向栈中压入元素,我们称之为 push
;射击的时候,弹夹会从顶端弹出子弹,这一步就好比从栈顶弹出元素,我们称之为 pop
。
可以发现,从栈的顶端弹出的子弹是目前弹夹中最后一个被压入的子弹,这也体现了栈的另一个重要性质——先进后出:越早进入栈的元素,出来的时间越晚。
为了方便,通常用一个 top
来指示栈顶的位置。
// stack
#include<iostream>
using namespace std;
struct Stack {
int data[10000];
int top = -1;
void push(int x) { //插入顶部一个元素
top++;
if(top < 10000) {
data[top] = x;
} else {
top--;
cout << "stack overflow" << endl;
}
}
void pop() { // 删除顶部一个元素
if(top >= 0) {
top--;
}
}
int topval() { // 顶部元素的值
if(top >= 0) {
return data[top];
}
return 0;
}
};
int main() {
Stack s;
for (int i = 1; i <= 10; i++) {
s.push(i);
}
for (int i = 1; i <= 10; i++) {
cout << s.topval() << "";
s.pop();
}
return 0;
}
标准库里面的stack
在头文件<stack>
里面,它的定义和map
、set
、vector
都大同小异. stack<T> s
就定义了一个储存T
类型数据的栈 s
.
标准库的栈除了支持 push()
,pop()
等基本操作外,还支持 top()
来获取栈顶元素、 empty()
判断栈是否为空, size()
计算栈中元素的个数
栈stack<T>
的方法总结:
方法 | 功能 | 参数类型 | 返回值类型 |
---|---|---|---|
push | 压入元素到栈顶 | T 类型 |
无 |
pop | 弹出栈顶元素 | 无 | 无 |
top | 返回栈顶元素 | 无 | T 类型 |
empty | 栈是否为空 | 无 | bool 类型:false 表示不为空,true 表示栈为空 |
size | 栈的元素个数 | 无 | 非负整数(size_t 类型) |
火车出入站
给定n个数的排列a和一个栈,n个值的入栈顺序为
1,2,…,n,判断出栈顺序是否可以是排列a。
输入样例1
5
1 3 2 5 4
输出样例1
legal
输入样例2
5
1 5 3 2 4
输出样例2
illegal
#include<iostream>
#include<vector>
#include<stack>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i]; // 读入出栈的顺序
}
stack<int> s;
int cur = 1; // 记录当前还没有压入栈中的元素的起始位置
bool f = 1; // 记录当前出栈序列是否合法
for (int i = 0; i < n; i++) {
while ((s.empty() || s.top() != a[i]) && cur <= n) {
s.push(cur);
cur++;
}
if (s.empty() || s.top() != a[i]) {
f = 0;
break;
} else {
s.pop();
}
}
if(f) {
cout << "legal" << endl;
} else {
cout << "illegal" << endl;
}
return 0;
}
递归
所谓递归,就是函数调用函数自身,一个函数在其定义中有直接或者间接调用自身都叫递归。而递归一般都用来解决有重复子问题的问题
我们先来理解直接递归,间接递归非常复杂,用的较少。下面通过求解 n!(!代表阶乘)的问题来理解直接递归。由 n! = n * (n-1)!
得出下面代码
long long factorial(int n) {
if (n <= 1) { // 边界条件(无论在什么情况下,函数不应该再继续调用自身)
return 1;
}
return n * factorial(n - 1);
}
// 斐波那契额数列 1 1 2 3 5 8 …
#include<iostream>
using namespace std;
int fib(int n) {
if(n == 1 || n == 2) {
return 1;
} else {
return fib(n - 1) + fib(n - 2);
}
}
int main() {
int n;
cin >> n;
cout << fib(n) << endl;
return 0;
}
汉诺塔问题
汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三个柱子之间一次只能移动一个圆盘,如图所示:
实现一个汉诺塔
写一个程序打算完成的功能是,输入一个n表示圆盘的个数,然后输出每一步的操作
// 汉诺塔问题1
#include<iostream>
#include<stack>
using namespace std;
stack<int> S[3];
void move(int x, int y) {
int temp = S[x].top();
S[x].pop();
S[y].push(temp);
cout << x << " --> " << y << endl;
}
void hanoi(int A, int B, int C, int n) {
// A表示需要移动的圆盘一开始的位置 B表示中间柱 C表示目标柱 n表示当前需要移动的圆盘个数
if (n == 1) {
move(A, C);
return;
}
hanoi(A, C, B, n - 1);
move(A, C);
hanoi(B, A, C, n - 1);
}
int main() {
int n;
cin >> n;
for (int i = n; i >= 1; i--) {
S[0].push(i); // 把n到1压入到0号栈中
}
hanoi(0, 1, 2, n);
while (!S[2].empty()) {
cout << S[2].top() << " ";
S[2].pop();
}
return 0;
}