栈:装水的杯子(六)同步练习及参考答案

 

同步练习:

1.       请用链表实现栈的五个基本操作,并解决文章开头的数字分离问题。

 

2.       数制转换:十进制数n和其他d进制数的转换是计算机实现计算的基本问题之一,其解决方法很多,其中一个简单算法基于下列原理:

n = n div d* d + n mod d(其中div为整除运算,mod为求余运算)

例如:(134810 = 25048,其运算过程如下:

n      n div 8    n mod 8

1348    168       4

168     21        0

21      2         5

2       0         2

现在要求编制一个满足下列要求的子程序:对于输入的任意一个非负十进制整数,打印输出与其等值的d进制数。子函数接口为:

PROCEDURE Conversion (n, d : integer);

其中n表示一个非负十进制整数,d表示某种进制,保证1 < d < 10

 

3.  文章中我利用乘法原理推出了列车出栈序列总量的递归公式,后来又给出了Catalan数的通项公式,实际上,我们可以从另一个角度直接推导出该通项公式,下面是一道殊途同归的经典例题:

n*n的网格中,只允许向右或向上走,从左下角到右上角共有多少种不同的走法?

让我们再进一步: n*n的网格中,只允许向右或向上走,从左下角走向右上角,只能经过但不能超越从左下角到右上角的对角线(即路线只能在对角线的右下方)。共有多少种不同的走法?    

 

4.  证明:有可能从输入序列1, 2, 3, , n,借助一个栈得到输出序列p1, p2, p3, , pn (它是输入序列的某一种排列)的充分必要条件是:不存在这样的ijk同时满足i < j < kpj < pk < p

 

5.         若入栈序列为1, 2, 3, , n,请根据第4题中的结论,判断一个出栈序列是否正确。设计一个算法来实现该功能,子函数接口为:

FUNCTION TrueList(inList, outList : List; len : integer) : BOOLEAN;

其中inList是存储了输出序列的数组,即inList[] = [1..n]outList是存储了该输出序列的数组,len为序列长度,输出序列正确返回true,否则返回false

 

6.  若入栈序列为1, 2, 3, , n,请编程输出所有可能的出栈序列。

子函数接口为:PROCEDURE AllList(inList : List; len : integer);

其中n为序列长度,直接输出所有可能的出栈序列,不考虑序列的先后顺序,要求每行输出一个序列,各序号之间用一个空格隔开。

例如当n = 2时,输出:

1 2

2 1

2 1

1 2

均算正确。

 

7.八皇后问题

八皇后问题是一个古老而著名的问题,是回溯算法的典型例题。该问题是十九世纪著名的数学家高斯1850年提出:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。

请编程输出所有的摆法。

 

 

 

 

参考答案:

 

1.       请用链表实现栈的五个基本操作,并解决文章开头的数字分离问题。

  {利用栈解决分离数字问题:输入一个正整数n,试分离并输出其各位数字}

PROGRAM Separate(INPUT, OUTPUT);

TYPE

    ElemType = integer;  {栈内元素数据类型}

    Stack = ^node;     {用链表表示的栈,以头结点作为栈顶}

    node = record

               data : ElemType;

               next : Stack;

           end;

 

VAR

    s    : Stack;       {定义s为栈}

    v, n : integer;

 

FUNCTION StackEmpty(s : Stack) : boolean; {判断是否栈空}

begin

    StackEmpty := (s = nil);

end; {StackEmpty}

 

FUNCTION StackFull(s : Stack) : boolean; {判断是否栈满}

begin

    StackFull := true; {理论上用链表表示的栈不会满}

end; {StackFull}

 

FUNCTION GetTop(s : Stack)  : ElemType; {获取栈顶元素的值}

begin

    if s = nil then

        writeln('underflow')

    else

        GetTop := s^.data;

end; {GetTop}

 

PROCEDURE Push(var s : Stack; data : ElemType); {入栈}

var

    p : Stack;

begin

    new(p);

    p^.data := data;

    p^.next := s;

    s := p;

end; {Push}

 

PROCEDURE Pop(var s : Stack) ; {出栈}

var

    p : Stack;

begin

    if s = nil then

        writeln('underflow')

    else

    begin

        p := s;

        s := s^.next;

        dispose(p);

    end; {else}

end; {Pop}

 

BEGIN {MAIN}

    s := nil; {栈顶初始化}

 

    writeln('Input n:');

    readln(n);

 

    while n <> 0 do {分离数字并将其入栈}

    begin

        v := n mod 10; {分离出n的个位数字}

        Push(s, v);    {个位数字入栈}

        n := n div 10; {去除n的个位数字}

    end; {while}

 

    while not StackEmpty(s) do {输出数字并出栈}

    begin

        write(GetTop(s) : 2);

        Pop(s);

    end; {while}

END.

 

2.       数制转换:十进制数n和其他d进制数的转换是计算机实现计算的基本问题之一,其解决方法很多,其中一个简单算法基于下列原理:

n = n div d* d + n mod d(其中div为整除运算,mod为求余运算)

例如:(134810 = 25048,其运算过程如下:

n      n div 8    n mod 8

1348    168       4

168     21        0

21      2         5

2       0         2

现在要求编制一个满足下列要求的子程序:对于输入的任意一个非负十进制整数,打印输出与其等值的d进制数。子函数接口为:

PROCEDURE Conversion (n, d : integer);

其中n表示一个非负十进制整数,d表示某种进制,保证1 < d < 10

 

PROGRAM Separate(INPUT, OUTPUT);

VAR

    d, n : integer;

   

PROCEDURE Conversion(n, d : integer);

var

    s : array [1..40] of integer; {用数组表示的栈}

    top : integer;

begin

    top := 0;

    while n > 0 do

    begin

        inc(top);

        s[top] := n mod d;

        n := n div d;

    end; {while}

   

    while top > 0 do

    begin

        write(s[top]);

        dec(top);

    end; {while}

    writeln(') ', d);

end; {Conversion}

 

BEGIN {MAIN}

    writeln('Input n, d');

    readln(n, d);

   

    write('(', n, ') 10 = (');

    Conversion(n, d);

END.

 

 

3.  文章中我利用乘法原理推出了列车出栈序列总量的递归公式,后来又给出了Catalan数的通项公式,实际上,我们可以从另一个角度直接推导出该通项公式,下面是一道殊途同归的经典例题:

n*n的网格中,只允许向右或向上走,从左下角到右上角共有多少种不同的走法?

让我们再进一步: n*n的网格中,只允许向右或向上走,从左下角走向右上角,只能经过但不能超越从左下角到右上角的对角线(即路线只能在对角线的右下方)。共有多少种不同的走法?    

解:对于第一种方案,每一种走法对应于一个长度为2n的字符串,该字符串由 n R nU字母构成,分别对应于向右和向上的动作。每种不同的走法可看成是从2n个位置中选择n个位置放上R,其余位置填上U得到。共有C(2n, n)种不同的走法。

对于第二种方案,引进新的网格(n+1) * (n-1)n*n网格中的违规走法与(n+1) * (n-1)网格中的所有走法存在着一一对应关系。

所以(n+1) * (n-1)网格的走法有C(2n, n-1)种,故第二种方案的正确走法有:

C(2n, n) - C(2n, n-1) = (2*n)! / (n! * n!) -  (2*n)! / ((n-1)! * (n+1)!) = (2*n)! / (n! * (n+1)!)

= C(2n, n) / (n + 1)

这就是大名鼎鼎的Catalan数。Catalan数的应用非常广泛,最典型的四类应用:

1.括号化问题:由n个左括号和 n个右括号构成的合法括号序列个数为多少?

类似:由n1n0组成的2n位二进制数, 要求从左到右扫描,1的累计数不小于0的累计数,请问这样的二进制数有多少个?

2.出栈次序问题:一个栈的进栈序列为1,2,3,..n,有多少个不同的出栈序列?

类似:有2n个人排成一行进入剧场。入场费5元。其中只有n个人有一张5元钞票,另外n人只有10元钞票,剧院无其它钞票,问有多少中方法使得只要有10元的人买票,售票处就有5元的钞票找零?(将持5元者到达视作将5元入栈,持10元者到达视作使栈中某5元出栈)

3.将多边行划分为三角形问题:将一个凸多边形区域分成三角形区域的方法数?

类似:一位大城市的律师在她住所以北n个街区和以东n个街区处工作。每天她走2n个街区去上班。如果她从不穿越(但可以碰到)从家到办公室的对角线,那么有多少条可能的道路?

类似:在圆上选择2n个点,将这些点成对连接起来使得所得到的n条线段不相交的方法数?

4.给顶节点组成二叉树的问题:给定n个节点,能构成多少种不同的二叉树?

 

以上问题的答案都是h(n)

 

4.  证明:有可能从输入序列1, 2, 3, , n,借助一个栈得到输出序列p1, p2, p3, , pn (它是输入序列的某一种排列)的充分必要条件是:不存在这样的ijk同时满足i < j < kpj < pk < pi

证明:充分条件:如果不存在这样的ijk同时满足i < j < kpj < pk < pi,即对于输入序列:…, pj, …, pk, …, pi, …(pj < pk < pi)

不存在这样的输出序列:…, pi, …, pj, …, pk, …

       例如对于输入序列123,不存在输出序列312

从中可以看到,pi后进先出,满足栈的特点,最后进栈的最早出栈;同时也说明,在pk之前进栈的pj不可能在pk之前出来,反过来说明满足先进后出的特点,所以构成一个栈。

必要条件:如果初始输入序列是123,。。。,n,假设是进栈,又存在这样的ijk同时满足i < j < kpj < pk < pi,即对于输入序列:…, pj, …, pk, …, pi, …(pj < pk < pi)

存在这样的输出序列:…, pi, …, pj, …, pk, …

从中可以看到,pi后进先出,满足栈的特点,最后进栈的最早出栈;同时又看到在pk之前进栈的pj却在pk之前出来,这不满足先进后出的特点,与前面假设的是栈不一致,本题得证。

补充:如果对上述证明看不太懂,下面补充几种对引理的证明。

试证明:若借助栈可由输入序列1, 2, 3, , n得到一个输出序列p1, p2, p3, , pn (它是输入序列的某一种排列),则在输出序列中不可能出现以下情况,即存在i<j<k,使得pj<pk<pi。证明一,归纳法:

证明:因为借助栈由输入序列1, 2, 3, , n,可得到输出序列p1, p2, p3, , pn ,如果存在下标i, j, k,满足入栈序列i < j < k,那么在出栈序列中,可能出现如下5种情况:

push(i)pop(i)push(j)pop(j)push(k)pop(k),此时最小值元素i位于pi位置,中间值元素j位于pj位置,最大值元素k位于后面的pk位置,得到出栈序列pi < pj < pk

push(i)pop(i)push(j)push(k)pop(k)pop(j),此时最小值元素i位于pi位置,中间值元素j位于后面的pk位置,最大值元素k位于pj位置,得到出栈序列pi < pk < pj

push(i)push(j)pop(j)pop(i)push(k)pop(k),此时最小值元素i位于pj位置,中间值元素j位于pi位置,最大值元素k位于后面的pk位置,得到出栈序列pj < pi < pk

push(i)push(j)pop(j)push(k)pop(k)pop(i),此时最小值元素i位于后面的pk位置,中间值元素j位于pi位置,最大值元素k位于pj位置,得到出栈序列pk < pi< pj

push(i)push(j)push(k)pop(k)pop(j)pop(i),此时最小值元素i位于后面的pk位置,中间值元素j位于pj位置,最大值元素k位于pi位置,得到出栈序列pk < pj< pi

综上所述,对于入栈序列i < j < k,出栈序列中不可能出现pj < pk < pi的情形;

 

证明二,反证法:

证明:假设对于入栈序列i < j < k,出栈序列中出现了pj < pk < pi的情形。

i < jpj < pi 说明pjpi之前入栈,并且pipj之前出栈,即pj先入栈,之后pi入栈,根据后进先出可以知道pi先出栈,然后pj出栈;

i < kpk < pi说明pkpi之前入栈,并且pipk之前出栈,即pk先入栈,之后pi入栈,根据后进先出可以知道pi先出栈,然后pk出栈;

由以上两条,我们推出pjpk都比pi后出栈,也即当pi出栈之时,pjpk尚在栈中,又因为j < k所以pjpk之前出栈,根据栈的LIFO特性可知pjpk之后入栈。因为入栈序列是按从小到大次序排列的,所以pj > pk这与题设矛盾。

所以不可能存在入栈序列i < j < k使得出栈序列为pj < pk < pi,也就是说,若有(i=1)(j=2)(k=3)顺序入栈,不可能有(pi =3),(pj =1),(pk =2)的出栈序列。

 

引理:以1…n顺序压栈的出栈序列为p1, p2,…, pn,对任意的pi而言,pi+1,…, pn中比pi小的数必须是按逆序排列的,即对任意的i < j < k而言,若pi > pjpi > pk,则必有pj > pk

 

证明: 因为入栈序列是按1n入栈的,所以小的数必定先入栈,又pi > pjpi > pk所以pjpk都比pi先入栈;

因为i < j < k,所以出栈序列为pi, pj, pk,即pjpk都比pi后出栈,也即当pi出栈之时,pjpk尚在栈中,又因为j < k所以pjpk之前出栈,根据栈的LIFO特性可知pkpj之前入栈,即pj > pk

 

5.  若入栈序列为1, 2, 3, , n,请根据第4题中的引理,判断一个出栈序列是否正确。设

计一个算法来实现该功能,子函数接口为:

FUNCTION TrueList(inList, outList : List; len : integer) : BOOLEAN;

其中inList是存储了输出序列的数组,即inList[] = [1..n]outList是存储了该输出序列的数组,len为序列长度,输出序列正确返回true,否则返回false

 

FUNCTION TrueList(inList, outList : List; len : integer) : BOOLEAN;

var

    i, j : integer;

begin

    for i:=2 to n-1 do

        if outList[i-1] > outList[i] + 1 then

            for j:=i+1 to n do

                if (outList[i] < outList[j]) and (outList[i-1] > outList[j]) then

                begin

                    TrueList := false;

                    exit;

                end; {if}

                       

    TrueList := true;

end; {TrueList}

 

6. 若入栈序列为1, 2, 3, , n,请编程输出所有可能的出栈序列。

子函数接口为:PROCEDURE AllList(inList : List; len : integer);

其中n为序列长度,直接输出所有可能的出栈序列,不考虑序列的先后顺序,要求每行输出一个序列,各序号之间用一个空格隔开。

例如当n = 2时,输出:

1 2

2 1

2 1

1 2

均算正确。

 

{输出所有可能的出栈序列}

PROGRAM TrainList(INPUT, OUTPUT);

CONST

  MAXCAPACITY = 255; {栈的最大容量}

 

TYPE

  List = array [1..MAXCAPACITY] of integer; {用数组表示的出入栈序列}

 

VAR

  inList : List; {定义出入栈序列}

  i, n : integer;

 

{穷举出栈序列的递归函数}

PROCEDURE GetList(var outList : List; s, inList : List; top, pos, num, len : integer);

var

  i : integer;

begin

  if num < len then

  begin

    inc(top);

    s[top] := inList[num]; {入栈}

    GetList(outList, s, inList, top, pos, num+1, len); {直接让下一辆列车入栈}

           

    while top > 0 do {先让部分列车出栈,再让下一辆列车入栈}

    begin

      inc(pos);

      outList[pos] := s[top];

      dec(top); {出栈}

      GetList(outList, s, inList, top, pos, num+1, len);

    end; {while}

  end {if}

  else

  begin

    for i:=1 to pos do {先输出已经出站的序列}

      write(outList[i], ' ');

 

    write(inList[num], ' '); {输出最后入站的列车,序号为inList[num]}

 

    for i:=top downto 1 do {输出仍在站内的列车序列}

      write(s[i], ' ');

    writeln;

  end; {else}

end; {GetList}

  

{采用递归的方式,穷举各种可能的出栈序列}

PROCEDURE AllList(inList : List; len : integer);

var

  s, outList : List;

begin

  GetList(outList, s, inList, 0, 0, 1, len);

end; {AllList}

 

BEGIN {MAIN}

  writeln('len of inList:');

  readln(n);

  for i:=1 to n do  {入栈序列}

    inList[i] := i;

 

  AllList(inList, n);

END.

 

7. 八皇后问题

八皇后问题是一个古老而著名的问题,是回溯算法的典型例题。该问题是十九世纪著名的数学家高斯1850年提出:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。

八皇后问题的解决方案与四色地图问题非常类似,都是用试错法来做,一一测试每种摆法,直到得出正确的答案。

算法中,用变量i表示处理的是第i行的皇后,a[i]表示第i行的皇后的列位置。

{八皇后问题}

PROGRAM Queens(input, output);

VaR

    a    : array[1..8] of integer;

    i, j , s: integer;

 

{判断皇后所处位置是否正确}

FUNCTION RightPlace(k : integer) : BOOLEaN;

var

    i : integer;

begin

    for i:=1 to k-1 do

        if (a[i] = a[k]) or (abs(a[i]-a[k]) = abs(i-k)) then

        begin

            RightPlace := false;

            exit;

        end; {if}

   

    RightPlace := true;

end; {Place}

 

BEGIN

    s := 0;

    i := 1;

    a[1] := 0;

    while i > 0 do

    begin

        inc(a[i]); {在当前列加1的位置开始搜索}

        while (a[i] <= 8) and not RightPlace(i) do {当前位置满足条件}

            inc(a[i]); {继续搜索下一位置}   

        if a[i] <= 8 then {存在满足条件的列}

        begin

            if i = 8 then {所有的皇后均已就位}

            begin

                inc(s);

                write(s, ' : ');

                for j:=1 to 8 do {输出一组解}

                    write(a[j]:4);

                writeln;

            end {if}

            else {处理下一个皇后的位置}

            begin

                inc(i);

                a[i] := 0;

            end; {else}

        end {if}

        else

        begin

            a[i] := 0;

            dec(i);

        end; {else}

    end; {while}

END.

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值