第二章 线性表

线性表 


一、线性表的逻辑结构

1 线性表的定义

1线性表:简称表,是nn≥0)个具有相同类型的数据元素的有限序列。

2线性表的长度:线性表中数据元素的个数。

3空表:长度等于零的线性表,记为:L=( )

4非空表记为:L=(a1,a2 , …,ai-1, ai ,…, an)。其中,ai1≤in)称为数据元素;下角标i表示该元素在线性表中的位置或序号。

2线性表的特性

1. 有限性:线性表中数据元素的个数是有穷的。

2. 相同性:线性表中数据元素的类型是同一的。

3. 顺序性:线性表中相邻的数据元素ai-1ai之间存在序偶关系(ai-1,ai),即ai-1ai的前驱,aiai-1的后继;a1无前驱,an无后继,其它每个元素有且仅有一个前驱和一个后继。

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

抽象数据类型定义为:

ADTList

Data

线性表中的数据元素具有相同类型,相邻元素具有前驱和后继关系

Operation

InitList

    前置条件:表不存在

    输入:无

    功能:表的初始化

    输出:无

    后置条件:建一个空表

DestroyList

    前置条件:表已存在

    输入:无

    功能:销毁表

    输出:无

    后置条件:释放表所占用的存储空间

Length

    前置条件:表已存在

     输入:无

     功能:求表的长度

     输出:表中数据元素的个数                                             

     后置条件:表不变

Get

    前置条件:表已存在

    输入:元素的序号i

    功能:在表中取序号为i的数据元素

    输出:若i合法,返回序号为i的元素值,否则抛出异常

    后置条件:表不变

Locate

    前置条件:表已存在

    输入:数据元素x

    功能:在线性表中查找值等于x的元素

    输出:若查找成功,返回x在表中的序号,否则返回0

    后置条件:表不变

Insert

      前置条件:表已存在

      输入:插入i;待插x

      功能:在表的第i个位置处插入一个新元素x

      输出:若插入不成功,抛出异常

    后置条件:若插入成功,表中增加一个新元素

Delete

      前置条件:表已存在

      输入:删除位置i

      功能:删除表中的第i个元素

    输出:若删除成功,返回被删元素,否则抛出异常

    后置条件:若删除成功,表中减少一个元素

Empty

    前置条件:表已存在

    输入:无

    功能:判断表是否为空

    输出:若是空表,返回1,否则返回0

后置条件:表不变

PrintList

    前置条件:表已存在

    输入:无

功能:遍历操作,按序号依次输出线性表中的元素

输出:线性表的各个数据元素

后置条件:线性表不变

endADT

说明:

线性表的基本操作根据实际应用是而定;

复杂的操作可以通过基本操作的组合来实现;

对不同的应用,操作的接口可能不同。

三、 线性表的顺序存储结构及实现

1 线性表的顺序存储结构——顺序表

存储要点:用一段地址连续的存储单元,

依次存储线性表中的数据元素

如何描述顺序表?

存储空间的起始位置、顺序表的容量(最大长度)、顺序表的当前长度

存储结构是数据及其逻辑结构在计算机中的表示;

存取结构是在一个数据结构上对查找操作的时间性能的一种描述。

顺序表是一种随机存取的存储结构的含义为:在顺序表这种存储结构上进行的查找操作,其时间性能为O(1)

2)顺序表的实现

顺序表类的声明:

constintMaxSize=100; 

template<classDataType>                      //模板类

classSeqList

{

 public: 

    SeqList() ;                              //构造函数

    SeqList(DataTypea[ ], int n);      

    ~SeqList();                            //析构函数

    intLength( );

    DataTypeGet(int i);

    intLocate(DataType x );

    voidInsert(int i, DataType x); 

    DataTypeDelete(int i);       

 private:

    DataTypedata[MaxSize];

    intlength;

};

(1)  构造函数

无参构造函数SeqList( )

SeqList<DataType>::SeqList()

   length= 0;

}

有参构造函数SeqList(DataType a[ ], int n)

template<classDataType> 

SeqList<DataType>::SeqList(DataTypea[], int n)

      if(n > MaxSize) throw "参数非法";

      for(i = 0; i < n; i+ +) 

            data[i]= a[i];

      length= n;

 }

2)插入操作

算法描述——伪代码

1. 如果表满了,则抛出上溢异常;

2. 如果元素的插入位置不合理,则抛出位置异常;

3. 将最后一个元素至第i个元素分别向后移动一个位置;

4. 将元素x填入位置i处;

5. 表长加1

算法描述——C++描述

template<classDataType> 

voidSeqList<DataType>::Insert(inti,DataType x)

{

     if(length >= MaxSize) throw "上溢";

     if(i < 1 || i > length + 1) throw"位置";

     for(j = length; j >= i; j--)

          data[j]= data[j-1];

     data[i-1]= x;

     length++;

}

时间性能分析:

最好情况( i=n+1):

        基本语句执行0次,时间复杂度为O(1)

最坏情况( i=1):

        基本语句执行n+1次,时间复杂度为O(n)

平均情况(1≤in+1):时间复杂度为O(n)

3          删除操作

intSeqList::Delete(inti)

{

     if(length==0) throw "下溢";

     if(i<1||i>length) throw "位置非法";

     intx=data[i-1];

     for(int j=i;j<length;j++)

          data[j-1]=data[j];

     length--;

     returnx;

}

4)查找操作

按位查找:

template<classDataType> 

DataTypeSeqList<DataType>::Get(int i )

{

    if(i >= 1 && i <= length)  throw “查找位置非法”;

   elsereturn data [i-1];

}

按值查找:

template<classDataType> 

intSeqList<DataType>::Locate(DataTypex)

{

    for(i = 0; i < length; i++)

        if(data[i] == x) return i + 1;

    return0;         

}

4          遍历操作:

voidSeqList::PrintList()

{

     for(int i=0;i<length;i++)

          cout<<data[i]<<"";

     cout<<endl;

}

四、        线性表的链接存储结构及实现

1     单链表

存储特点

1.逻辑次序和物理次序不一定相同。

2.元素之间的逻辑关系用指针表示。

单链表是由若干结点构成的;单链表的结点只有一个指针域。

结点结构

data:存储数据元素

next:存储指向后继结点的地址

template<classDataType>

structNode

{

   DataTypedata;

    Node<DataType>*next;

};

申请结点s=newNode <int> ;

引用数据元素s->data;

引用指针域s->next;

头指针:指向第一个结点的地址。

尾标志:终端结点的指针域为空。

空表first=NULL;

非空表:

头结点:在单链表的第一个元素结点之前附设一个类型相同的结点,以便空表和非空表处理统一。

单链表类的声明:

template<classDataType>

classLinkList

{  

public:

    LinkList()

    LinkList(DataTypea[ ], int n);

    ~LinkList();

    intLength( );         

    DataTypeGet(inti);          

    intLocate(DataType x);      

    voidInsert(int i, DataType x);  

    DataTypeDelete(int i);       

    voidPrintList( );          

private:

    Node<DataType>*first;

};

1)构造函数:

无参构造函数:

template<classDT>

LinkList<DT>::LinkList()

{

  first=newNode<DT>;

  first->next=NULL;

}

有参构造函数:

尾插法

template<classDT>

LinkList<DT>::LinkList(DTa[],intn)

{

  Node<DT> *r,*s;

  first=newNode<DT>;

  r=first;

  for(int i=0;i<n;i++)

  {

       s=newNode<DT>;

       s->data=a[i];

       r->next=s;

       r=s;

  }

  r->next=NULL;

}

头插法

template<classDT>

LinkList<DT>::LinkList(DTa[],intn)

{

  first=newNode<DT>;

  first->next=NULL;

  for( i=0;i<n;i++)

  {

       s=newNode<DT>;

       s->data=a[i];

       s->next=first->next;

       first->next=s;

  }

}

(2)  析构函数

template<classDT>

LinkList<DT>::~LinkList()

{

  while(first!=NULL)

  {

       q=first;

       first=first->next;

       deleteq;

  }

}

3插入操作

template<classDT>

voidLinkList<DT>::Insert(inti,DT x)

{

  p=first

  count=0;

  while(p!=NULL && count<i-1)

  {

       p=p->next;

       count++;

  }

  if(p==NULL) throw"位置";

  else

  {

       s=newNode <DT>;

       s->data=x;

       s->next=p->next;

       p->next=s;

  }

}

4删除操作

template<classDT>

DTLinkList<DT>::Delete(inti)

{

  p=first;

 count=0;

  while(p!=NULL&&count<i-1)

  {

       p=p->next;

       count++;

  }

  if(p==NULL||p->next==NULL)  throw "位置";

  else

  {

       q=p->next;

       x=q->data;

       p->next=q->next;

       deleteq;

       returnx;

  }

}

5)查找算法

按值查找

template<classDT>

intLinkList<DT>::Locate(DTx)

{

  p=first->next;

  count=1;

  while(p!=NULL)

  {

       if(p->data==x)return count;

       p=p->next;

       count++;

  }

  return0;

}

按位查找

template<classT>

TLinkList<T>::Get(inti)

{  

  p=first->next; 

  count=1;

 while (p!=NULL&&count<i)   

 {

   p=p->next;      //工作指针p后移

   count++;

 }

 if(p==NULL) throw "位置";

 elsereturn p->data;

}

6遍历操作

template<classDT>

voidLinkList<DT>::PrintList()

{

p=first->next;

  while(p!=NULL)

  {

       cout<<p->data<<"";

       p=p->next;

  }

}

(7)  求长度

template<classDT>

voidLinkList<DT>::Length()

{

 P=first->next;

count=0;

while(p!=NULL)

{

 p=p->next;

count++;

}

 returncount;

}

五、循环链表

将单链表的首尾相接,将终端结点的指针域由空指针改为指向头结点,构成单循环链表,简称循环链表。

空表和非空表的处理一致——附设头结点。

插入算法

s=newNode<DataType>;

s->data=x; 

s->next=p->next;      

p->next=s;

循环链表中没有明显尾端,如何避免死循环?

  循环条件:

p !=NULL—>p != first

p->next!=NULL—>p->next!= first

开始结点:first->next

  终端结点:将单链表扫描一遍,时间为O(n)

  带尾指针的循环链表

 一个存储结构设计得是否合理,取决于基于该存储结构的运算是否方便,时间性能是否提高。

六、双链表

双链表:在单链表的每个结点中再设置一个指向其前驱结点的指针域

data:数据域,存储数据元素;

prior:指针域,存储该结点的前趋结点地址;

next:指针域,存储该结点的后继结点地址。

定义:

template<classDataType>

structDulNode

{

    DataTypedata;

    DulNode<DataType>*prior, *next;

};

1) 插入操作

在结点p的后面插入一个新结点s,需修改4个指针:

s->prior=p;

s->next=p->next;

p->next->prior=s;

p->next=s;

2)删除操作

设指针p指向待删除结点,删除操作可通过下述语句完成,这两个语句顺序可颠倒。

(p->prior)->next=p->next;

(p->next)->prior=p->prior;

七、顺序表和链表的比较

1)存储分配方式比较

顺序表采用顺序存储结构,即用一段地址连续的存储单元依次存储线性表的数据元素,数据元素之间的逻辑关系通过存储位置来实现。

链表采用链接存储结构,即用一组任意的存储单元存放线性表的元素,用指针来反映数据元素之间的逻辑关系。

2 时间性能比较

时间性能是指实现基于某种存储结构的基本操作(即算法)的时间复杂度。

<1>按位查找:

顺序表的时间为(1),是随机存取;

链表的时间为(n),是顺序存取。

<2>插入和删除:

顺序表需移动表长一半的元素,时间为(n)

链表不需要移动元素,在给出某个合适位置的指针后,插入和删除操作所需的时间仅为(1)

3)空间性能比较

空间性能是指某种存储结构所占用的存储空间的大小。

定义结点的存储密度:

存储密度=数据域占用的存储量/整个结点占用的存储量

<1>结点的存储密度:

 顺序表:结点的存储密度为1(只存储数据元素),没有浪费空间;

 链表:结点的存储密度<1(包括数据域和指针域),有指针的结构性开销。

<2>结构的存储密度:

顺序表:需要预分配存储空间,如果预分配得过大,造成浪费,若估计得过小,又将发生上溢;

链表:不需要预分配空间,只要有内存空间可以分配,单链表中的元素个数就没有限制。

总结

若线性表需频繁查找却很少进行插入和删除操作,或其操作和元素在表中的位置密切相关时,宜采用顺序表作为存储结构;若线性表需频繁插入和删除时,则宜采用链表做存储结构。

当线性表中元素个数变化较大或者未知时,最好使用链表实现;而如果用户事先知道线性表的大致长度,使用顺序表的空间效率会更高。

总之,线性表的顺序实现和链表实现各有其优缺点,不能笼统地说哪种实现更好,只能根据实际问题的具体需要,并对各方面的优缺点加以综合平衡,才能最终选定比较适宜的实现方法。

八、线性表的其他存储方法

1、静态链表

静态链表:用数组来表示单链表,用数组元素的下标来模拟单链表的指针。

数组元素(结点)的构成:

data:存储放数据元素;

next:也称游标,存储该元素的后继在数组的下标。

first:静态链表头指针,为了方便插入和删除操作,通常静态链表带头结点

avail:空闲链表头指针,空闲链表由于只在表头操作,所以不带头结点

静态链表的存储结构定义如下:

constintMaxSize= 100;     //100只是示例数据

template<classDataType>

structSNode

 {

   DataTypedata;       //DataType表示不确定的数据类型

   intnext;            //指针域(也称游标)

}

 SList[MaxSize];

假设结点s插在结点p之后,则修改指针的操作为:

  s=avail;                     //利用空闲链的第一个结点

 avail=SList[avail].next;       //空闲链的头指针后移

 SList[s].data=x;              //x填入下标为s的结点

 SList[s].next=SList[p].next;    //将下标为s的结点插入到下标为p的结点后面

SList[p].next=s;               

假设删除结点p的后继结点,则修改指针的操作为:

 q=SList[p].next;           //暂存被删结点的下标

 SList[p].next=SList[q].next;  //摘链

 SList[q].next=avail;        //将结点q插在链avail的最前端

 avail=q;                 //空闲链头指针avail指向结点q

相对于顺序表而言,静态链表有什么优点?

优点:在执行插入和删除操作时,只需修改游标,不需要移动表中的元素,从而改进了在顺序表中插入和删除操作需要移动大量元素的缺点。

缺点:没有解决连续存储分配带来的表长难以确定的问题;静态链表还需要维护一个空闲链;静态链表不能随机存取。   

2、间接寻址:是将数组和指针结合起来的一种方法,它将数组中存储数据元素的单元改为存储指向该元素的指针。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值