线形表的复习--嘟嘟胖

什么是线形表?

答:一种数据结构,简单的说, N 个数据元素的有限序列。

线性结构的特点:

只有一个首结点和尾结点;

除首尾结点外,其他结点只有一个直接前驱和一个直接后继。

线性表顺序存储特点:

① 逻辑上相邻的数据元素,其物理上也相邻;

② 若已知表中首元素在存储器中的位置,则其他元素存放位置亦可求出(利用数组下标)。

1 :一个一维数组M,下标的范围是0到9,每个数组元素用相邻的5个字节存储。存储器按字节编址,设存储数组元素  M[ ]的第一个字节的地址是98,则M [ ] 的第一个字节的地是  ______      

  解: 地址计算通式为

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

    因此: LOC( M[3] ) = 98 + 5 × (3-0) =113

线形表的实现:

定义该数据结构:

#define LIST_INIT_SIZE  100  // 初始化分配量, 100 个元素为默认的 线性表的容量            

    #define LISTINCREMENT 10    // 线性表存储空间的分配增量

    Typedef struct {

    Elemtype *elem ;         // 定义一个指针,用于存放线性表的基址

    Int length ;             // 定义线性表的长度

    Int listsize ;}sqlist    // 当前线性表分配到的容量

 

创建一个线性表:即初始化一个线性表,构造一个空的线性表

   Status initList_sq (sqlist &L) {

    L.elem = (ElemType *) malloc (LIST_INIT_SIZE*sizeof(ElemType))

    If (!L.elem) exit(OVERFOLOW);

L.lengh = 0 ;

L.listsize = LIST_INIT_SIZE ;

Return OK }

 

顺序表的插入操作(重要,必须倒背如流)

思路:

①  插入操作应该必须知道些什么呢?

    答:插到哪个线性表,和插到该线性表的什么位置,还有插的元素,所以该插入函数有三个参数 &L , int I ,elemtype e .

②  插入操作中会有什么可能出现的错误呢?

 A: 插入的位置应该是逻辑上成立的 1 =< I <= length(L)+1]

B: 如果线性表已经装满了,应该分配新的空间

          Length>=listsize 时候说明满了

          Newbase = (elemtype *) realloc (L.elem,

( L.listsize + LISTINCREMENT ) *sizeof(elemtype));

  插入后,会对别的元素造成什么样的影响?

  如果插入的位置是 I ,那么 从 I ->length(L) 都将被移动,共需移动元素 N-I+1   很明显,应该从最右边的元素下手,依次将 I -> n 的元素右移。 P 是指向最后一个元素的指针,    *(P +1)= *P , 最后应该完成扫尾工作 ++length .

下面写出原码 :      

  Void List_insert_sq(&L , int I ,elemtype e .) {

      if (i<1 || i>length(L)+1); return Error;   // 插入的位置应该是逻辑上成立

if (L.length >= L.listsize ) {   // 如果线性表已经装满了,应该分配新的空间

Newbase = (elemtype *) realloc (L.elem,

L .listsize + LISTINCREMENT *sizeof(elemtype));

       If (!newbase = 0 )  exit(OVERFLOW)

L.elem = Newbase;

L.Listsize = .listsize + LISTINCREMENT ;  }

       Q = &(L.elem[i-1]);  // 插入的位置

        For (P = &(L.elem[L.length-1]) ; P>=Q; P--)  {

          *(P +1)= *P ; }  // 依次将 I -> n 的元素右移

          *P = e ;

          ++L.Length ;   }

                

分析上程序的时间复杂度  

最坏的情况下是: 插入的位置是 1 ,将移动 N-I+1 个元素,插入的位置的概率是 1/n+1, 所以平均算法时间复杂读是 O(N/2) ;

顺序表的删除操作(重要,必须倒背如流)

思路:

1 删除操作应该必须知道些什么呢?

答:删除哪个线性表,和删除该线性表的什么位置,返回删除元素的值,所以该插入函数有三个参数 &L , int I ,elemtype e .

2 删除操作会出现什么可能的错误?

答:删除的元素应该在线性表内   1=< I <= length

3 删除后会对周围元素造成什么样的影响?

答:删除的位置如果是 I ,那么 i+1 N 的元素都将会左移一位   * P-1 =*P

很明显,应该把从线性表的左边移动,此前把 *P 赋给 e , 最后扫尾 –Length

下面给出原码:

Status list_delete_sq (&L , int I ,elemtype e .) {

   If (i<1 || i>length(L)) then return error ;

   p = L.elem[i-1] ;

    E = *p;

    Q = L.elem + L.length-1;

    For (p; p<=q ; p++)

          * P-1 =* P ;

     ---L.length ;  }

分析上程序的时间复杂度  

最坏的情况下是: 删除的位置是 1 ,将移动 N-I 个元素,插入的位置的概率是 1/n+1, 所以平均算法时间复杂读是 O((N-1)/2) ;

 

关于 MALLOC REALLOC 的理解

首先看一下下面的 C 程序片断:

Elemtype malloc(size)    // Elemtype 指的是数据类型,比如 INT ,那就意味着一个指针指示 4 个字节

Elemtype realloc( 原指针, SIZE)      ,即一个元素是 4 个字节。

#include <malloc.h>

char  *p;

p = (char * ) malloc (10);

p = (char * ) realloc (p,20);

函数首先定义了一个字符型的指针 p ,然后为指针 p 分配了一个 10 个字节大小的内存空间,接着将这个内存块的大小增加到 20 个字节。关于 realloc 究竟在哪里分配的问题,完全由实现标准库的程序决定。它可能在原来的地方扩充,也有可能全部另行分配(想想,如果现在要扩大存储块,而后面的地方已经被分配出去另有他用了,你怎么可能在原位扩充呢?)。具体情况我们不必考虑,从抽象的层次上理解其功能就够了。

 

顺序表的定位操作:(巧妙的设计)

思路:要求查找线性表中是否有与 e 相同的元素,如果有则返回该元素的序号,否则返回 0

1. 定位过程必须知道什么?

答: 对哪个表进行定位,返回与哪个数相等的序列号。所以需要两个参数  

2.  怎么进行比较控制?

答: 须对线性表中的元素都进行比较,所以应该知道线性表的长度,用一个循环依次取出每个元素比较。找到回返回序号。否则返回 0

 Int locate_list-sq (&L,e)  {

For (I = 1 ; i<=L.length&&!(e==L.elem[i-1]);++i) {}

 If (I<=L.length) then reture I

 Else return 0 ; }

    

该算法的巧妙之处在于 巧妙的利用了 ++i,意思是在 L 查找不到相等的元素后跳出循环, I 已经先加上 1 了,所以 I>L.length, 返回的值是 0

分析上程序的时间复杂度  

最坏的情况下是:找到符合条件的元素在最后一个,所以时间复杂度是 O L.Length

线性表之间的操作:

1 线性表 LA LB ,求 A=AUB 

思路:求两线性表的并,注意集合的定义,没有相同的元素!,一个循环解决问题

    Void union(LA,LB) {

       For (I = 1, i<=LB.length;i++){

        Getelem(LB,I,e);

          If (!locateelem(LA,e));

          Listinsert(LA,++LA.length+1,e) }

2 线性表 LA LB 中元素是按非递减有序排列,求 LC=LAULB

思路:初始化一个线性表 LC ,将 LA LB 中元素比较,小的进 LC 。最后再扫尾

      Void mergelist(LA,LB,LC) {

        Init(LC);

         I=j=k=1 ;

         While (i<=la.length && j<=lb.length)

         {   Getelem(la,I,ai)  ; Getelem(la,j,ai) ;}

          If (ai<=bi)  {listinsert(LC, k++,ai) ; i++}

          Else {listinsert(LC,++k++,bi) ; J++}

       While(i<=la.length) {getelem(la,i++,ai) ;listinsert(LC, k++,ai);

       While(j<=lb.length) {getelem(lb,j++,bi) ;listinsert(LC, k++,bi);

 

线性表的链式表示和实现

链式存储特点

答: 逻辑上相邻,物理上不一定相邻

 

在单链表中,除了首元结点外,任一结点的存储位置由其直接前驱结点的链域的值指示

头结点 是在链表的首元结点之前附设的一个结点;数据域内只放空表标志和表长等信息 ;

首元结点 是指链表中存储线性表第一个数据元素 a1 的结点。

单链表是非随机访问的储存结构,它的 I 结点的存储位置包含在 i-1 个结点的信息中。

对单链表的访问操作 GETELEM

怎么访问 LINKLIST 中任一指定的结点?

答:与顺序表不同,顺序表只需要知道线性表的基址,再加上偏移量就可以得出。而链表的物理存储位置可能是不相邻的,因此我们必须从头结点开始,才可以找出某结点的存储位置。

那怎么控制呢?

答:如果访问的是第 5 个元素,那我们只需要找到 5-1=4 个元素,就可以访问它了。我们可以设置一个计数器控制循环,一直访问到 i-1 个元素。

怎么控制查询范围的错误呢?

       答:如果 i-1 个元素的指针域为空,那就说明 i-1 是链表的最后一个结点,没有 I 结点,返回错误。如果查询的是 <1 的结点,返回错误。

  

下面给出算法:

     Status Getelem(linklist L, int I, elemtype e) }

      P=L->next ; j=1;

       While ( p && j < I )  {

          P = p->next ;

++J }

        If ( !p && j > i)  returnerror ;

         E = p->data ;

         Return OK;  }

单链表的插入操作:

思路: 在顺序链表中,在 I 前插入一个元素,只需要将 N-I+1 个元素都向右移动一位首先将指针移动到表尾元素    *(p+1)=*p . 但是链表在物理上的存储空间是不相邻的,第 I 个元素的物理位置是保存在第 I-1 个元素的指针域里,所以,首先,我们必须找到第 I-1 个元素,也就是我们需要得到第 I-1 个元素的指针。

怎么能够得到第 I-1 个元素的指针呢?

答: L 是头指针,指向头结点。如果我们想得到指向 I-1 个元素的指针,我们应该将 L 移动 I - 1 次。所以应该注意,循环 I-1 次 。

如果我们想要插入的位置不在 1 <=i<=n+1 呢?

答:如果插入的位置超出表长,怎么判断超出了表长?如果一个链表有 6 个元素,那么从 1~7 的插入位置都是合理的,如果插入的位置是 7 ,那么 I-1 个元素的既 6 的指针是存放在第 5 个元素里的。所以 I-1 元素的指针不为空。那么如果刚刚超出表长一位,要求插入的位置是 8 ,那么 I-1 个元素即 7 的指针是存放在 6 里面的,而表长是 6 ,所以 6 的指针为空。   由此我们得出,看是否超出表长,只需要判断 I-1 个元素的指针是否为空。其次,插入位置当然不能小于 1

下面是源码:

    Status listinsert_link (linklist L, int I , elemtype e)  {

      P = L  ;  j = 0 ;

     While( p && j < i-1  ) {p=p->next , ++j}

     If (!P && j > i-1)  returnerror ;

     S = (linklist) malloc (sizeof(Lnode)) ;

     s->next = p-.next  ;

     p-next = s ;

     s->data = e ;

     return OK ; }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值