数据结构(2)线性表之顺序表

来源:互联网 发布:好用的隔离霜 知乎 编辑:程序博客网 时间:2024/06/09 15:34

  • 导言
  • 抽象数据类型线性表定义如下
    • 线性表的顺序表示和实现
      • 顺序表特点
        • 线性表顺序存储结构示意图
      • 线性表的动态分配顺序存储结构
      • 顺序表的插入和删除
        • 插入
          • 插入伪算法
        • 算法动态演示
        • 删除
        • 删除算法的动态演示
        • 插入和删除算法时间复杂度
          • 插入算法的平均期望
          • 删除算法的平均期望
    • 下一次文章是此文章概念伪代码的具体内容的实现

导言

首先说一下线性表是最基本、最简单、也是最常用的一种数据结构。
那么特点有哪些呢?
线性表一共有四大特点:

  1. 存在唯一的一个被称为”第一个“的数据元素
  2. 存在唯一的一个被称为”最后一个”的数据元素
  3. 除了第一个之外,集合中的每个数据元素均只有一前驱
  4. 除了最后一个之外,集合中每个数据元素均只有一个

简而言之,一个线性表是n个数据元素的有限序列。

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

 ADT List{        数据对象:D={ai|ai∈Elemset,i=1,2,…,n,n≥0}        数据关系:R1={<ai-1,ai>|ai-1,ai∈D,i=2,…,n}        基本操作:        InitList(&l)         操作结果:构造一个空的线性表L        DestroyList(&l)         初始条件:线性表已存在         操作结果:销毁线性表L        ClearList(&l)         初始条件:线性表已存在         操作结果:置线性表L为空表        ListEmpty(L)         初始条件:线性表已存在         操作结果:若线性表L为空表,则返回TRUE,否则返回FALSE        ListLenght(L)         初始条件:线性表已存在         操作结果:返回线性表L数据元素个数        GetElem(L,i,&e)         初始条件:线性表已存在(1i≤ListLenght(L))         操作结果:用e返回线性表L中第i个数据元素的值        locatElem(L,e,comare())         初始条件:线性表已存在,comare()是数据元素判定函数         操作结果:返回线性表L中第1个与e满足关系comare()的数据元素的位序        PriorElem(L,cur_e,&pre_e)         初始条件:线性表已存在         操作结果:若cur_e是线性表L的数据元素,且不是第一个,则用pre_e返回它的前驱,否则操作失败,pre_e无定义        NextElem(L,cur_e,&)         初始条件:线性表已存在         操作结果:若cur_e是线性表L的数据元素,且不是第最后一个,则用next_e返回它的后继,否则操作失败,next_e无定义        ListInsert(&L,i,e)         初始条件:线性表已存在(1i≤ListLenght(L)+1)         操作结果:在线性表L中第i个数据元素之前插入新元素e,L长度加1        ListDelete(&L,i,&e)         初始条件:线性表已存在(1i≤ListLenght(L))         操作结果:删除线性表L中第i个数据元素,用e返回其值,L长度减1        ListTraverse(L,visit())         初始条件:线性表已存在         操作结果:依次对线性表L的每个数据元素调用visit()函数,一旦visit()失败,则操作失败}ADT List

线性表中的元素的个数n(n0 )定义为线性表的长度,n=0时称为空表。
上面看得是不是很枯燥啊,没关系,其实从抽象数据类型线性表的定义,我们可以看出线性表最基本要能进行数据元素的访问,插入新的数据元素和删除元素,可以根据需要对线性表进行增加或缩短。

这是线性表的样子,大家可以看看
这里写图片描述

线性表的顺序表示和实现

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

假设线性表的每个元素需占用l个存储单元,并以所占的第一个单元的存储地址作为数据元素的存储位置。则线性表中第i+1个数据元素的存储位置LOC(ai+1)和第i个数据元素存储位置LOC(ai)之间满足下列关系:

LOC(ai+1)=LOC(ai)+l

一般来说,线性表的第i个数据元素ai的存储位置为
LOC(ai)=LOC(a1)+(i1)×l

这个式子LOC(a1)是线性表的第一个数据元素a1的存储位置,通常称为线性表的起始位置或基位置。

线性表的这种机内表示称为线性表的顺序存储结构或顺序映像,通常,称这种存储结构的线性表为顺序表

顺序表特点

  1. 表中相邻元素aiai+1被赋以相邻的存储位置LOC(ai)LOC(ai+1)

    每一个数据元素的存储位置都和线性表的起始位置相差一个和数据元素在线性表中的位序成正比的常数。

  2. 只要确定存储线性表的起始位置,线性表中任一数据元素都可以随机存取

    线性表的顺序结构是一种随机存取的存储结构

线性表顺序存储结构示意图

这里写图片描述

通常用数组来描述数据结构中的顺序存储结构

线性表的动态分配顺序存储结构

线性表的长度可变,而且所需最大存储空间随问题不同而不同,则C语言可用动态分配的一维数组,如下描述:

//------线性表的动态分配顺序存储结构--------------#define LIST_INIT_SIZE  100 //线性表存储空间的初始分配量#define LISTNCREMENT    10  //线性表存储空间的分配增量typedef struct{        ElemType  *elem;//分配空间基址        int       length;//当前长度        int       listsize;//当前分配的存储容量(以sizeof(ElemType)为单位)}

上诉定义中,数组指针elem指示线性表的基地址,length指示线性表的当前长度。顺序表的初始化操作就是为顺序表分配一个预定义大小的数组空间,并将线性表的当前长度为“0”。listsize指示顺序表当前分配的存储空间大小,一旦因为插入元素而空间不足时,可进行再分配,即为顺序表增加一个大小为存储LISTINCREMENT个数据元素的空间。

Status InitList_Sq(SqList &L){//构造一个空的线性表L。    L.elem = (ElemType *)malloc(LIST_INIT_SIZE * sizeof(ElemType));    if(!L.elem)exit()OVERFLOW;//存储分配失败    L.length=0;//空表长度为0    L.listsize = LIST_INIT_SIZE;//初始存储容量    return OK;}//InitList——Sq

顺序表的插入和删除

插入

插入操作是指在线性表的第i1个数据元素和第i个元素之间插入一个新的数据元素,就是要使长度为n的线性表

(a1,...,ai1,ai,...,an)

变成长度为n+1的线性表
(a1,...,ai1,b,ai,...,an)

数据元素ai1ai之间的逻辑位置发生了变化。

线性表的顺序存储结构中,由于逻辑上相邻的数据元素在物理位置上也是相邻的,因此,除非i=n+1,否则必须移动元素才能反映这个逻辑关系变化。

通俗理解,想想一批人正在按顺序排队看病,这时候有一个人员比大部分人要严重些,需要插队,那么,是不是他要在的那个地方以后的成员都往后移动一个可以容纳人的空间,才能让他加入队伍中,插入顺序表就是这个意思。

插入伪算法

一般情况下,第i(1nn)个元素之前插入一个元素时,需要将第n个至第i(共ni+1)个元素向后移动一个位置。

Status ListInsert_Sq(SqList &L,int i,ElemType e){//在顺序线性表L中第i个位置之前插入新的元素e,//i的合法值在1和ListLength_sq(L)+1    if(i< 1 || i > L.length + 1) return ERROR;//i值不合法    if(L.length >= L.listsize){    //存储空间已满,增加分配         newbase = (ElemType *)realloc(L.elem,  (L.listsize+LISTNCREMENT)*sizeof(ElemType));     if(!newbase)exit(OVERFLOW);//存储分配失败     L.elem = newbase;//新基址     L.listsize+=LISTINCREMENT;//增加存储容量    }    q = &(L.elem[i-1]);    for(p= &(L.elem[length-1]);p>=q;--p)*(p+1)=*p;        //插入位置及之后的元素往后移动(若从直线看是从右移动)    *q=e;//插入e    ++L.length;    return ok;}//InitList——Sq

算法动态演示

这里写图片描述

删除

删除操作是使长度为n的线性表

(a1,...,ai1,ai,ai+1...,an)

变成长度为n1的线性表
(a1,...,ai1,ai+1,...,an)

数据元素ai1aiai+1之间的逻辑位置发生了变化。

通俗理解,想想一批人正在按顺序排队看病,这时候有一个人突然觉得肚子不疼了,不想看病了,就离开了队伍,那么,是不是他要在的那个地方以后的成员是不是肯定会愿意都往前移动一步,删除顺序表就是这个意思。

Status ListDelete_Sq(SqList &L, int i, ElemType &e) {  // 算法2.5  // 在顺序线性表L中删除第i个元素,并用e返回其值。  // i的合法值为1≤i≤ListLength_Sq(L)。  ElemType *p, *q;  if (i<1 || i>L.length) return ERROR;  // i值不合法  p = &(L.elem[i-1]);                   // p为被删除元素的位置  e = *p;                               // 被删除元素的值赋给e  q = L.elem+L.length-1;                // 表尾元素的位置  for (++p; p<=q; ++p) *(p-1) = *p;     // 被删除元素之后的元素左移  --L.length;                           // 表长减1  return OK;} // ListDelete_Sq

删除算法的动态演示

这里写图片描述

插入和删除算法时间复杂度

插入算法的平均期望

假设pi是在第i个元素之前插入一个元素的概率,则在长度为n的线性表中插入时,所需移动元素次数的期望值(平均次数)为

Eis=i=1n+1pi(ni+1)

插入的位置是第i(0in)元素之前,则此元素原下标应该为i1,插入后的下标为i,而原表中一共为n个元素,从下标0开始到下标i2都是没有移动的,而这些元素的个数为n2+1个(因为下标为0开始,所以元素的个数就等于末元素下标+1),也就是i1个,而移动的个数就是n(i1)个,也就是ni+1个元素被移动。

每个元素被选择的概率是1n+1。这是因为最后一共是有n+1个位置,而插入元素可以选择的位置就会有n+1个可能,每一个位置的可能性都是相同的。故而每个元素被选择的概率是1n+1

删除算法的平均期望

假设qi是删除第i个元素的概率,则在长度为n的线性表中删除一个元素时所需移动元素次数的期望值(平均次数)为

Edl=i=1nqi(ni)

删除第i(0in)个元素,也就是删除下标为i1的元素,此时,线性表的元素个数为n1,从第i+1个元素到第n个元素就会往前移动一位,从从下标0开始到下标i2都是没有移动的,而这些元素的个数为n2+1个,也就是i1个,而移动的个数就是(n1)(i1)个,也就是ni个元素被移动。

n个元素被选择删除的概率是相同,故而是1n

为了不失一般性,我们假定在线性表的任何位置上插入或删除元素都是等概率的,即

pi=1n+1,qi=1n

则上面的两个式子可以化为:

Eis=1n+1i=1n+1(ni+1)=2n

Edl=1nnin(ni)=n12

由此可见顺序存储结构的线性表中插入或删除一个数据元素,平均约移动表中一半的元素,若表长为n,则ListInsert_Sq和ListDelete_Sq的时间复杂度为O(n)

**

下一次文章是此文章概念伪代码的具体内容的实现~

**

3 0