数据结构.docx

上传人:b****5 文档编号:4107515 上传时间:2022-11-27 格式:DOCX 页数:72 大小:230.83KB
下载 相关 举报
数据结构.docx_第1页
第1页 / 共72页
数据结构.docx_第2页
第2页 / 共72页
数据结构.docx_第3页
第3页 / 共72页
数据结构.docx_第4页
第4页 / 共72页
数据结构.docx_第5页
第5页 / 共72页
点击查看更多>>
下载资源
资源描述

数据结构.docx

《数据结构.docx》由会员分享,可在线阅读,更多相关《数据结构.docx(72页珍藏版)》请在冰豆网上搜索。

数据结构.docx

数据结构

信息技术竞赛培训教程

 

目录

 

第二部分数据结构

 

(一)――栈

(二)――队列

(三)――链表

(四)――迭代与递推

(五)――递归

(六)――搜索与回溯

(七)――树与二叉树

(八)――排序算法

(九)――查找算法

(十)――图论基础知识

●●        广度优先搜索

●●        广度优先搜索

第二部分算法和数据结构

 

(一)――栈

 

说到学习和掌握数据结构,很容易让人想到的就是其最本的数据结构模式:

栈、队这一讲,我们就来谈谈“栈”。

“栈”的应用很广泛,大家在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)

  程序要求:

当全部表给出后,示出所有元素的最大元素。

  思路:

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 小学教育 > 数学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1