OJ题:火车进栈(用到很简单的二叉树前序遍历,以及很简单的栈操作,完全新手向解说!)

这道题其实很简单 ,我早上九点看到题后,下午五点都还没解决,直到三天后,我才恍然大悟,把这道题做了出来。
为了让其它人可以不像我那么苦恼,所以在此我要把我解决的思路讲出来,让观众开心开心
首先说一下题目:

题目描述
有 n 列火车按 1 到 n 的顺序从东方左转进站,这个车站是南北方向的,它虽然无限长,只可惜是一个死胡同,而且站台只有一条股道,火车只能倒着从西方出去,而且每列火车必须进站,先进后出。
现在请你按字典序输出所有可能的出栈方案。

输入
输入一行一个整数 n。
输出
按字典序输出所有答案,每行一种,之间用空格隔开。w

题目的意思

就像这样,第一辆火车进站后,可以马上出站,也可以等第二辆火车进站后再出站,或者第三辆。 所以,假设有三辆火车,正确输入输出如下
输入:
3
输出:
1 2 3
1 3 2
2 1 3
2 3 1
3 2 1
其中的3 1 2因为不符合规矩,被剔除了。

那么了解题目后,是时候说下我解决的思路了
(完整代码可以直接拉最后看噢,是c语言写的)

1辆火车有进站和出站两个动作,那么2辆火车就有4个动作,3辆6个…n辆就有n * 2个动作。
我只要用一个数组,记录下所有可能的动作,然后再用栈模拟这组记录,进行进站(压栈)和出站(弹栈),在弹栈的时候进行打印输出即可

那么难点主要有一个,如何记录下所有可能的动作,并剔除掉不符合现实的动作?
这里作者我就想到了用二叉树了,只要设定二叉树的左支是出站,右支是进站,那么一颗完整的二叉树即可表示完所有的动作,我们再剪掉不符合现实的分枝,就可以得到答案了(符合字典序的噢)。
比如这棵树,A、B、D路线肯定都是错的啦,不知道为什么的观众往下看…
(注意。。。我忘记给根结点画上+1了)
在这里插入图片描述
如我画的图(请忽略粗糙的画工,作者我也找不到好的工具唉)

比如我有2辆火车,那么一共会有4个动作,那么我的树就会有有4层,深度为4,每层代表一个动作(进、出站)。
-1代表出站,+1代表进站,我会用前序遍历,在每经过一个结点,我就用一个数组record记录下每个动作,比如名称为A的终端结点,到它那里,record={1,-1,-1,-1}。(注意,我默认根节点就为1了…)
很明显,只进1辆火车,却出了3辆火车,是不符合现实的,待会我再讲怎么在第3层就把它剪掉。
为什么我要用数组来记录呢,因为这样我就不用每次搜索都真的去压栈弹栈呀(滑稽),如果有数万条分支,每次都真的去动,会承受不住的!

下面我直接解释我的递归循环来前序遍历二叉树吧,不懂什么是前序遍历的翻一下我另一篇文章(即将会有,狗头),超简单的

void tree(SqStack* p, int* record, int fork, int deep) {
    //首先,我们进来了一个节点,先让record记录下该层是左分支还是右分支
    //应该-1还是+1(注意,其实-1、+1是我个人爱好,你们-9、+9都可以噢)
    //fork为-1是左,1是右,左出右进
    //比如我刚进来根节点,那么record[0] = 1,然后deep再自加1
    record[deep++] = fork;

    //该句是判断该支路是否符合火车进站的规矩
    //judge函数其实是数一下之前的进站次数和出站次数,然后返回0或1
    //不符合直接return,剪枝,可以省很多时间和内存噢
    if (!judge(record, deep, p->length)) return ;

    //如果深度到达限制,将停止继续探索
    //并开始用“真的栈”按照record的记录打印
    //这就是到达终端结点的标志,一切递归终结的地方
    if(deep == p->length * 2) {
        int val = 1;//用于进栈时赋值
        for (int i = 1; i <= p->length * 2; i++) {
            if (record[i-1] == 1) {
                push(p, val++);  //压栈,出来后val自加1
            } else if (record[i-1] == -1) {
                pop(p);  //弹栈,我写的pop里面有输出函数了
                if (i == p->length * 2) printf("\n");
                else printf(" ");//为了符合输出格式而添加的
            }
        }
        return ; //结束,返回上一层
    }
	//经过上面的重重判断后,发现没违反现实,也没到达终端,终于要生孩子了
    tree(p, record, -1, deep);  //进入左分支
    //从左边出来后,继续探索右边噢
    tree(p, record, 1, deep);  //进入右分支
    //都探索完后,返回上一层吧
    return ;
}

上面的代码就好像下图一样,按着箭头探索
在这里插入图片描述

经历探索后,成功到达终端结点的,就是record{1, -1, 1, -1}
按照record记录,用栈去跟着压栈弹栈后,将会输出 2 1,这就是我们想要的结果,3个数如此,4个数也如此,那么n个数也同样可以搞定。

肯定会有人问我judge函数又是怎么回事,我也就懒得费口水了,看下面吧,反正judge函数内容很简单的。
下面代码我把压栈弹栈的push和pop放man函数后面,方便大家观看主要内容,如果观众是用c++或其它语言写,不用学我那样造轮子了(哭)

完整代码如下,直接复制粘贴就可以用噢↓
别看很长,因为我注释占一半,c语言的轮子也占一半,如果c++,我十几行就搞定的!(假的)

#include <stdio.h>
#include <stdlib.h>

typedef int sType;

typedef struct {
    sType *data;
    int length;
    int top;
} SqStack;

SqStack* init(int );
void push(SqStack *, int);
void pop(SqStack *);
int judge(int *, int ,int );
void tree(SqStack *, int *, int , int );

//judge传入的三个参数分别是
//用于记录的record指针,当前层的深度,以及最大允许的深度
//通过record的记录,判断压栈和弹栈的次数有没有超过规定,从而返回0或1
int judge(int* record, int deep_now, int num_max) {
    int num_push = 0;  //压栈的次数
    int num_pop = 0;  //弹栈的次数
    for (int i = 0; i < deep_now; i++) {
        if (record[i] == 1) ++num_push;
        else ++num_pop;
    }
    if (num_push > num_max || num_pop > num_max 
        || num_push < num_pop) return 0;  //不符合,返回0
    return 1;  //符合,返回1
}


//tree的三个参数分别是栈,记录record
//值为-1或1的fork,以及深度deepo
void tree(SqStack* p, int* record, int fork, int deep) {
    //进来先记录下该层是左分支还是右分支
    //fork为-1是左,1是右,左出右进
    record[deep++] = fork;

    //判断该支路是否符合火车进站的规矩
    //不符合直接剪枝,可以省很多时间和内存
    if (!judge(record, deep, p->length)) return ;

    //如果深度到达限制,将停止继续探索,并开始用真的栈按照record的记录打印
    if(deep == p->length * 2) {
        int val = 1;//用于进栈时赋值
        for (int i = 1; i <= p->length * 2; i++) {
            if (record[i-1] == 1) {
                push(p, val++);  //压栈,出来后val自加1
            } else if (record[i-1] == -1) {
                pop(p);  //弹栈,我写的pop里面有输出函数了
                if (i == p->length * 2) printf("\n");
                else printf(" ");//为了符合输出格式而添加的
            }
        }
        return ; //结束,返回上一层
    }

    tree(p, record, -1, deep);  //进入左分支
    tree(p, record, 1, deep);  //进入右分支
    return ;
}


int main() {
    int n;
    scanf("%d", &n);
    SqStack* p = init(n);  //创建一个空栈,用来实操

    //用于作模拟记录,这样可以在搜索时不用真的去压栈弹栈
    //record占的大小是要两倍长的,因为压栈弹栈是两个动作
    //总共有n个数,那么就会有n*2个动作
    int* record = (int *)malloc(sizeof(int) * n * 2);
    
    //进入递归搜索
    //第三个参数传1进去,是让record[0]=1,即根结点为1
    tree(p, record, 1, 0);
    return 0;
}
//下面的内容可看可不看,主要是我c语言写的,要用来建栈,压栈和弹栈用的。

//建立一个空栈,返回值为指向该栈的指针
SqStack* init(int n) {
   SqStack* p = (SqStack *)malloc(sizeof(SqStack));
    p->data = (sType *)malloc(sizeof(sType) * n);
    p->length = n;
    p->top = -1;
    return p;
}

//进栈
void push(SqStack *p ,int val) {
    if (p->top == p->length) {
        printf("push error");
        return ;
    }
    p->data[++p->top] = val;
    return ;
}

//出栈
void pop(SqStack *p) {
    if (p->top == -1) {
        printf("pop error");
        return ;
    }
    printf("%d", p->data[p->top]);
    p->top--;
    return ;
}

好吧,写完后我才发现我写成这样有人能看的懂才怪…
请原谅是新手的我写的新手文吧…

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值