一、线性表的链式存储—链表
在链式存储中,每个存储节点不仅包含有所存元素本身的信息(称之为数据域),而且包含有元素之间逻辑关系的信息,即前驱节点包含有后继节点的地址信息,这称为指针域,这样可以通过前驱节点的指针域方便地找到后继节点的位置,提高数据查找速度。 若一个节点中的某个指针域不需要任何节点,则仅它的值为空,用常量NULL表示。
在线性表的链式存储中,为了便于插入和删除运算的实现,每个链表带有一个头节点,并通过头节点的指针唯一标识该链表。
由于链表的每个结点带有指针域,从存储密度来讲,这是不经济的。所谓存储密度是指结点数据本身所占的存储量和整个结点结构中所占的存储量之比,即:存储密度=节点数据本身占用的空间/节点占用的空间总量。
一般地,存储密度越大,存储空间的利用率就越高。显然,顺序表的存储密度为1,而链表的存储密度小于1。例如,若单链表的结点数据均为整数,指针所占的空间和整数相同,则单链表的存储密度为50%。若不考虑顺序表中的空闲区,则顺序表的存储空间利用率为100%。
在单链表中,由于每个节点只包含有一个指向后继节点的指针,所以当访问过一个节点后,只能接着访问它的后继节点,而无法访问它的前驱节点。
另一种可以采用的方法是: 在每个节点中除包含有数值域外,设置有两个指针域,分别用以指向其前驱节点和后继节点,这样构成的链接表称之为线性双向链接表,简称双链表。
二、单链表
在单链表中,假定每个节点类型用LinkList表示,它应包括存储元素的数据域data和存储后继元素位置的指针域next。
LinkList类型的定义如下:
typedef struct LNode //定义单链表节点类型
{ ElemType data;
struct LNode *next; //指向后继节点
} LinkList;
1. 插入节点和删除节点操作
插入操作是将值为x的新节点插入到单链表的第i个节点的位置上。先在单链表中找到第i-1个节点,再在其后插入新节点。
单链表插入和删除节点的过程如下图所示。
插入操作语句描述如下: s->next=p->next; p->next=s;
删除操作语句描述如下: p->next=p->next->next;
2.采用头插法建立单链表的过程
代码:
void CreateListF(LinkList *&L,ElemType a[],int n)
{ LinkList *s;
int i;
L=(LinkList *)malloc(sizeof(LinkList));
L->next=NULL; //创建头节点,其next域置为NULL
for (i=0;i<n;i++) //循环建立数据节点
{ s=(LinkList *)malloc(sizeof(LinkList));
s->data=a[i]; //创建数据节点*s
s->next=L->next; //将*s插在原开始节点之前,头节点之后
L->next=s;
}
}
3.采用尾插法
头插法建立链表虽然算法简单,但生成的链表中节点的次序和原数组元素的顺序相反。
若希望两者次序一致,可采用尾插法建立。该方法是将新节点插到当前链表的表尾上,为此必须增加一个尾指针r,使其始终指向当前链表的尾节点。
void CreateListR(LinkList *&L,ElemType a[],int n)
{ LinkList *s,*r;
int i;
L=(LinkList *)malloc(sizeof(LinkList)); //创建头节点
r=L; //r始终指向尾节点,开始时指向头节点
for (i=0;i<n;i++) //循环建立数据节点
{ s=(LinkList *)malloc(sizeof(LinkList));
s->data=a[i]; //创建数据节点*s
r->next=s; //将*s插入*r之后
r=s;
}
r->next=NULL; //尾节点next域置为NULL
}
3. 线性表基本运算在单链表上的实现
(1)初始化线性表InitList(L)
该运算建立一个空的单链表,即创建一个头节点。
void InitList(LinkList *&L)
{ L=(LinkList *)malloc(sizeof(LinkList));
//创建头节点
L->next=NULL;
}
(2)销毁线性表DestroyList(L)
释放单链表L占用的内存空间。即逐一释放全部节点的空间。
void DestroyList(LinkList *&L)
{ LinkList *pre=L,*p=p->next; //pre指向*p的前驱节点
while (p!=NULL) //扫描单链表L
{ free(pre); //释放*pre节点
pre=p; //pre、p同步后移一个节点
p=pre->next;
}
free(pre); //循环结束时,p为NULL,pre指向尾节点,释放它
}
(3)判线性表是否为空表ListEmpty(L)
若单链表L没有数据节点,则返回1,否则返回-1。
int ListEmpty(LinkList *L)
{
return(L->next==NULL);
}
(4)求线性表的长度ListLength(L)
返回单链表L中数据节点的个数。
int ListLength(LinkList *L)
{ int n=0;
LinkList *p=L; //p指向头节点,n置为0(即头节点的序号为0)
while (p->next!=NULL)
{ n++;
p=p->next;
}
return(n); //循环结束,p指向尾节点,其序号n为节点个数
}
(5)输出线性表DispList(L)
逐一扫描单链表L的每个数据节点,并显示各节点的data域值。
void DispList(LinkList *L)
{ LinkList *p=L->next; //p指向开始节点
while (p!=NULL) //p不为NULL,输出*p节点的data域
{ printf("%d ",p->data);
p=p->next; //p移向下一个节点
}
printf("\n");
}
(6)求线性表L中指定位置的某个数据元素GetElem(L,i,&e)
思路:在单链表L中从头开始找到第i个节点,若存在第i个数据节点,则将其data域值赋给变量e。
int GetElem(LinkList *L,int i,ElemType &e)
{ int j=0;
LinkList *p=L; //p指向头节点,j置为0(即头节点的序号为0)
while (j<i && p!=NULL) //找第i个节点
{ j++;
p=p->next;
}
if (p==NULL) //不存在第i个数据节点
return -1;
else //存在第i个数据节点
{ e=p->data;
return 1;
}
}
(7)按元素值查找LocateElem(L,e)
思路:在单链表L中从头开始找第1个值域与e相等的节点,若存在这样的节点,则返回位置,否则返回0。
int LocateElem(LinkList *L,ElemType e)
{ int i=1;
LinkList *p=L->next; //p指向开始节点,i置为1
while (p!=NULL && p->data!=e)
{ p=p->next; //查找data值为e的节点,其序号为i
i++;
}
if (p==NULL) //不存在元素值为e的节点,返回0
return(0);
else //存在元素值为e的节点,返回其逻辑序号i
return(i);
}
(8)插入数据元素ListInsert(&L,i,e)
思路:先在单链表L中找到第i-1个节点*p,若存在这样的节点,将值为e的节点*s插入到其后。
int ListInsert(LinkList *&L,int i,ElemType e)
{ int j=0;
LinkList *p=L,*s; //p指向头节点,j置为0
while (j<i-1 && p!=NULL) //查找第i-1个节点
{ j++;
p=p->next;
}
if (p==NULL) //未找到第i-1个节点
return -1;
else //找到第i-1个节点*p,插入新节点
{ s=(LinkList *)malloc(sizeof(LinkList));
s->data=e; //创建新节点*s,其data域置为e
s->next=p->next; //将*s插入到*p之后
p->next=s;
return 1;
}
}
(9)删除数据元素ListDelete(&L,i,&e)
思路:先在单链表L中找到第i-1个节点*p,若存在这样的节点,且也存在后继节点,则删除该后继节点。
int ListDelete(LinkList *&L,int i,ElemType &e)
{ int j=0;
LinkList *p=L,*q; //p指向头节点,j置为0
while (j<i-1 && p!=NULL) //查找第i-1个节点
{ j++;
p=p->next;
}
if (p==NULL) //未找到第i-1个节点
return -1;
else //找到第i-1个节点*p
{ q=p->next; //q指向第i个节点
if (q==NULL) //若不存在第i个节点,返回false
return false;
e=q->data;
p->next=q->next; //从单链表中删除*q节点
free(q); //释放*q节点
return 1; //成功删除第i个节点
}
}