1. 单链表的数据抽象类型
对于单链表来说,它是线性表的一种存储结构,当我们把单链表的数据对象,数据关系,数据操作描述出来后,然后就可以考虑基本操作的实现了。
ADT ——线性表:
ADT List
{
数据对象:
D = {ai | ai∈ElemType, i=1,2,…,n, n≧0 } //ElemType为类型标识符
数据关系:
R = {<ai-1, ai> | ai-1, ai∈D, i=2,3,…,n }
数据操作:
(1)初始化线性表InitList(&L):构造一个空的线性表L
(2)销毁线性表DestroyList(&L):释放线性表L占用的内存空间
(3)判线性表是否为空表ListEmpty(L):若L为空表,则返回真,否则返回假
(4)求线性表的长度ListLength(L):返回L中元素个数
(5)输出线性表DispList(L):当线性表L不为空时,顺序显示L中各节点的值域
(6)求线性表L中指定位置的某个数据元素GetElem(L,i,&e):用e返回L中第 i 个元素的值
(7)查找元素LocateElem(L,e):返回线性表L中第1个与e相等的序号,找不到返回0
(8)插入元素ListInsert(&L, i, &e):在线性表L中的第i个位置插入元素e;
(9)删除元素ListDelete(&L, i, &e):在线性表L中删除第i个元素,有e返回删除的值;
}
2. 单链表的基本运算实现
1.初始化线性表InitList(L)
InitList()运算用于建立一个空的单链表,即创建一个头节点,这个头结点一般不存储数据。
void InitList(LinkList *&L)
{
//创建头结点,并通指针L指向头结点的地址
L=(LinkList *)malloc(sizeof(LinkList));
L->next=NULL;
}
2.销毁线性表DestroyList(L)
DestroyList()运算用于释放单链表L占用的内存空间。即逐一释放全部节点的空间。
在开始时,定义两个指针,指针pre记录销毁的节点,指针p用于记录下一个将要销毁的节点。
指针pre在释放完当前节点时,指针p不断的向后移动去指向下一个节点,当全部节点释放完毕时,p指向就会为NULL,pre就可以把指向的尾节点释放掉。
void DestroyList(LinkList *&L)
{
//相当于图1中的初始操作
LinkList *pre=L,*p=L->next;
//后面所有的代码相当于图2中的操作
while (p!=NULL)
{
//释放节点的空间
free(pre);
//移动到下一个要释放的节点位置
pre=p;
//p指向下一个节点
p=pre->next;
}
//释放尾节点的空间
free(pre);
}
3.判线性表是否为空表ListEmpty(L)
判断线性表是否为空,如下图所示:
第一种情况线性表是在只有一个头结点的情况,且头结点的next指针域为NULL,没有存储有效数据,所以这时线性表为空。
第二种情况线性表是在头结点的next指针域不为NULL,说明线性表中有多个节点,因此线性表就不为空。
无论是对于第一种情况,还是第二种情况,我们只需判断头结点的next指针域即可。
bool ListEmpty(LinkList *L)
{
//根据头结点next指针域来判断线性表是否为空
return(L->next==NULL);
}
4.求线性表的长度ListLength(L)
ListLength()运算用于返回线性表的长度,也就是节点的个数。
在计算线性表的长度时,定义一个指针p指向链表的头结点位置,再定义一个变量n记录节点的个数。
然后p往后移动,只要p指向的节点的next指针域不为NULL,说明后面还有节点,那么n就加1,当p的next指针域为空时,就可以返回线性表的长度n了。
int ListLength(LinkList *L)
{
//n记录节点的个数
int n=0;
//指针p用于记录节点
LinkList *p=L;
//只要next指针域不为NULL,说明还有节点
while (p->next!=NULL)
{
n++;
//指针p移动到下一个节点
p=p->next;
}
//最后返回线性表的长度
return n;
}
5.输出线性表DispList(L)
DispList()运算是用于输出链表中的每个节点的数据域的值。
void DispList(LinkList *L)
{
//指针p直接指向第一个节点
LinkList *p=L->next;
//只要p不为NULL,说明还有节点
while (p!=NULL)
{
//输出节点的数据域
printf("%d ",p->data);
//移动到下一个节点
p=p->next;
}
printf("\n");
}
6.求线性表L中指定位置的某个数据元素GetElem(L,i,&e)
GetElem()运算实现思路:在单链表L中从头开始找到第i个节点,若存在第i个数据节点,则将其data域值赋给变量e。
在开始时,j用于记录查找到第几个节点,然后指针p指向链表开头位置。
指针p移动到第i个节点的位置时,此时j = i,并判断p是否为NULL,如果不为NULL,说明找到了第i个节点,那么就可以把第i个节点的数据域赋值给e,然后返回。
bool GetElem(LinkList *L,int i,ElemType &e)
{
// j 用于记录查找到第几个节点
int j=0;
//指针p指向链表开头位置
LinkList *p=L;
//指针p移动到第i个节点的位置,j不能大于i
while (j<i && p!=NULL)
{
//同时j要记录移动到第几个节点
j++;
//移动指针p
p=p->next;
}
//如果p == NULL说明没有找到第i个节点,直接返回false
if (p==NULL)
return false;
else
{
//否则就找到了第i个节点,同时把数据域的值赋值给e,返回true说明查找成功
e=p->data;
return true;
}
}
7.按元素值查找LocateElem(L,e)
LocateElem()运算的实现思路:从头开始找第1个值域与e相等的节点,若存在这样的节点,则返回位置,否则返回0。
在开始时,先定义一个指针p指向链表的第一个有效节点,i = 1表示记录第一个有效节点的位置
判断指针p的指针域是否为空,同时判断p的数据域是否为要查找的元素,如果没查找到指针p就移动到下一个节点。
int LocateElem(LinkList *L,ElemType e)
{
int i=1;
//同样还是要定义一个指针p指向第一个有效节点
LinkList *p=L->next;
//判断指针p的指针域是否为空,同时判断p的数据域是否为要查找的元素,不是p就往后移动
while (p!=NULL && p->data!=e)
{
p=p->next;
i++;
}
//如果p == NULL,说明没找到要查找的元素
if (p==NULL)
return (0);
else
//否则说明就找到了要查找的元素,返回其位置
return (i);
}
8.插入数据元素ListInsert(&L,i,e)
ListInsert()运算的实现思路:先在单链表L中找到第i-1个节点 p,若存在这样的节点,将值为e的节点s插入到其后。
在开始时,先定义指针p指向链表的开始位置,依次往后移动到要插入节点的位置
这里要注意一点,因为我们要在第i个节点插入新节点,那么我们就需要找到第i-1个节点,在这个位置插入新节点,也就是说指针p要移动到第i-1个节点的位置。
当找到第i-1个节点时(指针p移动到第i-1个节点),给新节点分配空间,然后指针s指向新节点,并给新节点的数据域赋值,然后新节点的next指针域指向第i个节点的地址,第i-1个节点的next指针域指向新节点,完成插入操作。
bool ListInsert(LinkList *&L,int i,ElemType e){
//j用于记录i-1个节点
int j=0;
//定义指针p指向链表的开始位置,指针s用于指向要插入的新节点地址
LinkList *p=L,*s;
//指针p移动到i-1个节点的位置
while (j<i-1 && p!=NULL){
j++;
p=p->next;
}
//如果p为NULL说明没找到第i-1个位置,超过了链表的长度
if (p==NULL)
return false;
else{
//指针s指向新节点
s=(LinkList *)malloc(sizeof(LinkList));
//新节点数据域赋值
s->data=e;
//新节点指向第i个节点
s->next=p->next;
//然后第i-1个节点的next指针域指向新节点
p->next=s;
return true;
}
}
9.删除数据元素ListDelete(&L,i,&e)
ListDelete()运算的实现思路:先找到第i-1个节点p,若存在这样的节点,且也存在后继节点,则删除该后继节点。
跟插入数据元素一样,在开始时,先定义指针p指向链表的开始位置,依次往后移动到要删除节点的位置
需要先找到删除位置,也就是找到第i-1个节点的位置,指针p指向第i-1个节点。
在删除第i个节点时,指针q记录下第i个节点的数据域,再把指针p的next指针域指向第i+1个节点,同时把第i个节点的空间释放掉。
bool ListDelete(LinkList *&L,int i,ElemType &e){
int j=0;
//定义指针p指向链表的开始位置,指针q用于记录要删除的节点
LinkList *p=L,*q;
//移动指针p,直到指向第i-1个节点位置
while (j<i-1 && p!=NULL) {
j++;
p=p->next;
}
//p为null说明没找到第i-1个节点,链表长度不足
if (p==NULL)
return false;
else{
//找到第i-1个节点,并让指针q记录第i个节点
q=p->next;
//注意:当找到第i-1个节点时,需要判断第i个节点是否存在
if (q==NULL)
return false;
//记录下要删除的节点的数据域
e=q->data;
//把指针p指向第i+1个节点
p->next=q->next;
//释放第i个节点的空间
free(q);
return true;
}
}