您的位置:首页 > 理论基础 > 数据结构算法

C++版数据结构第二章线性表总结

2014-10-06 19:21 351 查看
一、线性表的逻辑结构
(1) 线性表的定义
1线性表:简称表,是n(n≥0)个具有相同类型的数据元素的有限序列。
2线性表的长度:线性表中数据元素的个数。
3空表:长度等于零的线性表,记为:L=(  )。
4非空表记为:L=(a1,a2
, …,ai-1, ai ,…, an)。其中,ai(1≤i≤n)称为数据元素;下角标i表示该元素在线性表中的位置或序号。
(2)线性表的特性
1. 有限性:线性表中数据元素的个数是有穷的。
2. 相同性:线性表中数据元素的类型是同一的。
3. 顺序性:线性表中相邻的数据元素ai-1和ai之间存在序偶关系(ai-1,ai),即ai-1是ai的前驱,ai是ai-1的后继;a1无前驱,an无后继,其它每个元素有且仅有一个前驱和一个后继。
二、线性表的抽象数据定义类型
抽象数据类型定义为:
ADT List
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>                      //模板类
class SeqList
{
 public: 
    SeqList( ) ;                               //构造函数
    SeqList(DataType a[ ], int n);      
    ~SeqList( );                            //析构函数
    int Length( );
    DataType Get(int i);
    int Locate(DataType x );
    void Insert(int i, DataType x); 
    DataType Delete(int i);       
 private:
    DataType data[MaxSize];
    int length;
};
(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≤i≤n+1):时间复杂度为O(n)。
(3)          删除操作
intSeqList::Delete(int i)
{
     if (length==0) throw "下溢";
     if (i<1||i>length) throw "位置非法";
     int x=data[i-1];
     for (int j=i;j<length;j++)
          data[j-1]=data[j];
     length--;
     return x;
}
(4)查找操作
按位查找:
template<classDataType> 
DataTypeSeqList<DataType>::Get( int i )
{
    if (i >= 1 && i <= length)  throw “查找位置非法”;
   else return data [i-1];
}
按值查找:
template<classDataType>  
intSeqList<DataType>::Locate(DataType x)
{
    for (i = 0; i < length; i++)
        if (data[i] == x) return i + 1;
    return 0;         
}
(4)          遍历操作:
voidSeqList::PrintList()
{
     for (int i=0;i<length;i++)
          cout<<data[i]<<"";
     cout<<endl;
}
四、        线性表的链接存储结构及实现
(1)     单链表
存储特点:
1.逻辑次序和物理次序不一定相同。
2.元素之间的逻辑关系用指针表示。
单链表是由若干结点构成的;单链表的结点只有一个指针域。
结点结构:
data:存储数据元素
next:存储指向后继结点的地址
template<classDataType>
struct Node
{
    DataType data;
    Node<DataType> *next;
};
申请结点:s=new Node <int> ;
引用数据元素:s->data;
引用指针域:s->next;
头指针:指向第一个结点的地址。
尾标志:终端结点的指针域为空。
空表:first=NULL;
非空表:
头结点:在单链表的第一个元素结点之前附设一个类型相同的结点,以便空表和非空表处理统一。
单链表类的声明:
template<classDataType>
class LinkList
{  
public:
    LinkList( );
    LinkList(DataType a[ ], int n);
    ~LinkList( );
    int Length( );         
    DataType Get(inti);          
    int Locate(DataType x);      
    void Insert(int i, DataType x);  
    DataType Delete(int i);       
    void PrintList( );          
private:
    Node<DataType> *first;
};
(1)构造函数:
无参构造函数:
template<classDT>
LinkList<DT>::LinkList()
{
  first=new Node<DT>;
  first->next=NULL;
}
有参构造函数:
尾插法
template<classDT>
LinkList<DT>::LinkList(DTa[],int n)
{
  Node <DT> *r,*s;
  first=new Node<DT>;
  r=first;
   for(int i=0;i<n;i++)
   {
       s=new Node<DT>;
       s->data=a[i];
       r->next=s;
       r=s;
   }
  r->next=NULL;
}
头插法
template<classDT>
LinkList<DT>::LinkList(DTa[],int n)
{
  first=new Node<DT>;
  first->next=NULL;
   for( i=0;i<n;i++)
   {
       s=new Node<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;
       delete q;
   }
}
(3)插入操作
template<classDT>
voidLinkList<DT>::Insert(int i,DT x)
{
  p=first;
  count=0;
  while (p!=NULL && count<i-1)
   {
       p=p->next;
       count++;
   }
   if(p==NULL) throw"位置";
  else
   {
       s=new Node <DT>;
       s->data=x;
       s->next=p->next;
       p->next=s;
   }
}
(4)删除操作
template<classDT>
DTLinkList<DT>::Delete(int i)
{
  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;
       delete q;
       return x;
   }
}
5)查找算法
按值查找
template<classDT>
intLinkList<DT>::Locate(DT x)
{
  p=first->next;
  count=1;
  while (p!=NULL)
   {
       if(p->data==x) return count;
       p=p->next;
       count++;
   }
  return 0;
}
按位查找
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>
struct DulNode
{
    DataType data;
    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>按位查找:
顺序表的时间为O(1),是随机存取;
链表的时间为O(n),是顺序存取。
<2>插入和删除:
顺序表需移动表长一半的元素,时间为O(n);
链表不需要移动元素,在给出某个合适位置的指针后,插入和删除操作所需的时间仅为O(1)。
(3)空间性能比较
空间性能是指某种存储结构所占用的存储空间的大小。
定义结点的存储密度:
存储密度=数据域占用的存储量/整个结点占用的存储量
<1>结点的存储密度:
 顺序表:结点的存储密度为1(只存储数据元素),没有浪费空间;
 链表:结点的存储密度<1(包括数据域和指针域),有指针的结构性开销。
<2>结构的存储密度:
顺序表:需要预分配存储空间,如果预分配得过大,造成浪费,若估计得过小,又将发生上溢;
链表:不需要预分配空间,只要有内存空间可以分配,单链表中的元素个数就没有限制。
总结
⑴若线性表需频繁查找却很少进行插入和删除操作,或其操作和元素在表中的位置密切相关时,宜采用顺序表作为存储结构;若线性表需频繁插入和删除时,则宜采用链表做存储结构。
⑵当线性表中元素个数变化较大或者未知时,最好使用链表实现;而如果用户事先知道线性表的大致长度,使用顺序表的空间效率会更高。
总之,线性表的顺序实现和链表实现各有其优缺点,不能笼统地说哪种实现更好,只能根据实际问题的具体需要,并对各方面的优缺点加以综合平衡,才能最终选定比较适宜的实现方法。
八、线性表的其他存储方法
1、静态链表
静态链表:用数组来表示单链表,用数组元素的下标来模拟单链表的指针。
数组元素(结点)的构成:
data:存储放数据元素;
next:也称游标,存储该元素的后继在数组的下标。
first:静态链表头指针,为了方便插入和删除操作,通常静态链表带头结点
avail:空闲链表头指针,空闲链表由于只在表头操作,所以不带头结点
静态链表的存储结构定义如下:
const intMaxSize= 100;     //100只是示例数据
template<classDataType>
struct SNode
 {
   DataType data;       //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、间接寻址:是将数组和指针结合起来的一种方法,它将数组中存储数据元素的单元改为存储指向该元素的指针。


 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  线性表 数据结构 c++