信息学奥赛数据结构教程PASCAL版.docx
- 文档编号:4723321
- 上传时间:2022-12-07
- 格式:DOCX
- 页数:11
- 大小:61.45KB
信息学奥赛数据结构教程PASCAL版.docx
《信息学奥赛数据结构教程PASCAL版.docx》由会员分享,可在线阅读,更多相关《信息学奥赛数据结构教程PASCAL版.docx(11页珍藏版)》请在冰豆网上搜索。
信息学奥赛数据结构教程PASCAL版
信息学奥赛数据结构教程PASCAL版
第三课 链表
存储方式的分类:
顺序存储结构和链式存储结构;
顺序存储结构:
在(子)程序的说明部分就必须加以说明,以便分配固定大小的存储单元,直到(子)程序结束,才释放空间。
因此,这种存储方式又称为静态存储。
所定义的变量相应的称为静态变量。
它的优缺点如下:
1.优点:
可以通过一个简单的公式随机存取表中的任一元素,逻辑关系上相邻的两个元素在物理位置上也是相邻的,且很容易找到前趋与后继元素;
2.缺点:
在线性表的长度不确定时,必须分配最大存储空间,使存储空间得不到充分利用,浪费了宝贵的存储资源;线性表的容量一经定义就难以扩充;在插入和删除线性表的元素时,需要移动大量的元素,浪费了时间;
链式存储结构:
在程序的执行过程中,通过两个命令向计算机随时申请存储空间或随时释放存储空间,以达到动态管理、使用计算机的存储空间,保证存储资源的充分利用。
这样的存储方式称为动态存储。
所定义的变量称为动态变量。
它的优点如下:
1.优点:
可以用一组任意的存储单元(这些存储单元可以是连续的,也可以不连续的)存储线性表的数据元素,这样就可以充分利用存储器的零碎空间;
2.概念1:
为了表示任意存储单元之间的逻辑关系,对于每个数据元素来说,除了要存储它本身的信息(数据域、data)外,还要存储它的直接后继元素的存储位置(指针域、link或next)。
我们把这两部分信息合在一起称为一个“结点node”。
3.概念2:
N个结点链接在一起就构成了一个链表。
N=0时,称为空链表。
4.概念3:
为了按照逻辑顺序对链表中的元素进行各种操作,我们需要定义一个变量用来存储整个链表的第一个结点的物理位置,这个变量称为“头指针、H或head”。
也可以把头指针定义成一个结点,称为“头结点”,头结点的数据域可以不存储任何信息,也可以存储线性表的长度等附加信息,头结点的指针域(头指针)存储指向第一个结点的指针,若线性表为空表,则头结点的指针域为空(NIL)。
由于最后一个元素没有后继,所以线性表中最后一个结点的指针域为空(NIL)。
5.概念4:
由于此链表中的每个结点都只包含一个指针域,故称为“线性链表或单链表”。
(一)指针类型和指针变量的说明、使用、操作
1.类型和变量的说明
type指针类型标识符=^基类型名;{基类型不能为文件类型}
var指针变量名:
指针类型标识符;
2.申请存储单元{动态申请、空间大小由指针变量的基类型决定}
new(指针变量名);{PASCAL标准过程}
3.指针变量的赋值
指针变量名:
=NIL;{初始化,暂时不指向任何存储单元}
如何表示和操作指针变量?
不同于简单变量(如A:
=0;),PASCAL规定用“指针变量名^”的形式引用指针变量(如P^:
=0;)。
如下图:
4.相同基类型的指针变量之间可以进行相互赋值
如有下面的程序段,可以画出右边的示意图:
varp1,p2:
^integer;
new(p1);new(p2);
p1^:
=90;p2^:
=80;
p1:
=p2;
5.关系运算
如:
ifp1=p2then……
whilep<>nildo……
6.释放动态存储单元
dispose(指针变量名);
(二)单链表的结构、建立、输出
由于单链表的每个结点都有一个数据域和一个指针域,所以,每个结点都可以定义成一个记录。
比如,有如下一个单链表,如何定义这种数据结构呢?
typepointer=^nodetype;
nodetype=record
data:
datatype;
next:
pointer;{嵌套定义}
end;
varhead,p,q,r:
pointer;
下面给出建立并输出单链表的程序,大家可以把它改成过程用在以后的程序当中。
Programcreat;
typepointer=^nodetype;
nodetype=record
data:
integer;
next:
pointer;
end;
var
head,p,r:
pointer;{r指向链表的当前最后一个结点,可以称为尾指针}
x:
integer;
begin
writeln('pleaseinputnum(-1isend):
');
read(x);
new(head);{申请头结点}
head:
=nil;{头结点初始化}
r:
=head;
whilex<>-1do{读入的数非-1}
begin
new(p);{则,申请一个新结点}
p^.data:
=x;
p^.next:
=nil;
r^.next:
=p;{把新结点链接到前面的链表中,实际上r是p的直接前趋}
r:
=p;{尾指针后移一个}
read(x);
end;
r^.next:
=nil;{最后一个结点的指针域赋空}
readln;
writeln('output:
');{输出}
p:
=head^.next;{头指针没有数据,只要从第一个结点开始就可以了}
whilep^.next<>nildo
begin
write(p^.data:
4);
p:
=p^.next;
end;
write(p^.data:
4);{最后一个结点的数据单独输出,也可以改用REPEAT循环}
readln;
end.{请大家改写这个程序,把链表的实际结点个数存入到头结点中,并输出}
(三)单链表的操作
1.查找“数据域满足一定条件的结点”
p:
=head^next;
while(p^.data<>x)and(p^.next<>nil)dop:
=p^.next;{找到第一个就结束}
ifp^.data=xthen找到了处理else输出不存在;
如果想找到所有满足条件的结点,则修改如下:
p:
=head^next;
whilep^.next<>nildo{一个一个判断}
begin
ifp^.data=xthen找到一个处理一个;
p:
=p^.next;
end;
2.取出单链表的第i个结点的数据域
functionget(head:
pointer;i:
integer):
integer;
var
p:
pointer;j:
integer;
begin
p:
=head^.next;
j:
=1;
while(p<>nil)and(j
begin
p:
=p^.next;
j:
=j+1;
end;
if(p<>nil)and(j=i)thenwriteln(p^.data)
elsewriteln(‘inotexsit!
’);
end;
3.插入一个结点在单链表中去
procedureinsert(head:
pointer;i:
integer;x:
integer);{插入X到第i个元素之前}
var
p,s:
pointer;j:
integer;
begin
p:
=head;
j:
=0;
while(p<>nil)and(j begin p: =p^.next; j: =j+1; end; if(p=nil)or(j>i-1)thenwriteln(‘nothisposition! ’) elsebegin{插入} new(s); s^.data: =x; s^.next: =p^.next; p^.next: =s; end; end; 4.删除单链表中的第i个结点(如下图的“b”结点) proceduredelete(head: pointer;i: integer;);{删除第i个元素} var p,s: pointer;j: integer; begin p: =head; j: =0; while(p^.next<>nil)and(j begin p: =p^.next; j: =j+1; end;{p指向第i-1个结点} if(p^.next=nil)or(j>i-1)thenwriteln(‘nothisposition! ’) elsebegin{删除p的后继结点,假设为s} s: =p^.next; p^.next: =p^.next^.next;{或p^.next: =s^.next} dispose(s); end; end; 5.求单链表的实际长度 functionlen(head: pointer): integer; var n: integer; begin p: =head; n: =0; whilep<>nildo begin n: =n+1; p: =p^.next; end; len: =n; end; (四)双向链表 每个结点有两个指针域和若干数据域,其中一个指针域指向它的前趋结点,一个指向它的后继结点。 它的优点是访问、插入、删除更方便,速度也快了。 但“是以空间换时间”。 数据结构的定义: typepointer=^nodetype; nodetype=record data: datatype; pre,next: pointer;{pre指向前趋,next指向后继} end; varhead,p,q,r: pointer; 下面给出双向链表的插入和删除过程。 Procedureinsert(head: pointer;i,x: integer);{在双向链表的第i个结点之前插入X} Var s,p: pointer;j: integer; Begin New(s); S^.data: =x; P: =head; j: =0; while(p^.next<>nil)and(j begin p: =p^.next; j: =j+1; end;{p指向第i个结点} ifp=nilthenwriteln(‘nothisposition! ’) elsebegin{将结点S插入到结点P之前} s^.pre: =p^.pre;{将S的前趋指向P的前趋} p^.pre: =s;{将S作为P的新前趋} s^.next: =p;{将S的后继指向P} p^.pre^.next: =s;{将P的本来前趋结点的后继指向S} end; End; Proceduredelete(head: pointer;i: integer);{删除双向链表的第i个结点} Var p: pointer;j: integer; Begin P: =head; j: =0; while(p^.next<>nil)and(j begin p: =p^.next; j: =j+1; end;{p指向第i个结点} ifp=nilthenwriteln(‘nothisposition! ’) elsebegin{将结点P删除} p^.pre^next: =p^.next;{P的前趋结点的后继赋值为P的后继} p^.next^.pre: =p^.pre;{P的后继结点的前趋赋值为P的前趋} end; End; (五)循环链表 单向循环链表: 最后一个结点的指针指向头结点。 如下图: 双向循环链表: 最后一个结点的指针指向头结点,且头结点的前趋指向最后一个结点。 如下图: 循环链表的应用举例: 约瑟夫问题。 [问题描述] 有n只猴子,按顺时针方向围成一圈(开始时编号为1,2,……n),选大王。 从第1号猴子开始报数1,2,3……,数到m号时该猴子退出到圈外,如此报数直到圈内只剩下一只猴子时,此猴便是大王。 你的任务是从键盘读入n,m,程序判断输出最后的大王是几号? [数据结构和算法分析] 数据结构: 显然是一个单向循环链表。 数据域为猴子的编号,指针域为下一个猴子的地址。 算法: 报数实际上是计数,只要设一个计数器就可以了。 当计数器由0变化到m时,删除该结点,计数器回0继续计数(或者用求余运算)。 直到链表中剩下一个结点。 [参考程序] programking(input,output); typepointer=^monkey; monkey=record num: integer; next: pointer; end; var head,p,q: pointer; n,m: integer; procedurecreat(varhead: pointer;n: integer);{建立一个单向循环链表} var p,q: pointer; i: integer; begin new(p);{建立头结点} head: =p; p^.num: =1; q: =p;{q指向链表的尾结点} fori: =2tondo{建立链表} begin new(p); p^.num: =i; q^.next: =p;{把P结点连接到q的后面} q: =p; end; q^.next: =head;{建立循环链表} end; procedureselectking(varhead: pointer;varm: integer); var p,q: pointer; i,count: integer; begin p: =head; count: =1;{指向第一个结点,洋计数1} q: =p;{q为p的前趋} repeat p: =q^.next; count: =count+1; ifcountmodm=0thenbegin{该猴子出圈,即删除结点} q^.next: =p^.next; dispose(p); end elseq: =p;{指针往后移一个} untilp^.next=p;{只剩下一个结点} head: =p; end; begin{main} write(‘inputmonkeynum: ’); readln(n); writeln(‘thebaoshunumber: ’); readln(m); creat(head,n); selectking(head,m); writeln(‘themoneykingisno.’,head^.num); readln end. [运行测试] 输入: inputmonkeynum: 13 thebaoshunumber: 5 输出: themoneykingisno.6 (六)线性表的应用举例 1.链表的归并操作 [问题描述] 已知线性表L1和L2中的数据元素按值非递减有序排列,现要求将L1和L2归并成一个新的线性表L3,使L3中的数据元素仍按非递减有序排列。 例如: L1=(1,3,4,5,8,9,10,11,12),L2=(2,4,6,8),则L3=(1,2,3,4,4,5,6,8,8,9,10,11,12)。 注意: 相同元素照算。 [标准过程] proceduremerge(h1,h2: pointer;varh3: pointer);{将头指针分别为h1,h2的两个单链表归并成一个新的单链表,该链表头指针为h3} var p1,p2,p3: pointer;{临时用工作指针,一般不能破坏头指针} begin p1: =h1^.next; p2: =h2^.next; h3: =h1;{新链表共用第一个链表,简化,也可以另外开辟一个头结点} p3: =h3; while(p1<>nil)and(p2<>nil)do{归并} begin ifp1^.data<=p2^.datathen begin{将p1结点链接到p3中去} p3^.next: =p1;{指向} p3: =p1;{p3后移} p1: =p1^.next{p1后移} end elsebegin{将p2结点链接到p3中去} p3^.next: =p2; p3: =p2; p2: =p2^.next end; end; ifp1<>nilthenp3^.next: =p1{将p1中剩下的结点一起链接到p3中} elsep3^.next: =p2;{将p2中剩下的结点一起链接到p3中} end; 2.一元多相式的表示和加减运算 [问题描述] 在数学上,一个一元n次多项式Pn(x),可以按升幂写成: Pn(x)=P0+P1X+P2X2+P3X3+……+PnXn 它由n+1个系数唯一确定。 因此,在计算机里,它可以用一个线性表P来表示: P=(P0,P1,P2,……Pn) 每一项的指数i隐含在系数Pi的序号里。 [任务] 给定一个一元n次多项式Pn(x)和一个一元m次多项式Qm(x),求它们的和与差。 [数据结构] 方法1: 按n,m分别生成n+1和m+1个结点的两个单链表,即不管系数是否为0都生成一个结点。 一个指针域指向后继结点,一个数据域存放系数(不存在的项系数为0)。 浪费了很多空间,尤其是指数很高,而项数很少的情况下,浪费更严重。 方法2: 只生成存在的项,实际多少项就有多少结点,每个结点有2个数据域,一个存放系数,一个存放指数。 如有以下多项式P8(x)=3+8x+9x5+6x8,用上述两种方法表示的示意图分别如下: 方法1示意图 方法2示意图 [算法分析] 算法非常简单,遍历两个单链表,根据指数和系数进行相应的加减,生成一个新链表。 系数为0的结点删除掉(或不生成这种结点),输出该链表。 3.魔术师与扑克问题 [问题描述] 13张黑桃扑克(A2345678910JQK),预先排好,正面朝下拿在魔术师的手里,从最上面开始,第一次数一张牌翻过来放在桌面上,正好是“A”;第二次数两张牌,数1的那张放在手中扑克的最下面,数2的那张翻过来放在桌面上正好是“2”;……,如此下去,放在桌面上的牌最后正好是“A2345678910JQK”的顺序(从下向上)。 [任务] 编程,找出魔术师手中扑克原来的排列顺序(从下向上)。 4.“法雷序列”问题 [问题描述] 对任意给定的一个自然数n(n<=100),将分母小于等于n的不可约的真分数按上升的次序排列,并且在第一个分数前加上0/1,在最后一个分数后加上1/1,这个序列称为n级法雷序列,以Fn表示,例如: F8=0/1,1/8,1/7,1/6,1/5,1/4,2/7,1/3,3/8,2/5,3/7,1/2,4/7,3/5,5/8,2/3,5/7,3/4,4/5,5/6,6/7,7/8,1/1。 [任务] 编程,求出n级法雷序列,每行输出10个分数。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 信息学 数据结构 教程 PASCAL