铁路栈问题

子渊暑假和爸爸妈妈一起回了一趟湖南老家,老家坐落在湘南丘陵的一个小山包上,交通非常不便,从宁波出发先要坐二十四小时火车,再坐三个多小时的汽车,最后步行两小时才到家。
       在火车站候车的时候,正好能够看见窗外的V字形站台,见子渊好奇地盯着站台看,爸爸心里有了一个主意。“子渊啊,爸爸有一个关于列车调度的问题,不知道你感不感兴趣?”

“列车调度?你是说我们今天要坐的这种列车吗?”
“是啊,铁路进行列车调度时, 常把站台设计成栈式结构的V字形站台,就像外面的那种(如右图所示)。那么现在我来考考你:
(1) 设有编号为1,2,3,4的四辆列车, 顺序开入栈式结构的站台, 则可能的出栈序列有多少种?如果是n辆列车呢?
(2) 若进站的四辆列车顺序如上所述, 那么是否能够得到1423,2413,3412和4312。的出站序列?为什么?
  
子渊从包里拿出纸笔计算起来,不一会纸上就写满了密密麻麻的答案:
可能的出栈序列:1234,1243,1324,1342,1432,2134,2143,2314,2341,2431, 3214,3241,3421,4321。
不可能的出栈序列:1423,2413,3124,3142,3412,4123,4132,4213,4231,4312。

如果是n辆列车,可能的出栈序列会有多少种?需要先将n取较小值时的可能序列数量计算出来,观察是否有规律,设n辆列车可能的出栈序列数量为F (n),则有:
F(1) = 1;
F(2) = 2;
F(3) = 5;
F(4) = 14;
子渊看来看去也找不出规律,只好做罢。
再来看看为什么1423,2413,3412和4312等序列是不可能出现的,子渊似乎发现了些什么:
在1423序列中,我们观察子序列423,发现2夹在4和3之间,即最大的数先出栈,最小的数中间出栈,中间的数最后出栈——这无论如何不可能出现,因为它违反栈“后进先出”的特点;同样2413序列中1夹在4和3之间,3412序列中1夹在4和2之间,4312序列中1夹在3和2之间,这都违反LOFI规律。
其他的不可能序列也可以按照同样的方法判断出来,但是由于知识结构的原因,虽然子渊能够看到这些,却不能从数学的角度加以归纳,也不能做出严格的数学证明。聪明的读者,如果你感兴趣的话,本文的后面有一些练习,其中练习2就是要求对下面的引理做一个数学上的严格证明。
子渊把自己思索的结果告诉了爸爸,并请爸爸解决自己的疑问。
爸爸首先对子渊独立思考的精神和思考问题的方法都给予了表扬,然后告诉他:
要求n辆列车可能的出栈序列总量,我们可以采用递归分治的思想(关于递归分治的详细内容,以后我会在系列文章中介绍)。我们把过程分成两步,根据组合数学计数原理中的乘法原理,总的可能性数量等于第一步的可能性数量和第二步的可能性数量之乘积。
我们以序号为n的列车为界,将列车分为两部分,一部分是在第n辆列车之前出栈的列车,另一部分是在第n辆列车之后出栈的列车。设在n号列车之前出栈的列车数量为i(0<=i<n),则在n号列车之后出栈的列车数量为(n – 1 – i)。前i辆列车出栈的可能性数量有F(i),后(n – 1 – i)辆列车出栈的可能性数量有F(n – 1 – i),所以总的数量为F(i)* F(n – 1 – i)。
因此我们可以得到一个递归公式:F(n) = F(0)*F(n-1) + F(1)*F(n-2) + F(2)*F(n-3) + ... + F(n-1)*F(n-n);(其中n>=1,F(0) = 1)。
我们把数据代人进行检验:
F(1) = F(0)*F(1-1) = 1;
F(2) = F(0)*F(2-1) + F(1)*F(2-2) = 1 + 1 = 2;
F(3) = F(0)*F(3-1) + F(1)*F(3-2) + F(2)*F(3-3) = 2 + 1 + 2 = 5;
F(4) = F(0)*F(4-1) + F(1)*F(4-2) + F(2)*F(4-3) + F(3)*F(4-4)= 5 + 4 + 4 + 5 = 14;
完全正确!
其实这与史上非常经典的数列——Catalan(卡特兰)数极其相似——就是n的初值少1。
Catalan数的递归公式是h(n) = h(1)*h(n-1) + h(2)*h(n-2) + …… + h(n-1)*h(1) (其中n>=2,h(1) = 1)。
后来又有人得到一个另类的递归公式:h(n) = ((4*n – 2) / (n + 1)) * h(n-1)(其中n>=2,h(1) = 1)。
根据递归公式我们可以得到数列的通项公式:
h(n) = C(2*n, n) / (n + 1) = (2*n)! / (n! * n! *(n + 1)) (其中n>=1)。
关于由递归公式推出数列通项公式的过程我们就不深究了,这里要用到生成函数等相关内容,感兴趣的话你可以自己去搜索有关Catalan数的更多知识。
至于第2个问题,我们可以用一个数学引理来归纳子渊发现的规律:
引理:以1…n顺序压栈的出栈序列为p1, p2,…, pn,对任意的pi而言,pi+1,…, pn中比pi小的数必须是按逆序排列的,即对任意的i < j < k而言,若pi > pj且pi > pk,则必有pj > pk。
关于引理的证明我把它作为本文的一个课后练习(练习4),有兴趣的读者可以自行证明。此外,练习5还要求根据该引理设计一个算法来判断某个输出序列是否正确。
这些问题我们暂时都先不去管,接下来看看爸爸又给子渊出了什么难题。
“子渊啊,刚才爸爸给你介绍的那些东西都是比较高深的数学知识,我猜你不太搞得懂,现在爸爸给你出一个简单点的题目,可以直接利用我们前面所学的栈来解决。”
“刚才我确实听得云里雾里的。什么问题能够直接用栈解决啊?真是太好了!”
“下面的问题需要模拟各个元素的入栈和出栈过程来解决:
若输入序列1, 2, 3, …, n,请模拟各元素的入栈和出栈过程,判断一个输出序列是否正确。设计一个算法来实现该功能,子函数接口为:
FUNCTION TrueList(inList, outList : List; len : integer) : BOOLEAN;
其中inList是存储了输出序列的数组,即inList[] = [1..n];outList是存储了该输出序列的数组,len为序列长度,输出序列正确返回true,否则返回false。
例如,当len= 4时,若outList [4] = [1,2,3,4],则返回true;若outList [4] = [1,4,2,3],则返回false。
子渊拿到题目后,大脑立刻高速运转起来:
要模拟所有列车的入栈和出栈过程,也就是要跟踪整个过程,直到出现错误或者列车全部出栈。在调度过程中,我用i表示入栈序列中最前方列车的编号(如果列车已经全部入栈,则i=len);用j表示当前出栈序列中出栈列车的编号(如果j = len,则表示所有列车都能出栈,出栈序列正确);用栈s存储当前停在站内的列车。初始时,i = j = 0,s为空。
在调度过程中:
如果j = len,则表示所有列车都能出栈,出栈序列正确;否则
如果inList[i] = outList[j],则表示列车进栈后马上出栈;否则
如果s非空且GetTop(s, top) = outList[j],那么让outList[j]出栈;否则
如果i<len,即还有列车未入栈,则让inList[i]入栈;否则
说明列车已全部进栈,但不满足后进先出原则,出错。
根据上述分析过程,子渊给出了代码:
{代码8:}
{模拟各元素的入栈和出栈过程,判断一个输出序列是否正确}
PROGRAM TrainList(INPUT, OUTPUT);
CONST
    MAXCAPACITY = 255; {栈的最大容量}
    BOTTOM = 0;        {栈底标志}

TYPE
    ElemType = integer;  {栈内元素数据类型}
    Stack    = array [1..MAXCAPACITY] of ElemType; {用数组表示的栈}
    List     = array [1..MAXCAPACITY] of ElemType; {用数组表示的出入栈序列}

VAR
    inList, outList : List; {定义出入栈序列}
    s    : Stack;       {定义s为栈}
    top  : integer;     {栈顶标志}
    i, n : integer;

。。。。。。{此处为栈的基本操作函数,不再重复列出}

FUNCTION TrueList(inList, outList : List; len : integer) : BOOLEAN;
var
    i, j : integer;
begin
    i := 1;
    j := 1;
    while j < len do {如果j = len,则表示出栈序列正确}
    begin
        if inList[i] = outList[j] then {列车进栈后马上出栈}
        begin
            inc(i);
            inc(j);
        end {if}
        else if (not StackEmpty(s, top)) and (GetTop(s, top) = outList[j]) then
        begin
            Pop(s, top); {出栈}
            inc(j);
        end {else if}
        else if i <= len then {还有列车未进栈,令其进栈}
        begin
            Push(s, top, inList[i]); {入栈}
            inc(i);
        end {else if}
        else    {列车已全部进栈,但不满足后进先出原则,出错}
        begin
            TrueList := false;
            exit;
        end; {else}
    end; {while}

    TrueList := true;
end; {TrueList}

BEGIN {MAIN}
    top := 0; {栈顶初始化}

    writeln('len of inList:');
    readln(n);
    for i:=1 to n do  {入栈序列}
        inList[i] := i;
  
    writeln('Input outList:');
    for i:=1 to n do  {出栈序列}
        read(outList[i]);

    if TrueList(inList, outList, n) then
        writeln('true!')
    else
        writeln('false!');
END.

随着最后一个字符地输入,火车进站的汽笛响了,子渊和爸爸妈妈赶紧收拾好行李,准备上车。漫漫的旅程开始了,正如我们的小子渊,踏上了算法学习的征程,前面的道路虽然曲折而遥远,但是前进的旅途中,我们总能欣赏到一道又一道美丽的风景。

  1. /******************************************************************************
  2.   Copyright (C), 2001-2011, Huawei Tech. Co., Ltd.
  3. ******************************************************************************
  4.   File Name     :
  5.   Version       :
  6.   Author        :
  7.   Created       : 2013/08/10
  8.   Last Modified :
  9.   Description   :
  10.   Function List :
  11.              
  12.   History       :
  13.   1.Date        : 2013/08/10
  14.     Author      :
  15.     Modification: Created file
  16. ******************************************************************************/ 
  17. #include <stdlib.h> 
  18. #include <string.h> 
  19. #include <stdio.h> 
  20. #include <iostream> 
  21. #include <stack> 
  22. #include <queue> 
  23. using namespace std; 
  24.  
  25. /*
  26. 详细描述:  
  27. int JudgeTrainSequence (int maxNum, char *pOutSeq);
  28. 输入参数:
  29.         int maxNum:进站的火车最大编号
  30.         char* pOutSeq:使用字符串表示火车出站序列
  31. 输出参数(指针指向的内存区域保证有效):
  32.         无。
  33. 返回值:
  34.         Int: 根据输入的进站序列判断,如果输入的出战序列是可能的,返回1,否则返回0;
  35. */ 
  36.  
  37. int JudgeTrainSequence (int maxNum, char *pOutSeq) 
  38.     stack<int> trainStack;     //用栈trainStack存储当前停在站内的列车 
  39.     queue<int> inSeq;        //用队列inSeq存储进栈列车 
  40.     int flag = 1;          //判断输入的出战序列 
  41.  
  42.     for (int i = 1; i <= maxNum; ++i) 
  43.     { 
  44.         inSeq.push(i + '0'); 
  45.     } 
  46.  
  47.     int outIndex = 0; 
  48.  
  49.     //队列inSeq.front()表示入栈序列中最前方列车的编号(如果列车已经全部入栈,则inSeq为空) 
  50.     //用outIndex表示当前出栈序列中出栈列车的编号 
  51.     //(如果outIndex = maxNum,则表示所有列车都能出栈,出栈序列正确) 
  52.      
  53.     while(outIndex < maxNum) 
  54.     { 
  55.         if(!trainStack.empty() && pOutSeq[outIndex] == trainStack.top())   
  56.         { 
  57.             trainStack.pop();    //入栈序列中最前方列车的编号和当前栈首位相等,则出栈 
  58.             ++outIndex; 
  59.             continue
  60.         } 
  61.         else if (!inSeq.empty())           //进栈还未结束 
  62.         { 
  63.             if(pOutSeq[outIndex] == inSeq.front())    
  64.             { 
  65.                 ++outIndex;  //入栈序列中最前方列车的编号和入栈序列中最前方列车的编号,则进栈后马上出栈 
  66.                 inSeq.pop(); 
  67.                 continue
  68.             } 
  69.             else                  //不等则入栈序列中最前方列车的编号进入trainStack 
  70.             { 
  71.                 trainStack.push(inSeq.front()); 
  72.                 inSeq.pop(); 
  73.                 continue
  74.             } 
  75.         } 
  76.         else 
  77.         { 
  78.             flag = 0; 
  79.             break
  80.         } 
  81.     } 
  82.  
  83.     return flag; 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值