您的位置:首页 > 其它

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

2009-01-21 20:51 387 查看
同步练习:
1.
请用链表实现栈的五个基本操作,并解决文章开头的数字分离问题。

2.
数制转换:十进制数n和其他d进制数的转换是计算机实现计算的基本问题之一,其解决方法很多,其中一个简单算法基于下列原理:
n = (n div d)* d + n mod d(其中div为整除运算,mod为求余运算)
例如:(1348)10 = (2504)8,其运算过程如下:
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 (它是输入序列的某一种排列)的充分必要条件是:不存在这样的i,j,k同时满足i < j <
k和pj < 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为求余运算)
例如:(1348)10 = (2504)8,其运算过程如下:
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 和 n个U字母构成,分别对应于向右和向上的动作。每种不同的走法可看成是从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个右括号构成的合法括号序列个数为多少?
类似:由n个1和n个0组成的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 (它是输入序列的某一种排列)的充分必要条件是:不存在这样的i,j,k同时满足i < j <
k和pj < pk <
pi。
证明:充分条件:如果不存在这样的i,j,k同时满足i < j <
k和pj < pk <
pi,即对于输入序列:…, pj, …, pk, …,
pi, …(pj < pk < pi)
不存在这样的输出序列:…, pi, …, pj, …, pk,

例如对于输入序列1,2,3,不存在输出序列3,1,2。
从中可以看到,pi后进先出,满足栈的特点,最后进栈的最早出栈;同时也说明,在pk之前进栈的pj不可能在pk之前出来,反过来说明满足先进后出的特点,所以构成一个栈。
必要条件:如果初始输入序列是1,2,3,。。。,n,假设是进栈,又存在这样的i,j,k同时满足i < j < k和pj
< 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 < j和pj
< pi 说明pj在pi之前入栈,并且pi在pj之前出栈,即pj先入栈,之后pi入栈,根据后进先出可以知道pi先出栈,然后pj出栈;
由i < k和pk
< pi说明pk在pi之前入栈,并且pi在pk之前出栈,即pk先入栈,之后pi入栈,根据后进先出可以知道pi先出栈,然后pk出栈;
由以上两条,我们推出pj和pk都比pi后出栈,也即当pi出栈之时,pj和pk尚在栈中,又因为j < k,所以pj在pk之前出栈,根据栈的LIFO特性可知pj在pk之后入栈。因为入栈序列是按从小到大次序排列的,所以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
> pj且pi > pk,则必有pj > pk。

证明: 因为入栈序列是按1到n入栈的,所以小的数必定先入栈,又pi > pj且pi
> pk,所以pj和pk都比pi先入栈;
因为i < j < k,所以出栈序列为pi, pj, pk,即pj和pk都比pi后出栈,也即当pi出栈之时,pj和pk尚在栈中,又因为j < k,所以pj在pk之前出栈,根据栈的LIFO特性可知pk在pj之前入栈,即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.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: