线性表的链式表示和实现

线性表的链式表示和实现

单链表的定义和表示

线性表链式存储结构的特点是:用一组任意的存储单元存储线性表中的数据元素(这组存储单元可以是连续的也可以是不连续的)。因此,为了表示没一个数据元素ai与其直接后继元素ai+1之间的逻辑关系。

对数据元素ai来说,除了存储其本身的信息以外,还需要存储一个指示其后继的信息(即直接后继的存储位置)。这两部分信息组成数据元素ai的存储映像,称为结点(node)它包括两个域,其中存储数据信息的域称为数据域;存储其直接后继存储位置的域称为指针域。指针域中存储的信息称作指针或链。n个节点(ai(1<=i<=n)的存储映像)链结成一个链表,即为线性表的链式存储结构。又由于此链表中的每个节点中只包含一个指针域,故又称其为线性链表或者单链表。链表还有很多种形式,其中单链表,循环链表,和双向链表用于实现线性表的链式存储结构,其他形式的多用于实现图和树等非线性结构。

我们在使用链表时,关注的是数据元素之间的逻辑顺序,而不是每个数据元素在存储器中的实际位置。

-------------单链表的存储结构-------------

typedef struct LNode
{
   ElemType data;         //结点的数据域
   struct LNode *next;    //结点的指针域
}LNode,*LinkList;         //LinkList为指向结构体LNode的指针类型

事实上LNode,*LinkList二者本质上是等价的。
若定义LinkList L,则L为单链表的头指针,若定义LNode p,则p为指向单链表某个节点的指针,用p表示该节点。

一般情况下,为了处理方便,在单链表的第一个节点之后会再附设一个节点,称之为头节点。
链表增加头结点的作用如下:
①便于对首元结点的处理
增加了头结点之后,首元结点的地址保存在头结点(即其前驱结点)的指针域中,则对链表第一个元素的操作和其他元素都相同,无需进行特殊处理。
②便于空表和非空表的统一处理
当链表不设头结点时,假设L为单链表的头指针,他应该指向首元节点,泽当单链表位长度n为0得空表示,L指针为空(判断空表的条件可记为L==NULL).
增加头结点后,无论链表是否为空,头指针都是指向头结点的非空指针。头指针指向头节点。若为空表,泽头结点的指针域为0(判断空表的条件可以是L->next == NULL)

首元结点:是指链表中存储第一个数据元素a1的节点。
头结点:实在首元结点之前附设的一个结点,其指针域指向首元结点。头结点的数据域可以不存储任何信息
头指针:是指向链表中第一个结点的指针。若线性表设有头结点,则头指针指向的是线性表的头结点,如果线性表没有设置头指针,则线性表的头指针指向的是线性表的首元结点。

单链表的基本操作的实现

①单链表的初始化

Status InitList(LinkList &L)
{
   L=new LNode;     //生成新结点作为头结点,用指针L指向头结点
   L->next=NULL;    //头节点的指针域置空
   return OK;
}

单链表是非随机存取的存储结构,要取得第i个数据元素必须从头指针出发顺链进行查找,也称为顺序存储的存储结构。则其取值不能像顺序表那样随机访问,而只能从单链表的首元结点出发,顺着链域next逐个向下访问。
②单链表的取值
用指针p指向首元节点

Status GetElem(LinkList &L,int i,ElemType &e)
{
   p=L->next;j=1;    //初始化,p指向首元节点,计数器j初值赋为1
   while(p&&j<i)
   {
       p=p->next;
       ++j;
   }
   if(!p||j>i) return ERROR;   //i值不合法
   e=p->data;                  //取第i个结点的数据域
   return OK;
}

该算法的基本操作是比较j和i并后移指针,如果说1<=i<=n,则语句频度为i-1,则平均查找长度为:
ASL=1/n∑(i-1) i从1到n
由此可见单链表取值的算法复杂度为O(n)

③查找
当e不是结构体时

LNode *LocateElem(LinkList &L,ElemType &e)
{
   p=L->next;  //初始化,p指向首元节点
   while(p && p->data!=e)
       p=p->next;
   return p;
}

当e是结构体时
比如是定义学生的结构体

typedef struct
{ 
   int no;
   char num[8];
   char name[20];
}Student;

Student student;

按学号查找

LNode *LocateElem(LinkList &L,ElemType &e)
{
   p=L->next;  //初始化,p指向首元节点
   while(p && strcmp(p->data.num,e.num))
       p=p->next;
   return p;
}

按名字进行查找

LNode *LocateElem(LinkList &L,ElemType &e)
{
   p=L->next;  //初始化,p指向首元节点
   while(p && strcmp(p->data.name,e.name))
       p=p->next;
   return p;
}

按照id进行查找

LNode *LocateElem(LinkList &L,ElemType &e)
{
   p=L->next;  //初始化,p指向首元节点
   while(p && p->data.no!=e.no)
       p=p->next;
   return p;
}

其时间复杂度为O(n)

④插入

Status ListInsert(LinkList &L,int i,ElemType e)
{
    p=L;
    j=0;
    while(p&&(j<i-1))  //找到第i-1个节点
    {p=p->next;j++;}
    if(!p||j>i-1) return ERROR; //i>n+1或者i<1(当p为空或者j>i-1)
    s=new LNode;
    s->data=e;
    s->next=p->next;
    p->next=s;
    return OK;
}

和顺序表中一样,如果表中有n的节点,则插入操作中可插入的位置有n+1个,即1<=i<=n+1
插入的时间复杂度为O(n)

⑤删除

Status ListDelete(LinkList &L,int i)
{
   p=L;j=0;
   while((p->next) && (j<i-1))   //查找第i-1个节点,p指向该节点
       {p=p->next;j++;}
   if(!(p->next)||(j>i-1))  return ERROR;   //当i>n时或i<1时,或者本来链表的头结点就是空的
   q=p->next;   //临时保存被删结点的地址以被释放
   p->next=q->next;
   delete q;
   return OK;   
}

时间复杂度为O(n)

删除算法中的循环条件while((p->next) && (j<i-1))和插入操作中的循环条件while(p&&(j<i-1))是有差距的。因为在插入操作中,合法的插入位置有n+1个,而删除操作的删除位置只有n个。如果使用相同的循环条件,则会出现引用空指针的情况。需特别注意。

⑥创建单链表
(1)前插法创建单链表

Void CreatList_H(LinkList &L,int n)
{
   L=new LNode;
   L->next=NULL;  //先建立一个带头结点的单链表
   for(i=0;i<n;i++)
   {
       p=new LNode; 
       cin>>p->data;
       p->next=L->next;   
       L->next=p;
   }
}

(2)后插法创建单链表

Void CreatList_H(LinkList &L,int n)
{
   L=new LNode;
   L->next=NULL;  //先建立一个带头结点的单链表
   r=L;    //尾指针r指向头结点
   for(i=0;i<n;i++)
   {
       p=new LNode; 
       cin>>p->data;
       p->next=NULL;
       r->next=p;
       r=p;  //r始终指向为节点*P
   }
}

算法的时间复杂度为O(n)

2.5.3循环链表

循环链表是另一种形式的链式存储结构,其特点是表中最后一个节点的指针指向头结点,整个链表构成一个环,从表中任意结点出发均可找到表中其它结点。

将两个线性表合成一个线性表时,仅需要将一个标的为指针指向第二的表的第一个结点,第二个表的尾指针指向第一个表的头节点,然后释放第二个表的头结点。上述操作的时间复杂度为O(1).。语句段和图形展示如下图所示:
在这里插入图片描述
2.5.4双向链表
在双向链表中,双向链表的节点有两个人指针域,一个指向直接后继,另一个指向直接前驱,节点结构如图所示;-在这里插入图片描述
--------------双向链表的存储结构--------------

typedef struct DuLNode
{
    ElemType data;
    struct DuLNode *prior;
    struct DuLNode *next;
}DuLNode,*DuLinkList;

若d为指向表中某一结点的指针(即d为DuLinkList型变量)则有 d->next->prior = d->prior->next = d
在双向表中有些操作只需要涉及一个方向的指针,则他们的算法描述和顺序表相同,但在插入删除操作中有很大的不同,在双向链表中需要修改两个方向上的指针,插入节点是需要修改四个指针,删除结点时需要修改两个指针。两者的算法复杂度均为O(n)
在这里插入图片描述
双向表的插入

Status ListInsert_DuL(DulinkList &L,int i,ElemType e)
{
   if(!p=GetElem_DuL(L,i))//在L中确定第i个元素的位置指针p
       return ERROR;//p为空时,第i个元素不存在
   s=new DuLNode;
   s->data=e;
   s->prior=p->prior;//对应①操作
   p->prior->next=e;//对应②操作
   s->next=p;//对应③操作
   p->prior=s;//对应④操作 
}

双向链表的删除

Status ListDelete_DuL(DuLinkList &L,int i)
{
   if(!p=GetElem_DuL(L,i))//在L中确定第i个元素的位置指针p
       return ERROR;//p为空时,第i个元素不存在
   p->prior->next=p->next;//对应①
   p->next->prior=p->prior;//对应②
   delete p;
   return OK;
   
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值