3. 线性表

线性表

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

  • 线性表是一个序列,元素之间有先来后到。
  • 元素存在多个,则第一个元素无前驱,而最后一个元素无后继,其他元素有且仅有一个前驱和后继。
  • 线性表强调的是有限的,无论计算机发展到多强大,它所处理的元素都是有限的。

抽象数据类型

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

  • ADT 抽象数据类型
  • Data 数据元素之间逻辑关系的定义
  • Operation 操作
  • endADT

线性表的抽象数据类型

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

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

  • ADT 线性表(List)
  • Data 线性表的数据对象集合为{a1,a2,...,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

示例:将集合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;

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

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

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

顺序存储结构的地址计算方法:
假设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. 插入操作

  • 如果插入的位置不合理,抛出异常;
  • 如果线性表长度大于等于数组长度,则抛出异常或者动态增加数组容量;
  • 从最后一个元素开始向前遍历到第 i 个位置,分别将它们都向后移动一个位置;
  • 将要插入元素填入位置 i 处;
  • 线性表长+1。
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. 删除操作

  • 如果删除的位置不合理,抛出异常;
  • 取出删除元素
  • 从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置;
  • 线性表长-1。
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)。这说明,他比较适合元素个数比较稳定,不经常插入和删除元素,而更多的操作是存取数据的应用。

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

  • 无须为表中元素之间的逻辑关系而增加额外的存储空间。
  • 可以快速的存取表中的任意位置的元素。
    缺点:
  • 插入和删除操作需要移动大量元素。
  • 当线性表长度变化较大时,难以确定存储空间的容量。
  • 容易造成存储空间的"碎片"。

线性表的链式存储结构

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

  • 我们把存储数据元素的域称为数据域,把存储直接后继元素的域称为指针域。指针域中存储的信息称为指针或链。这两部分信息组成的元素称为存储映像,称为结点(Node)。
  • n个结点链接成一个链表,即为线性表(a_1,a_2,a_3,……,a_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个数据的算法思路:

  • 声明一个结点 p 指向链表第一个结点,初始化 j 从1开始;
  • 当 j<i 时,就遍历链表,让 p 的指针向后移动,不断指向下一个结点,j+1;
  • 若到链表末尾 p 为空,则说明第 i 个元素不存在。
  • 若查找成功,返回结点 p 的数据。
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;
}
  • 由于这个算法的时间复杂度取决于 i 的位置,当 i=1 时,则不需要遍历,而 i=n 时则遍历 n-1 次才可以。因此最坏情况的时间复杂度为 O(n)。
  • 由于单链表的结构没有定义表长,所以不能实现知道要循环多少次,因此也就不方便使用for来控制循环。
  • 其核心思想叫做“工作指针后移”,这其实也是很多算法的常用技术。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容

  • 3.2 线性表的定义 线性表,从名字上你就能感觉到,是具有像线一样的性质的表。 零个或多个数据元素的有限序列。 这...
    努力生活的西鱼阅读 918评论 0 1
  • 3.7 单链表的读取 在线性表的顺序存储结构中,我们要计算任意一个元素的存储位置是很容易的。但在单链表中,由于第i...
    努力生活的西鱼阅读 357评论 0 0
  • 转自:http://blog.csdn.net/oreo_go/article/details/52116214 ...
    YYT1992阅读 965评论 0 4
  • 1.线性表的定义 线性表:零个或多个数据元素的有限序列序列:也就是说元素之间是有顺序的,若元素存在多个,则第一个元...
    e40c669177be阅读 2,035评论 6 15
  • 今天又完成了两件事情,活动月闭幕了,采购参数上报完了,明天还要带学生去彩排,又一次学习的机会,每天的能量朗读,每天...
    54ae05126891阅读 243评论 0 1