3. 线性表

2019-10-21  本文已影响0人  璎珞纨澜

线性表

线性表(List):由零个或多个数据元素组成的有限序列。

抽象数据类型

抽象数据类型(Abstact Data Type,ADT)是指一个数据模型及定义在该模型上的一组操作。抽象数据类型的定义仅取决于它的一组逻辑特性,而与其在计算机内部如何表示和实现无关。
抽象数据类型不仅指那些已经定义并实现的数据类型,还可以是计算机编程者自己定义的数据类型,例如,各种类。
我们为了方便后续对抽象数据类型进行规范的描述,给出以下标准格式:

线性表的抽象数据类型

  1. 线性表的创建和初始化。
  2. 线性表重置为空表。
  3. 删除线性表中的数据。
  4. 向线性表插入数据。
  5. 根据位序得到数据元素

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

示例:将集合A和集合B取并集

void unionL(List *La, List Lb)
{
  int La_len, Lb_len, i;
  ElemType e;
  La_len = ListLength(*La);
  La_len = ListLength(Lb);
  for(i = 1; i <= Lb_len; i++)
  {
    GetElem(Lb, i, &e);
    if(!LacateElem(*La, e))
    {
      ListInsert(La, ++La_len, e);
    }
  }
}

线性表的顺序存储结构

线性表有两种物理存储结构:

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

#define MAXSIZE 20
typedef int ElemType;
typedef struct
{
  ElemType data[MAXSIZE];
  int length; //线性表当前长度
} SqList;

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

注意:数组的长度和线性表的当前长度需要区分:数组的长度是存放线性表的存储空间的总长度,一般初始化后不变。而线性表的当前长度是线性表中元素的个数,是会变化的。

顺序存储结构的地址计算方法:
假设ElemType占用的是c个存储单元(字节),那么线性表中的第i+1个数据元素和第i个数据元素的存储位置的关系是(LOC表示获得存储位置的函数):LOC(a_i + 1) = LOC(a_i) + c
所以,第 i 个数据元素a_i的存储位置可以由a_1推算得出:
LOC(a_i) = LOC(a_1) + (i-1)*c
通过这个公式,我们可以随时计算出线性表中任意位置的地址,不管它是第一个还是最后一个,都是相同的时间。那么它的存储时间性能就是O(1),我们通常称为随机存储结构。

1. 线性表获取元素

要实现GetElem的具体操作,即将线性表L中的第i个位置元素值返回。就程序而言就非常简单了,我们只需要把数组的第 i-1 下标的值返回即可。

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

typedef int status
Status GetElem(SqList L, int i, ElemType *e)
{
  if( L.length == 0 || i < 1 || i > L.length)
  {
    return ERROR
  }
  *e = L.data[i-1]
}

2. 插入操作

Status ListInsert(SqList *L, int i, ElemType e)
{
  int k;
  if(L->length == MAXSIZE)
  {
    return ERROR;
  }
  if(i<1 || i>L->length+1)
  {
    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;
} 

3. 删除操作

Status ListDelete(SqList *L, int i, ElemType *e)
{
  int k = 0;
  if(L->length = 0) return ERROR;
  if(i < 1 || i > L->length)
  {
    return ERROR;
  }
  *e=L->data[i-1];
  if(i < L->length)
  {
    for(k = i; k < L->length; k++)
    {
          L->data[k-1] = L->data[k];
    }
  }
  L->length--;
  return OK;
}

现在我们来分析,插入和删除的时间复杂度:
最好的情况:插入和删除元素刚好在最后一个位置操作,因为不需要移动任何元素,所以此时的时间复杂度为O(1)
最坏的情况:如果插入或删除的位置是第一个元素,那就意味着要移动所有的元素向后或向前,所以这个时间复杂度为O(n)
至于平均情况,就取中间值O((n-1/2))
按照前面所说的简化原则其时间复杂度就是O(n)

线性表顺序存储结构的优缺点

线性表的顺序存储结构,在存、读数据时,不管是哪个位置,时间复杂度都是O(1)。而在插入或删除时,时间复杂度都是O(n)。这说明,他比较适合元素个数比较稳定,不经常插入和删除元素,而更多的操作是存取数据的应用。

线性表的顺序存储结构优缺点:
优点:

线性表的链式存储结构

线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以存在内存中未被占用的任意位置。
比起顺序存储结构的每一个数据元素只需要存储一个位置就可以了。现在链式存储结构中,除了要存储数据元素信息外,还要存储他的后继元素的存储地址(指针)。也就是说除了存储其本身的信息外,还要存储一个指示其直接后继的存储位置的信息。

空链表的头结点与头指针

单链表存储结构

我们在C语言中可以用结构指针来描述单链表。节点由存放元素的数据域和存放后继结点地址的指针域组成。

typedef struct Node
{
  ElemType data;     //数据域
  struct Node* Next; //指针域
} Node;
typedef struct Node* LinkList;

假设p是指向线性表第i个元素的指针,则该结点a_i的数据域我们可以用p->data的值是一个数据元素,节点a_i的指针可以用p->Next来表示。p->next的值是一个指针,指向的第i+1个元素。
如果p->data = a_i,那么p->next->data = a_{i+1}

单链表的读取

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

Status GetElem (LinkList L, int i, ElemType *e)
{
  int j;
  LinkList p;
  p = L->next;
  j = 1;
  while(p && j<i)
  {
    p = p->next;
    ++j;
  }
  if(!p || j>i)
  {
    return ERROR;
  }
  *e = P->data;
  return OK;
}
上一篇 下一篇

猜你喜欢

热点阅读