赞
踩
首先,我们需要理解题意。
我需要思考一个重要问题,什么是合法栈序列?
这里,读者可以自行思考一会。下面笔者会给出图解与文字说明。
首先第一个要义 “按顺序入栈” ,这是什么意思呢?
其实很简单,如果 n 入栈,那么其之前未出栈的所有车次均已入栈。
例如:n = 3时,且前面车次均未出栈
如果, n = 3, 但是2已经出栈,走了,上班去了,那么此时。
那么车站中,只有3、1在休息。
也就是说,栈中“休息”的元素,自底向顶有序但不一定连续。
接下来就是出栈的要义,这个就很容易理解了,出栈的元素有且只能时栈的首元素。
下面我们通过分析 “3412” 是否合法来简单捋一遍我们的思路。
首先,这是一个出栈序列,那么要抛出三意味着,栈中应当有:1、2、3(自底向顶)
抛出3后,为了准备抛出4,说明栈中应当有:1、2、4(自底向顶、3在上一轮中被派出干活了,故不应再栈中休息)
接下来,抛出 1。不能抛出,原因如下:抛出4后,2才是栈的首元素,而非1,所以1不能比2先抛出,所以“3412”非法。
理解完题目意思后,我们可以实现我们的代码了。一道题的解题思路是多样的,你可以正反面、可以检验正确性。
而这道题,因为其特殊性,我可以对每一个全排列进行检验,看看其是否是 “出栈序列” 即可(题目稍加限制,我们就检验到20个合法的出栈序列就可以结束我们的程序了)。
那么这种解法,我放在最后。
我们先来写写,正面解决 -- 模拟。
我相信大家再看题目的时候,一定发现了 “出栈序列” 就是 符合特定规则的 “全排列”
而枚举全排列,递归可就冲锋在前了,就算是检验法也不能绕开这一逻辑
那么我们心里大概有了一个底 , 大概需要 一个arr[] 存储枚举状态 ;vis[]存储访问状态;n来表示枚举元素最大值、枚举长度;cur_pos来标记现在的枚举位置;
但是这还没有结束,这道题还有他的特殊限制----栈,当然你可以使用栈直接来,事实上我也看到一些大佬用“递归+栈”直接来模拟整个过程。但是这里我采取一点变动,栈的核心有“进出”“完全包含关系”“以小问题撬动大问题”
当然这里没有那么复杂,我们只需要模拟栈的头元素变化即可,也就是“进出”关系
那么,思路又清晰了,我们需要一个 int x_h来模拟栈元素了变化
总结:arr[] 存储枚举状态 ;vis[]存储访问状态;n来表示枚举元素最大值、枚举长度;cur_pos来标记现在的枚举位置;int x_h来模拟栈元素了变化;times 记录 输出次数
于是我们可以定义出,或者说书写出以下框架:
#include <iostream>
#include <algorithm>
int arr[20], vis[21] = { 0 },times = 0;
void f(int x_h, int cur_pos, int n);
int main(){
int n;
std::cin >> n;
f(1, 0, n);
return 0;
}
整体框架搭好了,接下来,我们需要完善我们的递归函数f(...)就好了。
接下来,我们从需要进行的行为来模拟设计即可。
行为表:
1.只能抛出 x >= x_h;
2.先枚举自顶向下直接抛出的(向下扩展);例如 “321” “21” “4321” ---> if x==x_h
3.后枚举 先压入---再抛出的(向上扩展);例如“34”、“35” ---> if x > x_h
4.向下扩展每次只能抛出1个(扩展一次),也就是只能抛出第一个未访问结点。
void f(int x_h, int cur_pos, int ){
if(times == 20) return ;
if(cur_pos == n){
print_a_result(arr,n);
times++;
return;
}
//向下扩展,头节点在之前就抛出了;
if(vis[x_h] == 1){
for(int i = x_h-1; i >= 1; --i){
//向下(底)查找为访问元素
if(vis[i] == 0){
vis[i] = 1;
arr[cur_pos] = i;
f(i , cur_pos + 1, n);
vis[i] = 0;
//只扩展一次
break;
}
}
}
//向上扩展;
for(int i = x_h; i <= n; ++i){
if(vis[i] == 0){
vis[i] = 1;
arr[cur_pos] = i;
//i-1模拟了 填充至 i 再抛出后的 进出关系;
f(i-1, cur_pos, n);
vis[i] = 0;
}
}
return;
}
现在我们来完成 f(...) 中的print_a_result(...);
void print_a_result(int a[], int n){
for(int i = 0; i<n; ++i){
printf("%d",a[i]);
}
printf("\n");
return ;
};
至此,我们的程序完成,但是还有一个小小的bug,这是vis[]定义造成的,因为我定义开21个也就是vis[0] == 0;
但是我们不想让0出现,就要将vis[0] = 1;
至此大功告成。
- #include <iostream>
- #include <algorithm>
- using namespace std;
-
- int arr[20], vis[21] = {0};
- int times = 0;
-
- void print_a_result(int a[], int n) {
-
- for (int i = 0; i < n; ++i) {
- printf("%d", a[i]);
- }
- printf("\n");
-
- return;
- }
-
- void f(int x_h, int cur_pos, int n) {
- //if (x_h < 1) x_h = 1;
- if (times == 20) return;
-
- if (cur_pos == n) {
- print_a_result(arr,n);
- times++;
- return;
- }
- //向下扩展一个
- if (vis[x_h] == 1) {
- for (int i = x_h-1; i >= 1; i--) {
- if (vis[i] == 0) {
- vis[i] = 1;
- arr[cur_pos] = i;
- f(i, cur_pos + 1, n);
- vis[i] = 0;
- break;
- }
- }
- }
- //向上扩展
- for (int i = x_h; i <= n; ++i) {
- if (vis[i] == 0) {
- vis[i] = 1;
- arr[cur_pos] = i;
- f(i - 1, cur_pos + 1, n);
- vis[i] = 0;
- }
- }
- return;
- }
-
- int main() {
- int n;
- cin >> n;
- vis[0] = 1;
- f(1, 0, n);
- return 0;
- }
下面,我附上检验法:代码解释见注释;
- #include <iostream>
- #include <algorithm>
- using namespace std;
-
- //整个过程中,栈内元素只会出现一次。(需要再进一步理解,一个元素抛出后就不可能在出现)
- bool IsValue(int a[], int n) {
- stack<int> stk;
- int x = 1;
-
- for (int i = 0; i < n; ++i) {
- //当处于初始状态(栈为空) 或者 栈顶元素小于a[i] 时需要压入操作
- if (stk.empty() || stk.top() < a[i]) {
- while (x <= a[i]) stk.push(x), ++x;
- }
- //抛出值不等于栈顶值 则不合法;特殊情况栈空,但需要抛出操作
- //不能调换顺序 -- 涉及短路或
- if (stk.empty() || stk.top() != a[i]) {
- return false;
- }
- //模拟抛出栈的动作、抛出必然实行。
- stk.pop();
- }
-
- return true;
- }
-
- int main() {
- int n, a[25], cnt = 20;
- cin >> n;
- //模拟最开始的全排列;
- for (int i = 1; i <= n; ++i) a[i - 1] = i;
- //开始操作
- do {
- //判断,如果符合题意则输出,并且计数器-1
- if (IsValue(a, n)) {
- for (int i = 0; i < n; ++i) {
- cout << a[i];
- }
- cout << endl;
- cnt--;
- }
- } while (next_permutation(a, a + n) && cnt);//如果下一个排列存在而且未输出满20个,继续;
-
- return 0;
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。