线性表

线性表及其基本运算

一、            线性表(linear_list)

线性表示n个数据元素的有限序列,记为L=(a1,a2,…,an)

(线性表是最常用且最简单的一种数据结构)

形式化定义:linear_list=(D,R)

其中D={ai | ai ∈D0,i=1,2,…,n,n>=0}(属于相同的数据对象,具有相同的特性)

R={N},N={<ai-1,ai>| ai-1,ai ∈D0,i=2,3,…,n}

N是一个序偶的集合,它表示线性表中数据元素之间的相邻关系ai-1是ai的直接前驱,ai是ai-1的直接后继

 

二、            基本运算

INITIATE(L) 初始化操作:设定一个空的线性表L(线性表元素个数为0)

LENGTH(L)求长度函数:函数值为线性表L中数据元素的个数

GET(L,i)取元素函数:1<=1<=LENGTH(L)时返回L中第i个数据元素,否则为空元素NULL.(i称为该数据元素在L中的位序)

PRIOR(L,elm) 求前驱函数:elm为L中的一个数据元素,若它的位序大于1,则函数值为elm前驱,否则为NULL

NEXT(L,elm)求后继函数:若elm的位序小于表长,则函数值为elm的后继,否则为NULL

LOCATE(L,x)定位函数:给定值x,若x不在表中,则返回0,否则返回x在表中第一次出现的位序

INSERTE(L,i,b) 前插操作:在第i个元素之前插入新元素b,i的取值范围为1<=i

<=n+1;i=n+1表示在表尾插入,n为表长

DELETE(L,i)删除操作:删除线性表L中的第i个元素,1<=i<=n

EMPTY(L) 判空表函数:若L为空表,则返回布尔值“true”否则返回布尔值“false”

CLEAR(L) 表置空操作:将L置为空表(无返回类型)

 

1求两个集合的并,即A=A∪B

分析:设A,B分别由两个线性表LA和LB表示,要求将LB中存在而LA中不存在的DE插入到表LA中。

算法思想:(1)依次从LB中取出一个DE;(GET(LB,i))

(2)判断在LA中是否存在;(LOCATE(LA,x))

(3)若不存在,则插入到LA中。(INSERTE(LA,n+1,b)插在表尾)

形式化算法描述

PROC union(VAR LA:Linear_list;LB:Linear_liist);

{将所有在LB中存在而LA中不存在的DE插入到LA中去}

n=LENGTH(LA);{确定线性表LA的长度}

FOR i:=1 TOLENGTH(LB) DO

[x:=GET(LB,i);{取LB中第i个数据元素}

k:=LOCATE(LA,x);{在LA中进行搜索}

IF k=0THEN[INSERT(LA,n+1,x);{在LA表尾插入}

n:=n+1;{表长加1}] ]

ENDP:{union}

 

例2归并两个有序的线性表LA和LB为一个新的有序线性表LC

算法思想:

(1)       初始化:置LC为空表,设置变量i,j,初值为1,分别指向LA和LB的第一个DE,k表示LC的长度,初始化 (INITIATE(LC))

(2)       当i<=LENGTH(LA) AND j<=LENGTH(LB)时,判断:若i所指的元素<=j所指的元素,则将i所指的元素插入在LC的k+1前,并且i,k的值分别加1;否则,将j所指的元素插入在LC的k+1前,并且j,k的值分别加1(INSERTE(LC,k+1,b))

(3)       重复(2)直到某个表的元素插入完毕。

(4)       将未插入完的表的余下的元素,依次插入在LC后(INSERTE(LC,k+1,b))

形式化算法描述

PROC merge_list(LA,LB:Linear_liist ; VAR LC:Linear_list);

{ LA,LB中元素依值非递减有序排列,归并得到的LC的元素仍依值非递减有序排列}

INITIATE(LC);i:=1;j:=1;k:=1;{初始化}

WHILE(i<=LENGTH(LA))AND(j<=LENGTH(LB))DO

IFGET(LA,i)<=GET(LB,j)

THEN [INSERT(LC,k+1,GET(LA,i));k=k+1;i=i+1]

ELSE [INSERT(LC,k+1,GET(LB,j));k=k+1;j=j+1];

WHILE i<=LENGTH(LA) DO

[INSERT(LC,k+1, GET(LA,i));k=k+1;i=i+1]

WHILE j<=LENGTH(LB) DO

[INSERT(LC,k+1, GET(LB,j));k=k+1;j=j+1];

ENDP:{merge_list}

算法分析:

主要操作是插入

语句频度:LENGTH(LA)+LENGTH(LB)

算法时间复杂度:O(LENGTH(LA)+LENGTH(LB)),若LA和LB的元素个数同为数量级n,则该算法的时间复杂度为O(n)。

 

线性表的顺序存储结构

一、顺序存储结构(物理结构的一种)

用一组地址连续的存储单元依次存储线性表的元素,设线性表的每个元素占用k个存储单元,则第i个元素ai的存储位置为:Loc(ai)=Loc(a1)+(i-1)*k 其中,Loc(ai)为线性表的起址。

逻辑存放次序决定物理存放次序,逻辑存放决定物理存放次序,线性表的存储结构和逻辑存储一一对应。

线性表顺序存储结构的定义为:(定义要使用的线性表和存储空间)

CONST maxlen=线性表可能达到的最大长度;

TYPE sqlisttp=RECORD

 

elem:ARRAY[1…maxlen] OF elemtp 数据元素是相同类型的,允许是任何类型

last:0…maxlen           END

  

线性表的顺序存储结构是一个记录型的结构。

(数据域elem描述了线性表中的DE占用的数组空间,数组的第i个分量为线性表中第i个DE的存储映象;

数据域last指示最后一个DE在数组空间中的位置,也是表长。

 

二、            插入和删除操作

1.      插入运算INSERT(L,i,b)

插入前:L=(a1,…,ai-1,ai,…,an)

插入后:L=(a1,…,ai-1,b,ai,…,an)

算法思想:

(1)      进行合法性检查,(表头插入)1<=i<=n+1(表尾插入)

(2)      检查线性表是否已满;(L超过内存空间,导致溢出)

(3)      将第n个至第i个元素逐一后移一个单元;(从后往前)

(4)      在第i个位置处插入新元素;

(5)      将表的长度加1。

(1)(2)考虑边界条件,考虑程序健壮性

 

形式化算法描述:

PROC ins_sqlist(VAR v:sqlisttp; i:integerb:elemtp );

{在顺序存储结构的线性表v中第i个DE之前插入b}

IF(i<1) OR (i>v.last+1)

THEN ERROR(‘i值不合法’)

ELSE IFv.last>=maxlen

THEN ERROR(‘表满溢出’)

ELSE FOR j:=v.last DOWNTO i DO

v.elem[j+1]:=v.elem[j];{右移}

v,elem[i]:=b;

v.last:=v.last+1;

ENDP:{ins_sqlisr}

 

插入算法时间复杂度分析:

最坏情况是在第1个元素前插入(i=1),此时要后移n个元素,

因此T(N)=O(N)

 

2.      删除运算DELETE(L,i)

删除前:L=(a1,…,ai-1,ai,ai+1,…,an)

删除后:L=(a1,…,ai-1,ai+1,…,an)

算法思想:

(1)   进行合法性检查,1<=i<=n+1

(2)   判断线性表是否已空,v.last=0;

(3)   将第i+1至第n个元素逐一向前移一个位置;

(4)   将表长的长度减1

 

形式化算法描述:

PROC del_sqlist(VAR v:sqlisttp; i:integer);

{在顺序存储结构的线性表v中删除第i个DE}

IF(i<1) OR (i>v.last)

THEN ERROR(‘i值错误或者表已空’)

ELSE FOR j:=i+1TO v.last DO

v.elem[j-1]:=v.elem[j];{左移}

v.last:=v.last-1;

ENDP:{del_sqlist}

时间复杂度分析:

最坏情况是删除第一个元素,此时要前移n-1个元素

因此,T(N)=O(N)

 

三、            线性表顺序存储结构的特点

优点:

(1)      逻辑上相邻的元素,在物理位置也相邻;

(2)      可随机存取表中任一元素;

局限性:

(3)      必须按最大可能的长度预分存储空间,存储空间利用率低,表的容量难以扩充,是一种静态存储结构;

(4)      插入删除时,需移动大量元素,平均移动元素为n/2。

 

3顺序结构上的归并有序表算法

PROC merge_sqlist(va, vb: sqlisttp; VAR vc :sqlisttp);

i:=1; j:=1; k:=0;

WHILE ((i<=va.last) AND (j<=vb.last))DO

IF va.elem[i]<=vb.elem[j]

       THEN[vc.elem[k+1]:=va.elem[i]; k=k+1; i=i+1];

ELSE[vc.elem[k+1]:=vb.elem[j]; k=k+1; j=j+1];

WHILE i<=va.last DO

[vc.elem[k+1]:=va.elem[i]; k=k+1; i=i+1];

WHILE j<=va.last DO

[vc.elem[k+1]:=va.elem[j]; k=k+1; j=j+1];

vc.last:=k

ENDP: {merge_list}

 

线性表的链式存储结构

一、            线性链表

1.      链式存储结构

用一组任意的存储单元(不要求地址联系)来存储线性表中的元素,每个元素对应一组存储单元(结点),每个结点包括两个域:存储数据元素信息的数据域和存储直接后继所在位置的指针域

N个结点通过指针域组成的表,称为线性链表(单链表)

(1)线性表最后一个结点的指针域为“空”(NIL或者∧);

(2)用一个头指针指示链表中第一个结点的存储位置;

(3)链表L=(a1,a2,…,an)逻辑表示

用pascal的指针类型定义单链表

TYPE pointer=↑nodetype;

     nodetype=RECORD

            data:elemtp;

            next:pointer

END;

Linkisttp=pointer;{头指针可以唯一确定一个单链表}

pointer为一指针,指向nodetype记录类型;

nodetype为一记录,它由data和next两项组成;

data为数据元素类型,next为pointer指针;

单链表linkisttp定义为pointer指针。

 

2.      带头结点的线性链表

在线性链表的第一个元素结点之前附设一个结点(称头结点),它的数据域不存储任何信息,其指针域存储第一个元素结点的存储位置。头指针L指向该头结点。

空表时:L↑.next=NIL (头结点没有后继,空表中包括头结点)

带头结点链表的引入是为了使算法判空和处理一致。

 

3.   几种基本运算在单链表上的实现

(1)    GET(L,i)函数

FUNC get_linklist(la:linkisttp;i:integer):elemtp;

p:=la↑.next; j:=1{移动指针p,计数变量j}

WHLIE(p<>NUL AND j<i)DO {条件1,防止i>表长}

[P:=P↑.next ; j:=j+1;] {条件2控制取第i个,防止i<1}

IF(p<>NULAND j=i){只有一个条件不充分,找到}                     

THEN RETURN(p↑.data)   {正常出循环}

ELSE RETURN(NULL)  {异常出循环}

ENDF:{get_linklist}

 

边界条件:i的合法性检查已经蕴含在WHILE和if条件中;

循环条件分析:

p:=la↑.next; j:=1

WHLIE(p<>NUL AND j<i)DO [P:=P↑.next ; j:=j+1;]

条件1:防止i>表长,条件2:控制取第i个,并防止了i<1。

两个条件有6钟组合:

1.P=NIL AND j<i 空表且i>1或i>表长+1,异常,返回NULL

2.P=NIL AND j=i 空表且i=1或i=表长+1,异常,返回NULL

3.P=NIL AND j>i 空表且i<1,异常出循环,返回NULL

4. p<>NULAND j<i 继续循环

5. p<>NULAND j=i 确定第i个结点,正常出循环(找到的条件)

6. p<>NUL ANDj>i i<1,异常出循环,返回NULL

 

算法时间复杂度分析:WHILE最多执行i-1次,最坏情况是取第n个结点,需执行n-1次,故:T(N)=O(N)

 

(2)    INSERT(L,i,b) 插入运算

设在单链表结点x和结点y之间插入新结点b,已知p为指向结点x的指针,s为指向新结点b的指针。

插入前:  插入后

插入运算可以由定位修改指针来完成

定位:得到指针y的前驱的指针p

修改指针:s↑.next:= p↑.next;

         p↑.next:=s; (顺序不能变)

 

PROCins_linklist(la:linkisttp; i:integer; b:elemtp);

{la为带头结点单链表的头指针}

p:=la; j:=0;{置初值p指向头结点}

WHILE(P<>NILAND j<i-1) DO {定位}

[p:= p↑.next; j:=j+1;]

IF(P=NIL ORj>i-1)

THEN ERROR(“ 插入位置不对”)

ELSE[new(s):s↑.data:=b;{插入}

s↑.next:=p↑.next; p↑.next:=s;]

ENDP:{ins_linklist}

 

与get_linklist函数的区别:

初值保证能在第一个DE之前插入,插入范围为[1,表长+1]

循环定位条件,定位在i的直接前驱i-1

出循环IF条件是get_linklist的求反,定位后,插入

 

循环条件分析:

第一次循环p<>NIL,j有三种可能

(1)       j<i-1 继续循环;

(2)       j=i-1此时i=1,在第一个元素前插入;

(3)       j>i-1 i<1不合法

第二次循环后p可能为NIL,但j>i-1不可能,出现的各种可能情况:

(1)       p<>NIL AND j<i-1 继续循环;

(2)       p<>NIL AND j=i-1 已经定位,出循环

(3)       p=NIL AND j<=i-1 i不合法(i>=表长+2),出循环

 

算法复杂度:

关键在定位,最坏情况是INSERT(L,n+1,b)WHILE 执行n次

故T(N)=O(N)

 

(3)DELETE(L,i)删除运算

删除运算可以由定位和修改指针来完场:

定位:得到指向第i个元素的前驱的指针p;

修改指针:p↑.next:= p↑.next↑.next;

(删除运算和插入运算都定位到i-1个,i的取值范围不同)

 

PROCdel_linklist(la:linkisttp; i:integer);

{la为带头结点单链表的头指针}

p:=la; j:=0;{置初值p指向头结点}

WHILE(P↑.next <>NIL AND j<i-1) DO {定位}

[p:= p↑.next; j:=j+1;]

IF(P↑.next =NIL OR j>i-1)

THEN ERROR(“ 表空或者位置不对”)

ELSE[q:= p↑.next ;

p↑.next:=p↑.next↑.next;

     dispose(q)]

ENDP:{del_linklist}

 

删除算法讨论:

删除范围为[1,表长],不能删除头结点;

出循环的五种可能情况:

P↑.next =NIL AND j<i-1 空表或i>表长

P↑.next =NIL AND j=i-1 空表且i=1,或i=表长+1

P↑.next =NIL AND j>i-1 空表且i<1

P↑.next <>NIL AND j=i-1 非空表且已定位

P↑.next <>NIL AND j>i-1 非空表且i<1

 

时间复杂度:

WHILE至多执行i-1次,当i=n时为最坏情形,因此,T(N)=O(N)

 

(4)建立链表的算法

算法思想:从空链表开始,依次插入各个结点。

PROC crt_linklist(VAR la:linkisttp; a:ARRAY[1…n] OF elemtp);

new(la):la↑.next=NIL;{建立空表,只有一个头结点}

FOR i:=n DOWNTO 1 DO

[new(p):p↑.data=a[i];{等价于INSERT(L,1,a[i])}

p↑.next= la↑.next;la↑.next=p]

ENDP:{crt_linklist}

算法时间复杂度:T(N)=O(N)

思考:为什么从n到1依次插入?若从1到n算法将如何实现?

 

例:在单链表上实现归并两个有序表,要求产生的结果有序表仍用原有序表的结点空间。

PROCmerge_linklist(la,lb:linkisttp;VAR lc:linkisttp);

{la和lb为参与归并的两个有序表,lc为结果有序表}

pa:=la↑.next;pb:=lb↑.next;lc:=la;pc:=lc;

WHILE(pa<>NIL AND pb<>NIL)DO

IF pa↑.data<= pb↑.data

    THEN[pc↑.next:=pa; pc:=pa; pa:= pc↑.next]

    ELSE[pc↑.next:=pb; pc:=pb; pb:= pc↑.next];

IF(pa<>NIL)

THEN pc↑.next:=pa

ELSE pc↑.next:=pb;

dispose(lb)

ENDP:{ merge_linklist }

 

4.   线性表链式存储结构的特点

(1)       逻辑上相邻的元素,其物理位置不一定相邻:元素之间的邻接关系由指针域指示。

(2)       链表是非随机存取的存储结构;对链表的存取必须从头指针开始。

(3)       链表是一种动态存储结构;链表的结点可调用new()申请和dispose()释放。

(4)       插入删除运算非常方便;只需要改相应指针值。

 

二、            循环链表

1.   循环链表的存储结构

令链表中最后一个结点的指针域指向头结点,使整个链表形成一个环,称这样的链表为循环链表。

循环链表H=(a1,a2,…,an)逻辑表示为:

空表时:H=H↑.next;

 

2.   循环链表的特点

(1)       从任一结点出发,沿着链可访问全部结点;

(2)       运算与单链表基本一致,仅是循环结束条件为P↑.next=H(H为头指针);

 

 

线性表的链式存储结构——双向链表

三、            双向链表

1.      双向链表的存储结构

双向链表中每一个结点有两个指针域;priou和next,priou指向直接前驱;next指向直接后继。

循环链表L=(a1,a2,…,an)逻辑表示为:

 

2.双向链表的特点

(1)在双向链表中,查找某结点直接前驱PRIOR(L,elem)和直接后继NEXT(L,elem)的运算的时间复杂度均为O(1)。

(2)空表时:L↑.priou=L↑.next=NIL

(3)双向链表也可以首尾相连构成双向循环链表。双向循环链表满足L↑.priou=L↑.next=L。

(4)在双向链表中,除插入、删除操作差别比较大外,其它基本运算均与单链表相同。在P结点之前插入S结点应该做如下修改动作:

插入前:

插入后:

四个指针域修改:

p↑.priou↑.next=s;

s↑.priou= p↑.priou;

p↑.priou=s;

s↑.next=p;

(语句顺序不固定,要防止断链)

 

 

线性表的应用——一元多项式的表示

一元多项式按升幂可以表示为Pn(x)=p0+p1x+p2x2+…+pnxn

1.   多项式的几种存储结构

(1)    全部系数顺序存储结构

将多项式的所有幂的系数pi(i=0,1……n)依次存放在数组中。对幂很高,而系数为零的项也很多时,存储空间浪费很大。

例:S(x)=1+3x100 +2x20000 需要20001项

(2)    非零系数顺序存储结构

Pn(x)又可用非零系数项表示为:

Pn(x)=p1xe1+p2xe2+…+pnxen

其中pi为非零系数,ei为相应的指数。可以只将多项式的所有非零系数pi和相应的指数ei存放在数组中。

(3)    非零系数单链表存储结构

线性表的经典应用时表示一元多项式,并很容易实现多项式各种运算,

用单链表表示一元多项式时,每个结点有三个域;

系数域coef,指数域exp,指针域next。

S(x)=1+3x100+2x20000

 

2.   多项式相加算法的实现

设:(1)多项式采用非零系数单链表结构

(2)多项式A(x)和B(x)相加,“和多项式”C(x)的结点不另外申请存储空间;

(3)p,q分别指向A(x)和B(x)中的某结点。

        运算规则:指数相同,系数相加。

        若p↑.exp<q↑.exp,则p结点为C(x)的一项,移动p;

        若p↑.exp>q↑.exp,则q结点插入在p结点之前,移动q;

        若p↑.exp=q↑.exp,则p↑.coef:=p↑.coef +q↑.coef;释放q结点;当和为0时,释放p结点;移动p和q;

 

PROC add_poly(VARpa:polytp; pb:polytp);

    p:=pa↑.next; q:= pb↑.next;

    pre:=pa; pc:=pa;{pre指向p的直接前驱,pc为和多项式的头指针}

    WHILE p<>NIL AND q<>NIL DO

        CASE

            p↑.exp<q↑.exp:[pre:=p; p:= p↑.next];

            p↑.exp=q↑.exp:[ x:=p↑.coef +q↑.coef;

IF x<>0 THEN [p↑.coef:=x;pre:=p]

ELSE[pre↑.next= p↑.next; dispose(p)];

p:= pre↑.next; u:=q; q:=q↑.next;

dispose(u)];

p↑.exp>q↑.exp:[ u:=q↑.next; q↑.next=p;pre↑.next= q;pre:=q;q:=u]

 ENDC:

IF q<>NIL THEN pre↑.next:=q;

dispose(pb)

ENDP:{add_poly}

 

算法难点分析:

1.             pre指针的作用:pre为p的直接前驱,在将q插入在p之前,或者删除“和为0”的项时,以及最后将pb的剩余项挂入“和多项式”时,要用到p的直接前驱。

2.             三处dispose()的含义:

(1)dispose(u)是指数相同时,释放q结点;

(2)dispose(p)是指数相同且和系数为0时,释放p结点;

(3)dispose(pb)是释放多项式B的头结点。

3.             算法结束的三种可能:

(1)A、B最高次幂次相同:p、q都为NIL;

(2)A最高次幂高于B:q=NIL,结束;

(3)B最高次幂高于A:p=NIL,并将B中剩余项挂入A的尾端。

4.             最坏情形为:各指数均不相同,且B中余项仅一项,则算法的时间复杂度为O(m+n),即O(max(m,n)),m,n分别为A、B的最高次幂。当m与n相当时,时间复杂度为O(n)。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值