当前位置:文章首页 >> 程序设计 >> 数据结构 >> 线性表
线性表
2007-01-12 10:37:09  作者:佚名  来源:互联网  文字大小:【】【】【
  •   几乎所有的程序设计语言都定义数组为一种固有的数据类型,事实上,数组本身就具有线性表的性质。例如:一维数组,它的每个数组元素对应于一个下标,二维数组,它的每个数组元素对应于一对下标,对于n维数组,它的每个元素对应一组n个下标。而数组元素的数据类型都是相

    几乎所有的程序设计语言都定义数组为一种固有的数据类型,事实上,数组本身就具有线性表的性质。例如:一维数组,它的每个数组元素对应于一个下标,二维数组,它的每个数组元素对应于一对下标,对于 n 维数组,它的每个元素对应一组 n 个下标。而数组元素的数据类型都是相同的,元素的有序性可由下标的顺序关系来确定。但是数组与一般的线性表不同,数组一般不作插入和删除操作,而通常只定义两种操作,即对于给定的下标读取和修改相应的数组元素的值。

1.1 定义及基本运算

一.定义:长度为几的线性表是一种数据结构

    B=(K,R)

    其中:k由几个结点{k1,k2....kn}组成

    R只有一个关系N,且N确定了一个结点序列

    N={(ki-1,ki)/2<=i<=n}

n={(k1,k2),(k2,k1)....(kn-1,kn)}

    n--长度

    n=0空表

    k1--开始结点

    kn-- 端点

    ki-1--ki的直接前件

    ki+1--ki的直接后件

二、基本运算

    1.取(第i个结点)ki

    2.删(第i个结点)ki

    3.插入(在ki-1与ki之间)一新结点

    4.把k1放到它应有的位置上

三、对线必表的基本操作有以下几种:

1)INITIATE(L)初始操作.

2)LENGTH(L)求长度函数.

3)GET(L,I)取元素函数.

4)PRIOR(L,elm)求前驱函数.

5)NEXT(L,elm) 后继函数.

6)LOCATE(l,X) 定位函数.

7)INSERT(L,i,b) 前插操作.

8)DElETE(L,i)删除操作.

9)MPTY(L) 判空表函数.

10)CLEAR(L) 表置空操作.

1.2 线性表的顺序存储结构

一、存储方式
          定义:用一组地址连续的存贮这间线性的存贮线性表中的各元素

                       l───占单元数 loc(k1)──地址 loc(ki)───ki地址

                        基地址

k1     

k2     

..     

ki     

....  

kn       

    

b      

b+l    

       

b+(i-1)l

     

b+(n-1)l  

空间

 
二、特点

    逻辑关系相邻的结点物理位置上也相邻

    类型描述:

    const max=<线性表可能的最大长度>

    var r=arrraY[1..MAX] OF 数据类型

        n=integer;

    type sqlist=record

    顺序表 r=array[1..max] of elementype;

           n=integer

    end;

三、基本运算

                      1.取ki

                开始
                 ↓         是
                i<1 ─────↑───→出错,结束 ↓  i>A.n│
                   ----------------------------- ─────┘
                 ↓
                打 印 A.r[n]
                 ↓
                结束


       存贮说明:

       A────线性表

       I─────要取结点的序号


       procedure getsqlist (B:sqlist;i:integer)

       Begin if (i<1) or (i>A.n) 

       then call error ("出错')

       else write (A.r[i])

       END;


                    2.删除ki

                     pas.w
                     开 始
                       ↓
                      i<1 ↓ i>A.n
                       ↓
          ┌→  for  j=i+1  to A.n───┐
          │            ↓              │
          │    A.r[j-1]←A.r[j]        │
          │            │              │
          └──────┘              │
                          ┌──────┤
                          ↓ 
                         A.n←A.n-1
                           ↓
                          结束


      存贮说明:

      A───线性表

      i───要删结点的序号

      j───指示移动时的"指针"辅变


      procedure delisqlist (var A:sqlist;i:integer;j:integer)

      Begin if (i<10 or (i>A.n)

      then callerror ("出错")

      else [for j:=i+1 to a.n do

      A.n:=A.n-1]

      end;


                   3.插入(ki+s,ki) x:eementype

                       开始
                         ↓
                       A.n>=max───→出错,结束
                          ↓
                       i<1──────→出错,结束 ↓ i>A.n
                          ↓
            ┌─→  for j←a.n don't to i──┐
            │             ↓                │
            │     A.r[j+1]←A.r[j]          │
            └──────┤                 │
                                             │
                                             │
                           ┌────────┘
                           ↓
                         A.r[i]←x
                         A.n←A.n+1
                             ↓
                           结束


      procedure insqlist (var A:sqlist;i:integer);

      Begin if A.n>=max

      then call error ('出错')

      else if j(i<1) or (i>A.n)

      then call error ('不合理')

      else [for j:=A.n don't i do A.r[j+1]:=A.r[j];

      A.r[i]:=x;A.n:=A.n+1]

      END;


     分析:

          设:在表的任何位置插,删的概率相同

                   1                      1
          P(in)=  ──           q(del)= ──
                   n+1                    n
            插入                      删除


                   4.把k1放到它应有的位置上(k1,k2....kn)→(k1',....ks',k1,k1"....kl")

                        开始
                         ↓
                     T←A.r[1]   f3
                         ↓
             ┌──for   i←2  to  A.n →结束
             │          ↓
             │        r←A.r[i]
             │          ↓
             │       A.r[i]  f3>=T→x←A.r[i]───┐
             │          ↓ ←───────────┘
             │       for  j←1   own  to   1
             │          ↓
             │       A.r[j]←A.r[j-1]
             │          ↓
             │        A.r[j]←x
             │
             └───────↑
 

堆栈

2.1.1  抽象数据类型栈的定义

   栈(Stack)是限定仅在表尾进行插入或删除操作的线性表。因此, 对栈来说,表尾称为栈顶(top),相应地,表头称为栈底(bottom)。 不含元素的空表称为空栈。

   假设栈S=(a1,a2,...,an),则称a1为栈底元素,an为栈顶元素。 栈中元素按a1,a2,...,an的次序进栈,退栈的第一个元素应为 栈顶元素。因此,栈又成为后进先出(Last In First Out)的 线性表(简称LIFO结构),它的这个特点可用图(a)所示。  

2.1.2 栈的表示和实现

   和线性表类似,栈也有两种存储结构。

     顺序栈即栈的顺序存储结构是利用一组地址连续的存储单元依此存放自栈底到栈顶的数据元素,同时设指 针top指示栈顶元素的当前位置.空栈的栈顶指针值为零.

       假设用一维数组s(1:arrmax)表示栈,则对非空栈, s[1]为最早进入栈的元素,s[top]为最迟进入栈的元素,即栈顶元素.当top=arrmax时意为栈满,此时若有元素 入栈则将产生"数组越界"的错误,称为栈上溢,反之,top=0意为栈空,在应用中通常作为控制程序转移的条件.

        图3.2展示了顺序栈中数据元素和栈顶指针之间的对应关系.

以下是顺序栈的说明.由于顺序栈的入栈操作受数组上界的约束,当对栈的最大使用空间估计不足时,有可能 发生栈上溢,此时应向用户"报警".

2.2 表达式求值

       表达式求值是程序设计语言编译中的一个最基本问题.它的实现是栈应用的一个典型例子.这里 介绍一种简单直观,广为使用的算法,通常成为"算符优先法".

       要把一个表达式翻译成正确求值的一个机器指令序列,或直接对表达式求值,首先要能够正确 解释表达式.要对表达式求值,首先要了解算术四则运算的规则.即:

  • 1)  先乘除,后加减;

  • 2)  从左算到右;

  • 3)  先括号内,后括号外.

        算符优先法就是根据这个运算优先关系的规定来实现对表达式的编译或解释执行的.

        任何一个表达式都是由数,运算符和界限符组成的,我们称它们为单词.为了叙述的简洁,我们 仅讨论简单算术表达式的求值问题.这种表达式只含加,减,乘,除等四种运算符.读者不难将它推 广到一般的表达式上.

         我们把运算符和界限符统称为算符,它们构成的集合命名为OP.根据上述三条运算规则,在运算 的每一步中,任意两个相继出现的算符θ1和θ2之间的优先关系至多是下面三种关系之一;  θ1<θ2θ1的优先权低于θ2  θ1="θ2θ1的优先权等于θ2"  θ1>θ2θ1的优先权高于θ2

表3.1定义了算符之间的这种优先关系.

 

        为实现算符优先算法,可以使用两个工作栈.一个称作OPTR,用以寄存运算符;另一个称作POND ,用以寄存操作数或运算结果.算法的基本思想是:

  •    1)首先置操作数栈为空,表达式起始符"#"为运算栈的栈底元素;

  •    2)依此读入表达式中每个字符,若是操作数则进OPND栈,若是运算符,则和OPTR栈的栈顶 运算符比较优先权后  作相应操作,直至整个表达式求值完毕(即OPTR栈的栈顶元素和当前读入的字符均为"#").

以下算法描述了这个求值过程.

FUNC exp_reduce:operandfype;
    {算术表达式求值的算符优先算法.假定从终端输入的表达式无语法错误,以"#"作结束符.
     设OPTR和OPND分别为运算符栈和操作数栈,OP为运算符的集合}
     INISTACK(OPTR);PUSH(OPTR,'#');INISTACK(OPND);
                        {栈初始化,并在运算符栈的栈底压入表达式左端的虚设字符'#'}
     read(w);{从终端接受一个字符}
     WHILE NOT ((w='#')AND(GETTOP(OPTR)='#'))DO
      IF w NOT IN op THEN PUSH(OPND,w)
         ELSE CASE precde(GETTOP(OPTR),w)OF
                   '<':[PUSH(OPTR,w);read(w)];{栈顶元素优先权低} '=":[x:=POP(OPTR);read(w)];
                                                                    {脱括弧并接受下一字符}
                   ">':[theta:=POP(OPTR); b:=POP(OPND); a:=POP(OPND);
     PUSH(OPND,operate(a,theta,b))]{退栈并将运算结果入栈}
     ENDC;
  RETURN(GETTOP(OPND))
 ENDF;{exp_reducde}
  
   算法中还调用了两个函数。其中precede是判定运算符栈的栈顶运算符θ1与读入的运算符θ2之间
优先关系的函数;orerate为进行二元运算aθb的函数,如果是编译表达式,则产生这个运算的一组相
应指令并返回存放结果的中间变量名;如果是解释执行表达式,则直接进行该运算,并返回运算结果。
  
 例3-2 利用算法exp_reducde对算术表达式3*(7-2)求值,操作过程如下所示。

2.3.1 递归过程及其实现

        栈的另一个重要应用是在程序设计语言中实现递归过程。一个直接调用自己或通过一系列的过程语句间接地调用自己的过程,称做递归过程。

      例如, PROCEDURE A; BEGIN · · · A; · · · END; 过程A中的语句A直接调用了过程A本身,这叫做直接递归调用。又如: PROCEDURE B; PROCEDURE C; BEGIN BEGIN · · · · · · C; C; · · · · · · END; END; 在过程B中调用了过程C,而过程C又调用了过程B.这两个过程都通过另一个过程调用了它们自己, 这就叫做间接调用。

         递归是程序设计中一个强有力的工具。

  •           其一,有很多数学函数是递归定义的,如大家熟悉的阶乘函数 Fact(n)=1 若n = 1 Fact(n)= n·Fact(n-1) 若n >1 2阶Fibonacci数列 Fib(n)=0 若 n=0 Fib(n)=1 若n=1 Fib(n)=Fib(n-1)+Fib(n-2) 其它情形 和Ackerman函数 Ack(m,n)=n+1 m=0 Ack(m,n)=Ack(m-1,1) n=0 Ack(m,n)=Ack(m-1,Ack(m,n-1)) 其它情形等;

  •          其二,有的数据结构,如二叉树,广义表等,由于结构本身固有的递归特性,则它们的操作可递归地描述;

  •         其三,还有一类问题,虽则问题本身没有明显的递归结构,但用递归求解比迭代求解更简单,如八皇后问题,Hanio塔问题等.

队列

2.4.1 抽象数据类型队列的定义

        和栈相反,队列(Queue)是一种先进先出(First In First Out,缩写为FIFO)的线性表。它只允许在表的一端进行插入,而在另一端删除元素。这和我们日常生活中的排队是一致的,最早进入队列的元素 最早离开。在队列中,允许插入的一端叫做队尾(rear),允许删除的一端则称为队头(front)。

        假设队列为q=(a1,a2,...an),那么,a1是队头元素,an则是队尾元素。队列中的元素是按照a1,a2,...an的顺序进入 的,推出队列也只能按照这个次序依次推出,也就是说,只有在a1,a2,...an-1都离开队列之后,an才能 推出队列。 如图所示:

    未命名.bmp (360054 bytes)

        队列在程序设计中也经常出现。一个最典型的例子就是操作系统中的作业排对。在允许多道程序运行的计算机系统中,同时有几个作业运行。如果运行的结果都需要通过通道输出,那就要按请求输出的先后次序排队。每当通道传输完毕可以接受新的输出任务时,队头的作业先从队列中推出,作输出操作。凡是申请输出的作业都从队尾进入队列。

         队列的操作与栈的操作类似,也有七个。不同的是删除运算是在表的头部(即队头)进行。

         下面给出队列的抽象数据类型定义:

        规格说明3.2 ADT Queue

         数据元素可以是各种类型的,只要同属一个数据对象即可。

         结构数据元素之间呈线性关系。假设队列中有n个数据元素(a1,a2,...an),则对每一个元素

ai(i=1,2,...,n-1)都存在关系(ai,ai+1),并且a1无前趋,an无后继。

  操作
    INIQUEUE(Q) 初始化操作。设置一个空的队列;
    EMPTY(Q) 判空函数。若队列为空,则返回函数值"true",否则返回函数 值"false"。
    ENQUEUE(Q,X) 入队列操作。Q为已知队列,本操作的结果是,在队列的尾部 插入元素x,若队列非空,则x成为插入前的队尾元素的后继,即x为新的队尾元素。
    DLQUEUE(Q) 出队列函数。若已知队列Q不空,则删除队头元素并返回该队头 元素,且其后继为新的队头元素,否则返回空元素"NULL"。
    GETHEAD(Q) 取队头元素函数。若已知队列Q不空,则返回队头元素,否则返 回空元素"NULL"。
    CLEAR(Q) 队列置空操作,不论已知队列Q是否是空队列,本操作结果是将Q置 为空队列。
    CURRINT_SIZE(Q) 求已知队列Q当前所含元素个数。


        和栈类似,在本书以后各章中,凡引用的队列都按上述规格说明作为接口,队列的数据元素 类型在调用过程内定义,并且允许记录型的数据元素作为函数值返回。
        除了栈和队列之外,还有一种限定性数据结构是双端队列(Deque)。
       双端队列是限定插入和删除操作在表的两端进行的线性表。这两端分别称为端点1和端点2 (如图画3.11所示)。也可象栈一样,可以用一个铁道转轨网络来比喻双端队列。在实际使用中,还可以有输出 受限的双端队列(即一个端点允许插入和删除,另一个端点只允许插入的双端队列)。而如果限定双端队列从 某个端点插入的元素只能从该端点删除,则该双端队列就蜕变为两个栈底相邻接的栈了。
         尽管双端队列看起来似乎比栈和队列更灵活,但实际上在程序系统中远不及栈和队列有用 ,故在此不作详细讨论。    

                    

3.1 串的基本概念

    是零个或多个字符组成的有限序列。一般记S=‘a1a2....an ’其中,S是串名,单引号括起的字符序列是串值;ai(1〈=i〈=n)可以是字母,数字或其它字符;串中所包含的字符个数为该串的长度。长度为零的串称为空串,它不包含任何字符。             

     串中任意个连续的字符组成的子序列称为该串的子串。包含子串的相应地称为主串。通常,把子串在主串中第一次出现时,子串的第一次字符在主串中的序号,定义为子串在主串中的序号

      称两个串是相等的,当且仅当这两个串的值相等。也就是说,只有当两个串的长度相等,并且各个对应位置的字符都相等时才相等。

   值得一提的是,串值必须用一对单引号括起来,但单引号本身不属于。     

3.2 基本操作

1)ASSIGN(s,t)和CREAT(s,ss) 赋值操作。其中t为串名,ss 为字符序列。

2)EQUAL(s,t) 判等函数。若s和t相等,则返回函数值“true”,否则返回函数值“false”。

3)LENGTH(s) 求长函数。其函数值为串s中字符的个数。

4)CONCAT(s,t) 联接函数。其函数值为一个新的串。

5)SUBSTR(s,start,len) 求子串函数。返回函数值为从串s中第start个字符起,长度为len的字符序列。

6)INDEX(s,t) 定位函数。若在主串s中存在和t相等的子串,则函数值为s中第一个这样的子串在主串s中的位置,否则函数值为零。注意:在此t不能为空串。

7)REPLACE(s,t,v) 置换操作。操作结果是以串v替换所有在串s中出现的和非空串t相等的重叠的子串。

8)INSERT(s,pos,t) 插入操作。在串s中第pos个字符之前插入串t。

9)DELETE(s,pos,len) 删除操作。从串s中删去第pos个字符起长度为len的子串。

3.3 存储结构

   1. 静态存储结构

     是用一组地址连续的存储单元存储串的字符序列。可分为两种格式:非紧缩格式:数组的一个分量至少占一个字符单元(如下图一);紧缩格式:在一个字存储单元中存放多个字符(如下图二)。

           long2.bmp (64794 bytes)                

   2.  动态存储结构

   可采用链表方式存储串值,每个结点可以存放一个字符,也可以存放多个字符(如下图三)。

                               long3.bmp (131814 bytes)          

3.4 基本操作实现

这里介绍一下在串处理系统中比较重要的几个操作

1.串的链结 CONCAT(l,s,t)

            CONST  maxlen=串被确认的最大长度;

               TYPE  strtp=RECORD

                                  ch:ARRAY [1..maxlen] OF char;

                           curlen:0..maxlen

                     END

   假设l,s,t都是strtp型的串变量,且l为s联结t之后得到的串,则联结运算是将s和t的值分别传送到l的相应位置上,超过maxlen的部分截断之。其运算结果可能有三种情况(如下图a,b,c)。

相关文章

  •  ©  2006-2008 www.qq08.net 业务联系 广告刊登 QQ:517165800统计

  • 皖ICP备07000033号