线性表的链式表示和实现
单链表的定义和表示
线性表链式存储结构的特点是:用一组任意的存储单元存储线性表中的数据元素(这组存储单元可以是连续的也可以是不连续的)。因此,为了表示没一个数据元素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;
}