当前位置:   article > 正文

火车进栈(栈--应试基础、必知必会)

火车进栈

ecd5ec1e447c4288839c4d0ebe985f92.png

a96c33929f8d4a2a8642b57d9690c0c6.png 

首先,我们需要理解题意。 

我需要思考一个重要问题,什么是合法栈序列?

这里,读者可以自行思考一会。下面笔者会给出图解与文字说明。

 

 

 

 

 

题意理解:

首先第一个要义 “按顺序入栈” ,这是什么意思呢?

其实很简单,如果 n 入栈,那么其之前未出栈的所有车次均已入栈。

 

例如:n = 3时,且前面车次均未出栈

de1d61beb43f4685812a08c9a00dd5d9.png

如果, n = 3, 但是2已经出栈,走了,上班去了,那么此时。

7170968acc674fab9d2a4cab6afdcd22.png 

那么车站中,只有3、1在休息。

也就是说,栈中“休息”的元素,自底向顶有序但不一定连续。

 

接下来就是出栈的要义,这个就很容易理解了,出栈的元素有且只能时栈的首元素。

下面我们通过分析 “3412” 是否合法来简单捋一遍我们的思路。

首先,这是一个出栈序列,那么要抛出三意味着,栈中应当有:1、2、3(自底向顶)

de1d61beb43f4685812a08c9a00dd5d9.png

抛出3后,为了准备抛出4,说明栈中应当有:1、2、4(自底向顶、3在上一轮中被派出干活了,故不应再栈中休息)

c8f8ec3ed85c488999becdaad0f78a60.png

接下来,抛出 1。不能抛出,原因如下:抛出4后,2才是栈的首元素,而非1,所以1不能比2先抛出,所以“3412”非法。

27227880bb6c49e8b803a3ee37498eed.png

 

 

 

代码实现:

理解完题目意思后,我们可以实现我们的代码了。一道题的解题思路是多样的,你可以正反面、可以检验正确性

而这道题,因为其特殊性,我可以对每一个全排列进行检验,看看其是否是 “出栈序列” 即可(题目稍加限制,我们就检验到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;

至此大功告成。

  1. #include <iostream>
  2. #include <algorithm>
  3. using namespace std;
  4. int arr[20], vis[21] = {0};
  5. int times = 0;
  6. void print_a_result(int a[], int n) {
  7. for (int i = 0; i < n; ++i) {
  8. printf("%d", a[i]);
  9. }
  10. printf("\n");
  11. return;
  12. }
  13. void f(int x_h, int cur_pos, int n) {
  14. //if (x_h < 1) x_h = 1;
  15. if (times == 20) return;
  16. if (cur_pos == n) {
  17. print_a_result(arr,n);
  18. times++;
  19. return;
  20. }
  21. //向下扩展一个
  22. if (vis[x_h] == 1) {
  23. for (int i = x_h-1; i >= 1; i--) {
  24. if (vis[i] == 0) {
  25. vis[i] = 1;
  26. arr[cur_pos] = i;
  27. f(i, cur_pos + 1, n);
  28. vis[i] = 0;
  29. break;
  30. }
  31. }
  32. }
  33. //向上扩展
  34. for (int i = x_h; i <= n; ++i) {
  35. if (vis[i] == 0) {
  36. vis[i] = 1;
  37. arr[cur_pos] = i;
  38. f(i - 1, cur_pos + 1, n);
  39. vis[i] = 0;
  40. }
  41. }
  42. return;
  43. }
  44. int main() {
  45. int n;
  46. cin >> n;
  47. vis[0] = 1;
  48. f(1, 0, n);
  49. return 0;
  50. }

 

 

下面,我附上检验法:代码解释见注释;

  1. #include <iostream>
  2. #include <algorithm>
  3. using namespace std;
  4. //整个过程中,栈内元素只会出现一次。(需要再进一步理解,一个元素抛出后就不可能在出现)
  5. bool IsValue(int a[], int n) {
  6. stack<int> stk;
  7. int x = 1;
  8. for (int i = 0; i < n; ++i) {
  9. //当处于初始状态(栈为空) 或者 栈顶元素小于a[i] 时需要压入操作
  10. if (stk.empty() || stk.top() < a[i]) {
  11. while (x <= a[i]) stk.push(x), ++x;
  12. }
  13. //抛出值不等于栈顶值 则不合法;特殊情况栈空,但需要抛出操作
  14. //不能调换顺序 -- 涉及短路或
  15. if (stk.empty() || stk.top() != a[i]) {
  16. return false;
  17. }
  18. //模拟抛出栈的动作、抛出必然实行。
  19. stk.pop();
  20. }
  21. return true;
  22. }
  23. int main() {
  24. int n, a[25], cnt = 20;
  25. cin >> n;
  26. //模拟最开始的全排列;
  27. for (int i = 1; i <= n; ++i) a[i - 1] = i;
  28. //开始操作
  29. do {
  30. //判断,如果符合题意则输出,并且计数器-1
  31. if (IsValue(a, n)) {
  32. for (int i = 0; i < n; ++i) {
  33. cout << a[i];
  34. }
  35. cout << endl;
  36. cnt--;
  37. }
  38. } while (next_permutation(a, a + n) && cnt);//如果下一个排列存在而且未输出满20个,继续;
  39. return 0;
  40. }

 

 

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/458085
推荐阅读
相关标签
  

闽ICP备14008679号