第三章——线性表( 1 )

(  以下内容全部来自《大话数据结构》  )


线性表:

零个或多个数据元素的有限序列。

     若将线性表记为 (a1,...,ai-1,ai,ai+1,...,an ),则表中 ai-1 领先于 ai,ai  领先于  ai+1, 称 ai-1 是 ai 的直接前驱元素, ai+1  是 ai  的直接后区元素。当  i= 1, 2, 3, ... , n-1 时, ai  有且仅有一个直接后继,当   i= 2, 3, ... , n 时,ai  有且仅有一个直接前驱。


线性表元素的个数 n(n>=0) 定义为线性表的长度,当 n=0 时,称谓空表

线性表的抽象数据模型

线性表的抽象数据类型定义如下:

ADT    线性表    (List)

Data

    线性表的数据对象集合为 { a1a2 ,...,an  },每个元素的类型均为 DataType。 其中,除了第一个元素  a1   外有且只有一个直接前驱元

素,除了随后一个元素  an  外,每一个元素有且只有一个直接后继元素。数据元素之间的关系是一对一的关系。

Operation

    InitList ( *L ) :    初始化操作,建立一个空的线性表 L 。

    ListEmpty( L ) :    若线性表为空,返回 true, 否则返回 false 。

    ClearList ( *L ):    将线性表清空。

    GetElem ( L, i, *e ):    将线性表 L 中的 第  i 个位置元素值返回给 e 。

    LocateElem ( L, e ):    在线性表 L 中查找与给定值 e 相等的元素,如果查找成功,返回该元素在表中序号表示成功;否则,返回 0 标示失败。

    ListInsert ( *L, i, e ):    在线性表  L 中的第 i  个位置插入新元素 e 。

    ListDelete ( *L, i, *e ):    删除线性表 L 中第 i  个位置元素, 并用  e 返回其值。

    ListLength ( L ):    返回线性表 L 的元素个数

endADT

合并两个集合

/*  将所有的在线性表 Lb 中但不在La 中的数据元素插入到 La 中  */

void union ( List  *La,   List  Lb   )

{

        int  La_Len,  Lb_len,  i ;

        ElemType  e;                                 /* 声明 La 和 Lb 相同的数据元素 e   */

        La_len = ListLength ( La );            /*  求线性表的长度  */

        Lb_len = ListLength ( Lb );

        for(  i = 1 ;  i <= Lb_len ; i++    )

        {

                GetElem (  Lb,  i,  e  );            /*  取  Lb 中第 i 个数据元素赋给 e  */

                if  ( !LocateElem ( La, e, equal )  )            /*  La 中不存在和 e 相同的数据元素  */

                {   

                        ListInsert (  La,  ++ La_len,  e  );       /*  插入  */

                }

        }

}


线性表的顺序存储结构

顺序存储定义

线性表的顺序存储结构, 指的是用一段地址连续的存储单元依次存储线性表的数据元素。

顺序存储方式(用的是一维数组来实现顺序存储结构)

线性表的顺序存储的结构代码。

#define MAXSIZE 20                    /*  存储空间初始分配量   */

typedef  int  ElemType ;                   /*   ElemType 类型根据实际情况而定  */

typedef  struct 

{

        ElemType  data [ MAXSIZE ]                    /*  数组存储数据元素,最大值为 MAXSIZE  */;

        int  length ;                                               /*  线性表当前长度  */

}sqlList ;

顺序存储结构需要三个属性:

  • 存储空间的起始位置:数组date,它的存储位置就是存储空间的存储位置。
  • 线性表的最大存储变量:数组长度 MaxSize 。
  • 线性表的当前长度:length。

数据长度与线性表长度的区别:

在任意时刻,线性表的长度应该小于等于数组的长度。


地址计算方法

存储器中的每个存储单元都有自己的编号,这个编号称谓地址。

(LOC 表示获得存储位置的函数)

一个元素占有 c 个存储单元。

对于第 i 个数据元素    的存储位置可以由   推算得出:

                LOC( ai ) = LOC(a1)+ (i - 1)* c

顺序存储结构的插入与删除

获得元素的操作

代码:

#define  OK  1

#define  ERROR  0

#define  TRUE 1

#define  FALSE  0

#typedef  int  Status ;

/*  Status 是函数的类型,其值是函数结果状态代码, 如 OK 等  */

/*  初始条件:顺序线性表 L 已经存在, */

/*  操作结果:用 e 返回 L 中第 i 个数据元素的值  */

Status  GetElem  (  SqlList  L ,  int i , ElemType  *e   )

{

        if  (  L.length ==0  ||  i < 1 ||  i > L.length  )

        {

                return  ERROR;

        }

        *e = L.data[ i-1 ] ;

        return OK;

}

插入操作

插入算法的思路:

  • 如果插入位置不合理,抛出异常;
  • 如果线性长度大于数组长度,则抛出异常或动态增加容量
  • 从最后一个元素开始向前遍历到第 i  个位置, 分别将他们都向后移动一个位置
  • 将要插入元素填入位置 i 处;
  • 表长加  1;

/*  初始条件:顺序线性表 L 已存在, 1<=i<=ListLength( L )   */

/*  操作结果:在 L 中第 i 个位置  */

Status  ListInsert ( SqList  *L, int i, ElemType e  )

{

        int  k ;

        if( L->length == MAXSIZE )                    /*   顺序线性表已经满了   */   

        {

                return ERROR;

        }

        if ( i<1 || i>-length+1 )                    /*  当 i  不在范围内时   */ 

        {

                return  ERROR;

        }

        if  ( i <= L->length )                    /*  若插入数据位置不在表尾  */ 

        {

                for (  k=L->length-1 ;  k>=i-1  ;  k--  )                        /*  将要插入的数据元素向后移动一位  */ 

                {

                        L->data[k+1] = L->data[k];

                }

        }

        L->data[i-1] = e;                                /*  将新元素插入  */ 

        L->length++;

        return OK;

}


删除操作:

删除算法的思路:

  • 如果删除位置不合理,抛出异常
  • 取出删除元素
  • 从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置
  • 表长减  1 ;

实现代码

/* 初始条件:顺序线性表 L 已经存在, 1 <= i <= ListLength (L)   */

/* 操作结果:删除 L 的第 i 个数据元素,并用 e 返回其值,L 的长度减 1     */

Status  ListDelete  ( SqlList  *L,  int  i  ,  ElempType  *e   )

{

        int   k ;

        if (L->length==0)                         /*  线性表为空 */

        {

                return  ERROR;

        }

        if  (  i < 1   ||   i>L-length )             /*   删除位置不正确   */

        {

                return ERROR;

        }

        *e = L->data[i-1];

        if ( i length )            /*   如果删除不是最后位置    */

        {

                for ( k = i ; k < L->length ; k++  )            /*   将删除位置后继元素后移   */

                {

                        L->data[k-1] = L-data[k];

                }

        }

        L->length--;

        return OK;

}

线性列表的优点和缺点

优点

缺点

1.     无须为表示表中元素之间的逻辑关系而增加额外的存储空间。

2.     可以快速的存取表中任一位置的元素。

1.     插入和删除操作需要移动大量元素。

2.     当线性表长度变化较大时,难以确定存储空间的容量。

3.     造成存储空间的碎片



线性表的链式存储结构。

   为了表示每个数据元素  ai  与其直接后继数据元素 ai+1  之间的逻辑关系,对数据元素 ai   来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。我们把存储数据元素信息的域成为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称作指针或链。这两部分信息组成数据元素  ai   的存储映像,称为结点(Node)。

  n 个结点(ai  的存储影像)链结成一个链表,即为线性表(a1 ,a2 ,...,an )的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫单链表。

 把链表中的第一个结点的存储位置叫做头指针。

在单链表的第一个结点前辐射一个结点,称为头结点。(头结点的数据域可以不存储任何信息,也可以存储如线性表的长度等附加信息,头结点的指针域存储指向第一个结点的指针)。

头指针与头结点的异同

头指针

头结点

1.      头指针是指链表指向第一个几点的指针,若链表有头结点,则是指向头结点的指针。

2.      头指针具有标示作用,所以常用头指针冠以链表的名字

3.      无论链表是否为空,头指针均不为空。头指针是链表的必要元素。

1.      头结点是为了操作的统一和方便而设立的,放在第一个元素的结点之前,其数据域一般无意义(也可以存放链表的长度)。

2.      有了头结点,对在第一元素借点钱插入结点和删除第一结点,其操作与其他结点的操作就统一了。

3.      头结点不一定是链表必须要素。

线性表链式存储结构代码描述

单链表中我们可用结构指针来描述

/*  线性表的单链表存储结构 */

typedef  struct  Node 

{

        ElemType  data  ;

        struct  Node  *next ;

} Node;

typedef  struct  Node  *LinkList ;  /*  定义 LinkList  */

结点有存放数据元素的数据域存放后继结点地址的指针域组成 。

单链表的读取:

获取链表第 i 个数据的算法思路:

  1. 声明一个结点 P 指向链表的第一个结点,初始化 j 从 1 开始;
  2. 当 j < i 时,就遍历链表,让 P 的指针向后移动,不断指向下一节点, j 累加 1 ;
  3. 若到链表结尾 P 为空,则说明第 i 个元素不存在;
  4. 否则查找成功,返回结点 P 的数据;

/*  初始条件:顺序链表 L 已经存在, 1 <= i <= ListLength( L )  */

/*  操作结果:用 e 返回 L 中第 i 个数据元素的值    */

Status  GetElem  ( LinkList  L,  int  i , ElemType  *e )

{

        int  j ;

        LinkList  p;                                            /*   声明一个结点 P    */

        p = L->next ;                                           /*   让 p 指向链表 L 的第一个结点   */

        j = 1 ;                                                       /*   j 为计数器   */

        while( p && j                                           /*   p 不为空或者计数器 j 还没有等于 i 时 ,循环继续   */

        {

                p = p->next ;                                           /*   让 p 指向下一个结点   */

                ++j ;

        }

        if(  !p ||  j > i  )

        {

                return  ERROR;                                           /*   第 i 个元素不存在   */

        }

        *e = p->data;                                           /*   取出来第 i 个元素的数据   */

        return  OK;

}

    (主要核心思想就是“工作指针的后移”;)


单链表的插入与删除

单链表的插入思路

  1. 声明一个结点 p 指向链表第一个结点,初始化 j 从 1 开始;
  2. 当 j < i  时, 就遍历链表,让 p 的指针向后移动,不断指向下一结点,j 累加 1;
  3. 若到链表末尾 p 为空,则说明第 i 个元素不存在;
  4. 否则查找成功,在系统中生成一个空结点 s ;
  5. 将数据元素 e 赋值给 s->data ;
  6. 单链表的插入标准语句 s->next = p->next ; p->next = s;
  7. 返回成功;

/*  初始条件:顺序线性表 L 已经存在, i <= i <= ListLength( L )   */

/*  操作结果:在 L 中第 i 个位置之前插入新的数据元素 e,  L 的长度加 1    */

Status  ListInsert (  LinkList  *L  , int  i,  ElemType  e   )

{

        int  j  ;

        LinkList  p,   s  ;

        p = *   L  ;

        j  =  1  ;

        while (  p  &&  j < i   )                /*  寻找第 i 个结点 */

        {

                p  =  p->next;

                ++j;        

        }

        if  (  !p  ||   j > i )

        {

                return   ERROR;                        /*  第 i 个元素不存在  */

        }

        s = (  LinkList  )  malloc (  sizeof(  Node )  );       /*  生成新节点(C标准函数) */

        s->data = e ;

        s->next = p->next ;             /*  将 p 的后继结点赋值给 s 的后继  */

        p->next = s ;                        /*  将 s 赋值给  p 的后继  */

        return  OK ;

}

单链表的删除思路

  1. 声明一结点 p 指向链表的第一个结点,初始化 j 从 1 开始;
  2. 当 j < i 时,就遍历链表,让 p 的指针向后移动,不断指向下一个结点, j 累加 1 ;
  3. 若到链表末尾 p 为空, 则说明第 i 个元素不存在;
  4. 否则查找成功,将欲删除的结点 p->next 赋值给 q ;
  5. 单链表的删除标准语句 p->next = q->next ;
  6. 将 q 结点中的数据赋值给 e , 作为返回;
  7. 释放 q 结点;
  8. 返回成功;

实现代码算法如下:

/*  初始条件:顺序线性表L已存在, 1 <= i <= ListLength(  L  )  */

/*  操作结果:删除 L 的第 i 个数据元素, 并用 e 返回其值,L 的长度减 1   */

Status  ListDelete  ( LinkList  *L , int  i,  ElemType  *e  )

{

        int   j  ;

        LinkList  p , q  ;

        p = *L  ;

        j  =  1  ;

        while  (   p->next   && j < i   )     /*  遍历寻找第 i 个元素    */

        {

                p = p->next  ;

                ++ j ;

        }

        if  (  !( p->next )    ||    j > i  )

        {

                return   ERROR  ;         /*  第 i 个元素不存在    */

        }

        q = p->next  ;

        p->next = q->next ;        /*  将 q 的后继赋值给 p 的后继    */

        * q = q->data ;                /*  将 q 结点中的数据给 e     */

        free ( q ) ;                        /*   让系统回收此结点,释放内存   */

        return  OK  ;

}

对于插入或者删除数据越频繁的操作,单链表的效率优势就越是明显。

单链表的整表创建的算法思路:

  1. 声明一个结点 p 和计数器变量 i ;
  2. 初始化一空链表 L ;
  3. 让 L 的头结点的指针指向 NULL,即建立一个带头结点的单链表;
  4. 循环:

  • 生成一个新结点 p 和计数器变量 i ;
  • 随机生成一个数组赋值给 p 的数据域 p->data ;
  • 将 p 插入到头结点与前一新结点之间;                

实现代码:

/*  随机产生 n 个元素的值,建立带表头结点的单链线性表 L ( 头插法 )  */

void  CreateListHead  (   LinkList   *L ,  int  n  )

{

        LinkList p ;

        int  i ; 

        srand  (  tiime (0) );                             /*  初始化随机数种子  */

        *L = ( LinkList ) malloc (  sizeof(Node) ); 

        (*L)->next j= NULL ;                         /*   先建立一个带头结点的单链表 */

        for (  i = 0; i

        {

                p = ( LinkList ) malloc ( sizeof( Node ) ) ;    /*  生成新节点  */

                p->data = rand() % 100 + 1 ;                      /*  随机生成 100 以内的数字  */

                p->next = (*L)->next ;

                (*L) ->next = p ;                                           /*  插入到表头   */

        }  

}


/*  随机生成 n 个元素的值,建立带表头结点的单链表 L(尾插法)  */
{
        LinkList   p,  r ;
        int  i  ;
        srand  (  time(0)  );                                                  /*   初始化随机数种子  */
        *L = ( LinkList ) malloc (  sizeof( Node )  );               /*   为整个线性表   */
        r = *L ;                                                                     /*   r 为指向尾部的结点   */
        for  (  i = 0 ; i < n ; i++   )
        {
                p = ( Node *  ) malloc (  sizeof(Node)  );           /*  生成新结点   */
                p->data = rand() % 100 + 1 ;                            /*   随机生成 100 以内的数字    */
                r->next = p ;                                                      /*  将表尾终端结点的指针指向新结点    */
                r = p;                                                                 /*  将当前的新结点定义为表尾终端结点     */
        }
        r-next = NULL;                                                           /*   表示当前链表结束    */
}
单链表的整表删除 
删除思路:

  1. 声明一个结点 p 和 q ;
  2. 将第一个结点赋值给 p;
  3. 循环;
  • 将下一结点赋值给q ;
  • 释放 p ;
  • 将 q 赋值给 p 。

实现算法:

/*   初始条件 : 顺序线性表 L 已存在,操作结果 : 将 L 重置为空表   */

Status  ClearList (  LinkList  *L  )

{

        LinkList   p,   q  ;

        p = ( *L )->next;

        while ( p )

        {

                q = p -> next ;

                free ( p ) ;

                p = q ;

        }

        ( *L )->next = NULL   ;

        return   OK;

}





































































    

你可能感兴趣的:(数据结构)