指针及线性链表.docx
《指针及线性链表.docx》由会员分享,可在线阅读,更多相关《指针及线性链表.docx(33页珍藏版)》请在冰豆网上搜索。
![指针及线性链表.docx](https://file1.bdocx.com/fileroot1/2023-5/24/991b0d9e-54ff-4a96-afc7-49c2eae2997c/991b0d9e-54ff-4a96-afc7-49c2eae2997c1.gif)
指针及线性链表
指针及线性链表
[内容提要]
1.了解静态存储、动态存储的概念以及各自的优缺点;
2.掌握指针类型及指针变量的含义、定义和使用方法;
3.掌握线性链表的基本概念;
4.掌握线性链表、循环链表、双向链表、双向循环链表的基本操作;
5.能够恰当应用指针和线性链表解决一些实际问题;
[重点难点]
1.重点:
指针概念和基本操作;
线性链表的基本操作;
线性链表的应用;
2.难点:
指针变量的含义、与静态变量的区别、使用方法;
线性链表的基本操作;
灵活运用线性链接表的思想解决实际问题;
[内容讲授]
一、静态存贮和动态存贮
1、静态存储
程序中的变量一经说明,计算机操作系统就会在内存空间中分配相应的存贮单元,其中变量名是存贮单元的地址,而变量的值是存贮单元的内容,且该存贮单元自始至终都被该变量所占用,直到程序结束。
如果变量是局部变量,那么在它的作用域内,一经说明也占有一定的存贮单元,直到退出其作用域为止。
这样的变量,在程序执行过程中,不能随时使用随时分配存贮空间,也不能在程序执行的过程中,释放这些空间。
也就是说,一旦给这些变量分配存贮空间,无论程序是否还需要使用,它们都要占用一定的存贮空间,以便给用户存贮数据。
我们称具有这样特点的存贮为静态存贮,它所对应的变量称为静态变量。
如字符类型、数组类型、记录类型等。
这类变量的优点是存贮方便,查找容易,可以通过一个简单的公式随机存取表中的任一元素,逻辑关系上相邻的两个元素在物理位置上也是相邻的,很容易找到前趋与后继元素;缺点是在线性表的长度不确定时,必须分配足够大的存储空间,经常浪费了宝贵的存储资源;而线性表的容量一经定义确定后就难以扩充;在插入和删除线性表的元素时,需要移动大量的元素,时间效率也比较差。
2、动态存贮
在程序执行过程中,通过向操作系统申请存贮空间或释放存贮空间的命令,达到动态管理计算机的存贮空间,以保证存贮空间的充分利用。
存贮空间可以随时申请、随时释放,这样的存贮方式称为动态存贮,其变量称为动态变量。
指针变量即为动态变量。
动态存储所需要的空间可以是不连续的,这样有利于充分利用零散的小空间。
但缺点是无法用O
(1)的时间实现存取了。
如何用这些零散的空间存储数组这些大规模数据呢?
如何表示这些数据之间的逻辑关系呢?
为了表示这些物理存储单元之间的逻辑关系,对于每个数据元素来说,除了要存储它本身的信息(数据域data)外,还要存储它的直接后继元素的存储位置(指针域,一般用link或next表示)。
我们往往把这两部分信息合在一起称为一个“结点node”。
N个结点链接在一起就构成了一个链表。
N=0时,称为空链表。
同时,为了按照逻辑顺序对链表中的元素进行各种操作,我们需要定义一个变量用来存储整个链表的第一个结点的物理位置,这个变量称为“头指针,一般用H或head表示”。
也可以把头指针定义成一个结点,称为“头结点”,头结点的数据域可以不存储任何信息,也可以存储线性表的长度等附加信息,头结点的指针域(头指针)存储指向第一个结点的指针,若线性表为空表,则头结点的指针域为空(NIL)。
由于最后一个元素没有后继,所以线性表中最后一个结点的指针域为空(NIL)。
由于此链表中的每个结点都只包含一个指针域,故称为“线性链表”或“单向链表”。
二、指针类型与指针变量
1.指针类型和指针变量的说明
Type指针类型标识符=^基类型名;{基类型不能为文件类型}
Var指针变量名:
指针类型标识符;
2.申请存储单元{动态申请、空间大小由指针变量的基类型决定}
New(指针变量名);{PASCAL标准过程}
3.指针变量的赋值
指针变量名:
=NIL;{初始化,暂时不指向任何存储单元}
如何表示和操作指针变量?
不同于简单变量(如A:
=0;),PASCAL规定用“指针变量名^”的形式引用指针变量(如P^:
=0;)。
区分如下图所示:
如计算机执行下面的程序段时:
new(H);
H^:
=123;
new(H);
H^:
=234;
内存示意图如下(阴影部分表示该单元已被占用):
4.相同基类型的指针变量之间可以进行相互赋值。
如有下面的程序段,可以画出右边的示意图:
var
p1,p2:
^integer;
begin
new(p1);
new(p2);
p1^:
=90;
p2^:
=80;
p1:
=p2
end.
5.关系运算
如:
ifp1=p2then……
whilep<>nildo……
6.释放动态存储单元
dispose(指针变量名);
系统收回指针变量所指的内存单元另作它用,此时指针变量的值变成无定义。
注意,我们应该养成一个好的习惯,就是及时释放不用的动态存储单元,很多同学使用指针变量时就知道new(p),而不知道及时dispose(p),最后造成内存空间溢出错误、出现死循环甚至死机现象。
三、单向链表
1、单向链表的结构
由于单向链表的每个结点都有一个数据域和一个指针域,所以,每个结点都可以定义成一个记录。
一般,把head称为头结点,head^.next称为头指针。
比如,有如下一个单向链表,如何定义这种数据结构呢?
方法如下:
type
pointer=^nodetype;
nodetype=record{嵌套定义}
data:
datatype;
next:
pointer;
end;
varhead,p,q,r:
pointer;
2、单链表的建立、输出
下面结合一个例子,给出建立并输出单向链表的程序。
例1、从键盘输入若干个正整数,请按输入顺序建立一个单向链表并输出它,输入-1时表示结束(creat.?
?
?
)。
Programcreat;
typepointer=^nodetype;
nodetype=record
data:
integer;
next:
pointer;
end;
varhead,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.
为了充分利用空间和随时统计出链表的实际结点个数,我们经常把链表的实际结点个数存入到头结点的数据域(head^.data)中,请大家改写上面的程序,并输出最后的结点个数。
参考程序见program\creat.pas。
3、查找“数据域满足一定条件的结点”
(1)从前往后找到第一个满足条件的结点,程序如下:
p:
=head^.next;
while(p^.data<>x)and(p^.next<>nil)dop:
=p^.next;{找到第一个就结束}
ifp^.data=xthen找到了处理else输出不存在;
(2)如果想找到所有满足条件的结点,则修改如下:
p:
=head^next;
whilep^.next<>nildo{一个一个判断}
begin
ifp^.data=xthen找到一个处理一个;
p:
=p^.next
end;
4、获取第i个结点的数据域
functionget(head:
pointer;i:
integer):
integer;
varp:
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;
5、插入一个结点到单链表中
一般情况:
s^.next:
=p^.next;p^.next:
=s;
特殊情况,插在表头:
s^.next:
=head;head:
=s;
插在表尾:
p^.next:
=s;p:
=s;{假设p已是表尾}
程序实现时,从表头开始找,是一致的。
procedureinsert(head:
pointer;i:
integer;x:
integer);{插入X到第i个元素之前}
varp,s:
pointer;j:
integer;
begin
p:
=head;
j:
=0;
while(p<>nil)and(jbeginp:
=p^.next;j:
=j+1end;
if(p=nil)or(j>i-1)thenwriteln(‘nothisposition!
’)
elsebegin{插入}
new(s);
s^.data:
=x;
s^.next:
=p^.next;
p^.next:
=s
end
end;
6、删除单向链表中的第i个结点(如下图中数据域为“b”的结点)
proceduredelete(head:
pointer;i:
integer;);{删除第i个元素}
varp,s:
pointer;j:
integer;
begin
p:
=head;
j:
=0;
while(p^.next<>nil)and(jbegin
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;
7、求单向链表的实际长度
functionlen(head:
pointer):
integer;
varn:
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}
Vars,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;{1、将S的前趋指向P的前趋}
p^.pre:
=s;{2、将S作为P的新前趋}
s^.next:
=p;{3、将S的后继指向P}
p^.pre^.next:
=s{4、将P的本来前趋结点的后继指向S}
end;
End;
Proceduredelete(head:
pointer;i:
integer);{删除双向链表的第i个结点}
Varp:
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;{1、P的前趋结点的后继赋值为P的后继}
p^.next^.pre:
=p^.pre{2、P的后继结点的前趋赋值为P的前趋}
end;
End;
五、循环链表
1、单向循环链表:
最后一个结点的指针指向头结点。
如下图:
2、双向循环链表:
最后一个结点的后继指针指向头结点,且头结点的前趋指针指向最后一个结点。
如下图:
3、循环链表的应用举例
例2、约瑟夫问题(king.?
?
?
)
[问题描述]
有n只猴子,按顺时针方向围成一圈(开始时编号为1,2,……,n),选大王。
从第1号猴子开始报数1,2,3,……,数到m号时该猴子退出到圈外,如此报数直到圈内只剩下一只猴子时,此猴便是大王。
你的任务是从键盘读入n,m(n>m>1),程序判断输出最后的大王是几号?
如输入:
135
输出:
6
换个问法:
n只猴子围成一个圈,按顺时针方向报数,报到m的出圈,直到剩下一只猴子结束。
输出猴子依次出圈的序号。
[问题分析]
很明显这是一个单向循环链表。
数据域为猴子的编号,指针域为下一个猴子的地址。
采用模拟法,从第1个猴子开始一一报数,报数实际上是计数,只要设一个计数器就可以了。
当计数器由1变化到m时,删除该结点,从下一个结点开始继续计数(计数器回1或者用求余运算)。
直到链表中只剩下一个结点。
[参考程序]
programking;
type
point=^node;
node=record
data:
integer;
next:
point
end;
varm,n,s:
integer;
p,q,head:
point;
begin
write('inputn,m:
');
readln(n,m);
new(head);q:
=head;head^.data:
=1;
fors:
=2tondo
begin
new(p);p^.data:
=s;q^.next:
=p;q:
=p
end;
q^.next:
=head;
s:
=1;q:
=head;
repeat
p:
=q^.next;s:
=s+1;
ifsmodm=0thenbegin
q^.next:
=p^.next;
writeln(p^.data:
4);
dispose(p)
end
elseq:
=p
untilq^.next=q;
writeln('thekingis:
',q^.data)
end.
[输入输出样例]
输入:
135
输出:
5
10
2
8
1
9
4
13
12
3
7
11
thekingis:
6
六、线性表的综合应用
例3、用单向链表实现线性表的归并操作(merge.?
?
?
)
[问题描述]
已知线性表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,注意h3要用变量型参数}
varp1,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^.datathenbegin{将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;
例4、一元多相式的表示和加减运算(add.?
?
?
)
[问题描述]
在数学上,一个一元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),输出它们的和。
注意:
不许输出系数为0的项、不要输出为1的系数和幂,且按幂的升序输出。
[输入输出样例]
输入:
3x^2+8-5x^6+x
6x^6+5x-3x^2+8x^9-20
输出:
-12+6x+x^6+8x^9
[数据结构]
方法1:
按n,m分别生成n+1和m+1个结点的两个单链表,即不管系数是否为0都生成一个结点。
一个指针域指向后继结点,一个数据域存放系数(不存在的项系数为0)。
浪费了很多空间,尤其是指数很高,而项数很少的情况下,浪费更严重。
方法2:
只生成存在的项,实际多少项就有多少结点,每个结点有2个数据域,一个存放系数,一个存放指数。
如有以下多项式P8(x)=3+8x+9x5+6x8,用上述两种方法表示的示意图分别如下:
方法1示意图
方法2示意图
[算法分析]
先生成两个单链表(按插入排序的方式生成,使链表按幂升序),然后遍历两个链表,根据指数和系数进行相应的加减,生成一个新链表。
系数为0的结点删除掉(或不生成这种结点),输出该链表(不要输出系数中的1和幂1,但如果是常数项1,则要输出)。
[参考程序]
programmerge;
typepoint=^node;
node=record
coe,exp:
longint;
next:
point;
end;
varh1,h2,h3:
point;
procedureput(h:
point;coe,exp:
longint);
varp,newp,tmp:
point;
begin
p:
=h;
while(p^.next<>nil)and(p^.next^.exp=p^.next;
if(p^.next=nil)or(p^.next^.exp>exp)then
begin
new(newp);newp^.coe:
=coe;newp^.exp:
=exp;
newp^.next:
=p^.next;p^.next:
=newp;
endelse
begin
p^.next^.coe:
=p^.next^.coe+coe;
ifp^.next^.coe=0then
begin
tmp:
=p^.next;
p^.next:
=tmp^.next;
dispose(tmp);
end;
end;
end;
procedurereadata(h:
point);
vars:
string;
i,st,coe,exp:
longint;
positive,yesexp:
boolean;
begin
readln(s);
s:
=s+'+';
coe:
=0;exp:
=0;
ifs[1]='-'
thenbeginst:
=2;positive:
=false;end
elsebeginst:
=1;positive:
=true;end;
yesexp:
=false;
fori:
=sttolength(s)do
if(s[i]='+')or(s[i]='-')then
begin
ifnotpositivethencoe:
=-coe;
put(h,coe,exp);
ifs[i]='+'thenpositive:
=trueelsepositive:
=false;
coe:
=0;exp:
=0;yesexp:
=false;
endelse
ifs[i]='^'thenyesexp:
=trueelse
ifs[i]='x'then
begin
if(i=1)or(s[i-1]='+')or(s[i-1]='-')thencoe:
=1;
ifs[i+1]<>'^'thenexp:
=1;
endelse
ifyesexp
thenexp:
=exp*10+ord(s[i])-48
elsecoe:
=coe*10+ord(s[i])-48;
end;
proceduremerge(h1,h2,h3:
point);
varp,tail,p1,p2,newp:
point;
begin
tail:
=h3;
p1:
=h1;p2:
=h2;
while(p1^.next<>nil)and(p2^.next<>nil)do
begin
ifp1^.next^.exp<=p2^.next^.ex