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

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;

至此大功告成。

#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;
}

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值