数据结构.docx
《数据结构.docx》由会员分享,可在线阅读,更多相关《数据结构.docx(72页珍藏版)》请在冰豆网上搜索。
![数据结构.docx](https://file1.bdocx.com/fileroot1/2022-11/27/e19d3d1c-2113-4531-9d8e-88beb8cc0031/e19d3d1c-2113-4531-9d8e-88beb8cc00311.gif)
数据结构
信息技术竞赛培训教程
目录
第二部分数据结构
(一)――栈
(二)――队列
(三)――链表
(四)――迭代与递推
(五)――递归
(六)――搜索与回溯
(七)――树与二叉树
(八)――排序算法
(九)――查找算法
(十)――图论基础知识
●● 广度优先搜索
●● 广度优先搜索
第二部分算法和数据结构
(一)――栈
说到学习和掌握数据结构,很容易让人想到的就是其最本的数据结构模式:
栈、队这一讲,我们就来谈谈“栈”。
“栈”的应用很广泛,大家在PASCAL程序设计中,常遇的一种错误就是“栈”超界,那么,“栈”为何物呢?
栈是只能在某一端插入和删除的特殊线性表。
用桶堆积物品,先堆进来的压在底下,随后一件一件往堆。
取走时,只能从上面一件一件取。
堆和取都在顶部进行,底部一般是不动的。
栈就是一种类似桶堆积物品的数据结构,进行删除和插入的一端称栈顶,另一堆称栈底。
插入一般称为进栈(PUSH),删除则称为退栈(POP)。
栈也称为后进先出表(LIFO表)。
一个栈可以用定长为N的数组S来表示,用一个栈指针TOP指向栈顶。
若TOP=0,表示栈空,TOP=N时栈满。
进栈时TOP加1。
退栈时TOP减1。
当TOP<0时为下溢。
栈指针在运算中永远指向栈顶。
1、进栈(PUSH)算法
①若TOP≥n时,则给出溢出信息,作出错处理(进栈前首先检查栈是否已满,满则溢出;不满则作②);
②置TOP=TOP+1(栈指针加1,指向进栈地址);
③S(TOP)=X,结束(X为新进栈的元素);
2、退栈(POP)算法
①若TOP≤0,则给出下溢信息,作出错处理(退栈前先检查是否已为空栈,空则下溢;不空则作②);
②X=S(SOP),(退栈后的元素赋给X);
③TOP=TOP-1,结束(栈指针减1,指向栈顶)。
进栈、出栈的Pascal实现过程程序:
CONSTn=100;
TYPE
stack=ARRAY[1..n]OFinteger;
PROCEDUREPUSH(VARs:
stack;VARtop,x:
integer);{入栈}
BEGIN
IFtop=nTHEN
writeln('overflow')
ELSEBEGIN
top:
=top+1;s[top]:
=x;
END;
END;
PROCEDUREPOP(VARs:
stack;VARy,top:
integer);{出栈}
BEGIN
IFtop=0THENwriteln('underflow')ELSEBEGIN
y:
=s[top];top:
=top-1;
END
END;
对于出栈运算中的“下溢”,程序中仅给出了一个标志信息,而在实际应用中,下溢可用来作为控制程序转移的判断标志,是十分有用的。
对于入栈运算中的“上溢”,则是一种致命的错误,将使程序无法继续运行,所以要设法避免。
堆栈的数组模拟
十进制数N和其它d进制数的转换是实现计算的基本问题,解决方法很多,下面给出一中算法原理:
N=(Ndivd)×d+Nmodd(其中div为整除运算,mod为求余运算)。
例如:
(1348)10=(2504)8运算过程如下:
N
Ndiv8
Nmod8
1348
168
4
168
21
0
21
2
5
2
0
2
N
Ndiv8
Nmod8
9413
1、1、 填空:
(9413)10=()8=()16=()2
2、下面的程序实现这个转换过程,请补充完整。
'数制转化程序【xoi00_07.pas】
programxoi00_07;
constsize=100;
vara:
array[1..size]ofinteger;
n,d,i,j:
integer;
begin
writeln;
write('Pleaseenteranumber(N)base10:
');
readln(n);
write('pleaseenteranumber(d):
');
readln(d);
i:
=1;
repeat
a[i]:
=nmodd;
n:
=ndivd;
inc(i);
untiln=0;
forj:
=i-1downto1dowrite(a[j]:
5);
end.
2、火车站列车调度示意图如下,假设调度站两侧的轨道为单向行驶轨道。
1、1、 如果进站的车厢序列为123,则可能的出战车厢序列是什么?
2、2、 如果进展进站的车厢序列为123456,问能否得到135426和435612的出站序列。
栈的用途极为广泛,在源程序编译中表达式的计算、过程的嵌套调用和递归调用等都要用到栈,下面以表达式计算为例子加以说明。
源程序编译中,若要把一个含有表达式的赋值语句翻译成正确求值的机器语言,首先应正确地解释表达式。
例如,对赋值语句
X:
=4+8×2-3;(式11.1)
其正确的计算结果应该是17,但若在编译程序中简单地按自左向右扫描的原则进行计算,则为:
X=12×2-3=24-3=21
这结果显然是错误的。
因此,为了使编译程序能够正确地求值,必须事先规定求值的顺序和规则。
通常采用运算符优先数法。
一般表达式中会遇到操作数、运算符和语句结束符等,以算术运算符为例,对每种运算赋予一个优先数,如:
运算符:
× ÷ + -
优先数:
2 2 1 1
(语句结束符“;”的优先数为零)
在运算过程中,优先数高的运算符应先进行运算(但遇到括号时,应另作处理)。
按这样的规定,对式(11.1)自左向右进行运算时,其计算顺序就被唯一地确定下来了。
计算顺序确定后,在对表达式进行编译时,一般设立两个栈,一个称为运算符栈(OPS),另一个称为操作数栈(OVS),以便分别存放表达式中的运算符和操作数。
编译程序自左向右扫描表达式直至语句结束,其处理原则是:
①凡遇到操作数,一律进入操作数栈;
②当遇到运算符时,则将运算符的优先数与运算符栈中的栈顶元素的优先数相比较;若该运算符的优先数大,则进栈;反之,则取出栈顶的运算符,并在操作数栈中连续取出两个栈顶元素作为运算对象进行运算,并将运算结果存入操作数栈,然后继续比较该运算符与栈顶元素的优先数。
例如式(11.1)中,当扫描到“+”和“×”时都要将运算符入栈。
接着扫描到“-”号,其优先数小于乘号所以乘号退栈,并执行8×2,将结果16再存入操作数栈。
再将“-”号的优先数与运算符栈的栈顶元素“+”号的优先数相比较,两者相等,所以再将加号退栈,进行4+16,结果为20,再入栈,接着,由于运算栈已空,所以减号入栈。
当扫描到“3”时,操作数入栈。
当扫描到“;”时,其优先数最低,所以减号退栈并执行20-3,结果为17并入栈。
因已扫描到语句结束符,所以表达式的求值结束,结果为17。
例题模拟计算机处理算术表达式过程。
从键盘上输入算术表达式串(只含+、-、×、÷运算符,充许含括号),输出算术表达式的值。
设输入的表达式串是合法的。
分析:
建立两个栈,一个是操作数栈(number),一个是运算符栈(symbol),根据运算符的优先级对两个栈进行相应的操作。
源程序
programex11_4;
const
max=100;
var
number:
array[0..max]ofinteger;
symbol:
array[1..max]ofchar;
s,t:
string;
i,p,j,code:
integer;
procedurepush;{算符入栈运算}
begin
inc(p);
symbol[p]:
=s[i];
end;
procedurepop;{运算符栈顶元素出栈,并取出操作数栈元素完成相应的运算}
begin
dec(p);
casesymbol[p+1]of
'+':
inc(number[p],number[p+1]);
'-':
dec(number[p],number[p+1]);
'*':
number[p]:
=number[p]*number[p+1];
'/':
number[p]:
=number[p]divnumber[p+1];
end;
end;
functioncan:
boolean;{判断运算符的优先级别,建立标志函数}
begin
can:
=true;
if(s[i]in['+','-'])and(symbol[p]<>'(')thenexit;
if(s[i]in['*','/'])and(symbol[p]in['*','/'])thenexit;
can:
=false;
end;
begin
write('String:
');
readln(s);
s:
='('+s+')';
i:
=1;
p:
=0;
whilei<=length(s)do
begin
whiles[i]='('do{左括号处理}
begin
push;
inc(i);
end;
j:
=i;
repeat{取数入操作数栈}
inc(i);
until(s[i]<'0')or(s[i]>'9');
t:
=copy(s,j,i-j);
val(t,number[p],code);
repeat
ifs[i]=')'then{右括号处理}
begin
whilesymbol[p]<>'('do
pop;
dec(p);
number[p]:
=number[p+1];
end
else
begin{根据标志函数值作运算符入栈或出栈运算处理}
whilecando
pop;
push;
end;
inc(i);
until(i>length(s))or(s[i-1]<>')');
end;
write('Result=',number[0]);
readln;
end.
练习题:
1、读入一英文句子,单词之间用空格或逗号隔开,统计其中单词个数,并输出各个字母出现的频率。
(句子末尾不一定用"."结束)如果含有其他的字符,则只要求输出错误信息及错误类型。
含有大写字母错误类型error1
数字(0-9)错误类型error2
其他非法字符错误类型error3
如输入:
Itis12!
输出:
error123
输入:
iam,astudent
输出:
4
2、2、 编码解码:
从键盘输入一个英文句子,设计一个编码、解码程序。
(string)
编码过程:
先键入一个正整数N(1<=N<=26)。
这个N决定了转换关系。
例如当N=1,输入的句子为ABCXYZ时,则其转换码为ABCXYZ不变。
当N=2时,其转换码为BCDYZA,其它的非字母字符不变。
为使编码较于破译,将转换码的信息自左而右两两交换,若最后仅剩单个字符则不换。
然后,将一开始表示转换关系的N根据ascii表序号化成大写字母放在最前面。
如:
abcABCxyzXYZ-/,1.n=3
cdeCDEzabZAB-/,1.{根据N的值转换}
dcCeEDazZbBA/-1,.{两两交换}
CdcCeEDazZbBA/-1,.{最后编码}
解码过程为编码的逆过程。
4、计算器的改良【第三届全国青少年信息学奥林匹克分区联赛复赛试题普及组题一】
〖问题描述〗
NCL是一家专门从事计算器改良与升级的实验室。
最近该实验室收到了某公司所委托的一个任务:
需要在该公司某型号的计算器上加上解一元一次方程的功能。
实验室将这个任务交组了一个刚进入的新手ZL先生。
为了很好的完成这个任务,ZL先生首先研究了一些一元一次方程的实例:
4+3X=8
6a-5+1=2-2
-5+12Y=0
ZL先生被告知:
在计算器上键入的一个一元一次方程中,只包含整数、小写字母入+、-、=这三个数学符号(当然,“-”既可当减号也可当负号)。
方程中并没有括号,也没有除号,方程中的字母表示末知数。
〖问题求解〗
编写程序,解输入的一元一次方程,将解方程的结果(精确到小数点后三位)输出至屏幕。
键入的一元一次方程均合法,且有唯一的实数解。
〖样例〗
输入:
6a-5+1=2-2a
输出:
a=0.750
(二)――队列
在
(一)中,我们谈了"栈"的应用,下面我们谈谈队列,队列是限定在一端进行插入,另一端进行删除和特殊线性表。
正象排列买东西,排在前面的人买完东西后离开队伍(删除),而后来的人总是排在队伍未尾(插入)。
通常把队列的删除和插入分别称为出队和入队。
允许出队的一端称为队头,允许入队的一端称为队尾。
所有需要进队的数据项,只能从队尾进入,队列中的数据项只能从队头离去。
由于总是先入队的元素先出队(先排队的人先买完东西),这种表也称为先进先表(FIFO)表。
队列可以用数组Q[1…m]来存储,数组的上界m即是队列所容许的最大容量。
在队列的运算中需设两个指针:
head:
队头指针,指向实际队头元素的前一个位置tall:
队尾指针,指向实际队尾元素所在的位置一般情况下,两个指针的初值设为0,这时队列为空,没有元素。
图1(a)画出了一个由6个元素构成的队列,数组定义Q[1…10]。
Q(i)i=3,4,5,6,7,8头指针head=2,尾指针tail=8。
队列中拥有的元素个数为:
L=tail-head现要让排头的元素出队,则需将头指针加1。
即head=head+1这时头指针向上移动一个位置,指向Q(3),表示Q(3)已出队。
见图1(b)。
如果想让一个新元素入队,则需尾指针向上移动一个位置。
即tail=tail+1这时Q(9)入队,见图1(c)。
当队尾已经处理在最上面时,即tail=10,见图1(d),如果还要执行入队操作,则要发生"上溢",但实际上队列中还有三个空位置,所以这种溢出称为"假溢出"。
克服假溢出的方法有两种。
一种是将队列中的所有元素均向低地址区移动,显然这种方法是很浪费时间的;另一种方法是将数组存储区看成是一个首尾相接的环形区域。
当存放到n地址后,下一个地址就"翻转"为1。
在结构上采用这种技巧来存储的队列称为循环队列,见图2
循环队的入队算法如下:
1、tail=tail+1;
2、若tail=n+1,则tail=1;
3、若head=tail尾指针与头指针重合了,表示元素已装满队列,则作上溢出错处理;
4、否则,Q(tail)=X,结束(X为新入出元素)。
队列和栈一样,有着非常广泛的应用。
考虑一个分时系统,如果一台计算机联有四个终端,即允许四个用户同时使用这一台计算机。
那么,计算机系统必须设立一个队列,用以管理各终端用户使用CPU的请求。
当某个用户要求使用CPU时,相应的终端代号就入队(插入队尾),而队头的终端用户则是CPU当前服务的对象。
我们考虑最简单的情况,
对于当前用户(队头),系统每次分配一个为时间片的时间间隔,在一个时间片内,如果当前用户的作业没有结束,则该终端用户的代号出队后重新入队,插入队尾,等待下一次CPU服务。
如果某个用户的作业运行结束,则先退出,出队后不再入队,
整个运行过程就是各终端代号不断地入队、出队,CPU轮流地为n(n≤4)个终端用户服务。
由于计算机的运行速度极快,所以,对于每个终端用户来说,似乎计算机是单独在为其服务。
和线性表一样,栈和队可以采用链表存储结构,当要实现多个栈共享内存或多个队共享内存时,选择链式分配结构则更为合适。
例1求两个一元多项式的和。
输入多项式方式为,多项式项数,每项系数和指数,按指数从大到小的顺序输入。
分析
多项式的算术运算是表处理的一个经典问题。
建立两张表a、b分别存放两个多项式的内容,建立表指针ta、tb,指向表a和表b的元素,根据表a、b元素中的指数大小合并输出。
1、比较ta、tb指向元素的大小,若ta的指数大于tb的指数,输出ta元素,改变指针ta;
2、若ta的指数小于tb的指数,输出tb元素,改变指针tb;
3、若ta的指数等于tb的指数,ta、tb元素的系数相加输出,同时改变指针ta和tb;
4、若有一表取空,则输出另一表剩余的内容。
源程序一:
多项式相加的顺序表实现
programex11_5a;
type
node=record
zhi,xi:
integer;
end;
ar=array[1..1000]ofnode;
var
a,b:
ar;
ta,tb,n:
integer;
begin
write('One:
');
readln(n);{输入第一个多项式的系数和指数}
forta:
=ndownto1do
readln(a[ta].xi,a[ta].zhi);
ta:
=n;
write('Two:
');
readln(n);{输入第二个多项式的系数和指数}
fortb:
=ndownto1do
readln(b[tb].xi,b[tb].zhi);
tb:
=n;
write('Resultis');
while(ta>0)and(tb>0)do{当两个表均不空时}
begin{比较两表指针指向的项指数,输出指数小的项系数和指数,同时改变该表指针}
ifa[ta].zhi>b[tb].zhithen
begin
ifa[ta].xi<0thenwrite(#8''#8);
write(a[ta].xi,'x',a[ta].zhi,'+');
dec(ta);
end
else
ifa[ta].zhi
begin
ifb[tb].xi<0thenwrite(#8''#8);
write(b[tb].xi,'x',b[tb].zhi,'+');
dec(tb);
end
else
begin{若两表指针指向的项指数相等,则两系数相加输出,两表指针同时改变}
ifb[tb].xi+a[ta].xi<>0then
begin
ifb[tb].xi+a[ta].xi<0thenwrite(#8''#8);
write(b[tb].xi+a[ta].xi,'x',b[tb].zhi,'+');
end;
dec(ta);
dec(tb);
end;
end;
whileta>0do{若有一表空,则输出另一表的剩余项}
begin
ifa[ta].xi<0thenwrite(#8''#8);
write(a[ta].xi,'x',a[ta].zhi,'+');
dec(ta);
end;
whiletb>0do
begin
ifb[tb].xi<0thenwrite(#8''#8);
write(b[tb].xi,'x',b[tb].zhi,'+');
dec(tb);
end;
writeln(#8''#8);
readln;
end.
源程序二:
多项式相加的链表实现
programex11_5b;
type
link=^node;
node=record
zhi,xi:
integer;
nxt:
link;
end;
var
a,b:
link;
n:
integer;
procedurecreatefifo(varc:
link);{建立多项式系数、指数链表}
var
p:
link;
i:
integer;
begin
new(p);
readln(p^.xi,p^.zhi);
c:
=p;
fori:
=1ton-1do
begin
new(p^.nxt);
p:
=p^.nxt;
readln(p^.xi,p^.zhi);
end;
p^.nxt:
=nil;
end;
begin
write('One:
');
readln(n);
createfifo(a);
write('Two:
');
readln(n);
createfifo(b);
write('Resultis');
while(a<>nil)and(b<>nil)do
begin
ifa^.zhi>b^.zhithen
begin
ifa^.xi<0thenwrite(#8''#8);
write(a^.xi,'x',a^.zhi,'+');
a:
=a^.nxt;
end
else
ifa^.zhi
begin
ifb^.xi<0thenwrite(#8''#8);
write(b^.xi,'x',b^.zhi,'+');
b:
=b^.nxt;
end
else
begin
ifb^.xi+a^.xi<>0then
begin
ifb^.xi+a^.xi<0thenwrite(#8''#8);
write(b^.xi+a^.xi,'x',b^.zhi,'+');
end;
b:
=b^.nxt;
a:
=a^.nxt;
end;
end;
whilea<>nildo
begin
ifa^.xi<0thenwrite(#8''#8);
write(a^.xi,'x',a^.zhi,'+');
a:
=a^.nxt;
end;
whileb<>nildo
begin
ifb^.xi<0thenwrite(#8''#8);
write(b^.xi,'x',b^.zhi,'+');
b:
=b^.nxt;
end;
writeln(#8''#8);
readln;
end.
3.队列的应用:
例:
设有一个表,记为L=(a1,a2,a3,...,an),其中
L——表名
a1,a2,a3,..,an——表中元素。
当ai为数值时表示元素,当ai为大写字母时,表示另一个表,但不能循环定义。
例如,下列的定义是合法的(约定L是第一个表的表名):
L=(3.4,3,4,K,8,0,8)
K=(15,5,8,P,9,4)
P=(4,7,8,9)
程序要求:
当全部表给出后,示出所有元素的最大元素。
思路: