线性表的链式存储结构
链表的概述
结点不仅包含本身的信息还包含元素之间逻辑关系的信息
在链表存储结构中每个结点用于存储线性表的一个元素,每个结点不仅包含所存元素本身的信息,而且包含元素之间逻辑关系的信息,即前驱结点包含后继结点的地址信息,称为指针域链表中数据元素可以存储在内存中被占用的任意位置
链表由多个结点组成,这些结点在地址上可以是连续的,也可以不连续。头指针
链表中的一个结点的存储位置称为头指针,如果链表带有头结点,那么头指针为头结点的地址,如果链表不带头结点,头指针为开始结点的地址。通常用头指针来标识一个链表
特点:
顺序表比链表密度高
由于每个结点带有指针域,因而在存储空间上比较比顺序表要付出较大的代价链表操作简单
在链表中插入或删除操作中,只需要修改相关结点的指针域即可,不需要移动结点。
单链表增加一个头结点的优点如下:
第一个结点的操作和表中其他结点的操作一致,无须进行特殊处理
无论表是否为空,都有一个头结点,因此空表和非空表的处理也统一了
单链表
在单链表中,假设每个结点的类型用LinkNode表示,它应包括存储元素的数据域,这里用data表示,其类型用通用类型标识符ElemType表示,还包括存储后继结点位置的指针域,这里用next表示。LinkNode类型的声明如下:*相比较顺序表的优点
* 1、不用规定长度
* 2、存储的元素个数不受限制
* 3、插入和删除时,不用移动其他元素
*/
typedef struct LNode
{
ElemType data; //存放元素值
struct LNode * next; //指向后继结点
}LinkNode; //单链表的结点类型
为了方便,假设ElemType为int类型,使用以下自定义类型语句:
typedef int ElemType;
在后面的算法设计中,如果没有特别说明,均采用带头结点的单链表。其优点有:
- 单链表中首结点的插入和删除操作与其他结点一致,无须进行特殊处理
- 无论单链表是否为空都有一个头结点,因此统一了空表和非空表的处理过程。
/**
* 头指针:链表中的第一个结点的存储位置
* 头结点:在单链表的第一个结点前附设的一个结点
*/
//结点包括指针域和数据域
//链表是由N个结点链结成
/**头结点
*注意:我们在定义链表时,习惯性的会定义头结点,以便统一链表结点的插入和删除操作。
* 头结点也可以称为首元结点,最后一个结点也称为尾元结点
*/
typedef struct LinkList {
LNode* next;//头指针(如果链表有头结点, next就指向头节点,如果没有就指向第一个结点)
int length;//链表的长度,初始值为0;
}LinkList;
建立单链表
1、头插法
该方法从一个空表开始依次读取数组a中的元素,生成一个新结点(由s指向它),将读取的数组元素存放到该结点的数据域中,然后将其插入到当前链表的表头上,知道数组a的所有元素读完为止。void CreateListF(LinkNode *&L,ElemType a[],int n)
{
LinkNode *s; //生成一个新结点s
L=(LinkNode *)malloc(sizeof(LinkNode)); //分配内存空间
L->next =NULL;//创建头结点,其指针域next置为NULL;
for(int i=0;i<n;i++) //循环建立数据结点s
{
s=(LinkNode *)malloc(sizeof(LinkNode)); //循环分配s的空间
s->data=a[i]; //创建数据结点s
s->next=L->next; //将结点s插入到原首结点之前,头结点之后
L->next = s;
}
}
2、尾插法
该方法从一个空表开始依次读取数组a中的元素,生成一个新结点s,将读取的数组元素存放到该结点的数据域中,然后将其插入到当前链表的表尾上,知道数组a的所有元素读完为止。为此需要增加一个尾指针r,使其始终指向当前链表的尾结点,每插入一个新结点后让r指向这个新结点,最后还需要将r所指结点的next域置为空。void CreateListR(LinkNode *&L,ElemType a[],int n)
{
LinkNode *s,*r; //生成新结点
L=(LinkNode *)malloc(sizeof(LinkNode)); //分配内存空间,创建头结点
r=L;//r始终指向尾结点,初始时指向头节点
for(int i=0;i<n;i++) //循环建立数据结点
{
s=(LinkNode *)malloc(sizeof(LinkNode)); //循环分配s的空间
s->data=a[i]; //创建数据结点s
r->next=s; //将结点s插入到结点r之后
r = s;
}
r->next=NULL; //尾结点的next域置为空NULL;
}
线性表基本运算在单链表中的实现
1)初始化线性表InitList(&L)
该运算建立一个空的单链表,即创建一个头结点并将其next域置为NULL;void InitList(LinkNode *&L)
{
L=(LinkNode *)malloc(sizeof(LinkNode));
L->next = NULL; //创建头结点,其next域置为NULL
}
2)销毁线性表DestroyList(&L)
该运算释放单链表L占用的内存空间,即逐一释放全部结点的空间。其过程是让pre,p指向两个相邻的结点(初始时pre指向头结点,p指向首结点)。当p不为空时循环;释放结点pre,让后pre、p同步后移一个结点。循环结束后,pre指向尾结点,在将其释放。void DestroyList(LinkNode *&L)
{
LinkNode *pre = L,*p=L->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中没有数据结点时返回真,否则返回假。bool ListEmpty(LinkNode *L)
{
return (L->next==NULL);
}
4)求线性表的长度ListLength(L)
该运算返回单链表L中数据结点的个数。由于单链表没用存放数据结点个数的信息,需要通过遍历来统计。其过程是让P指向头结点,n用来累计数据结点个数(初始值为0),当p不为空时循环:n增1,p指向下一个结点。循环结束后返回nint ListLength(LinkNode *L)
{
int n=0;
LinkNode * p=L; //p指向头结点,n置为0(0即为头结点的序号)
while(P->next!=NULL)
{
n++;
p=p->next;
}
return n; //循环结束,p指向尾结点,其序号n为结点个数
}
5)输出线性表DispList(L)
该运算逐一扫描单链表L的每个数据结点,并显示个结点的data域值。int DispList(LinkNode *L)
{
LinkNode * p = L->next; //p指向首结点
while(p!=NULL) //p不为NULL,输出P结点的data域
{
printf("%d",p->data);
p=p->next; //p移向下一个结点
}
printf("\n");
}
6)求线性表中某个数据元素值GetElem(L,i,&e)
该运算在单链表L中从头开始找到第i个结点,若存在第i个数据结点,则将其data域值赋给变量e。void GetElem(LinkNode *L,int i,ElemType &e)
{
int j=0;
LinkNode * p = L; //p指向头结点,j置为0即头结点的序号
if(i<=0)retrun; //如果i错误则停止
while(j<i&&p!=NULL) //找到第i个结点p
{
j++;
p=p->next;
}
if(p==NULL)return //不存在第i给数据结点,停止
else
e=p->data; //存在则赋值给引用型参数e
}
7)按元素值查找LocateElem(L,e)
该运算在单链表L中从头开始找第一个值域与e相等的结点,若存在这样的结点,则返回逻辑序号,否则返回0int LocateElem(LinkNode *L,ElemType e)
{
int i=1;
LinkNode * p=L->next; //p指向首结点,i置为1(即首结点的序号为1)
while(p!=NULL&&p->data!=e) //查找data值为e的结点,其序号为i
{
p=p->next;
i++;
}
if(p==NULL)return 0; //不存在值为e的结点,返回0
else return i; //存在值为e的结点 ,返回逻辑序号i
}
8)插入数据元素ListInsert(&L,i,e)
该运算的实现过程是先在单链表L中找到第i-1个结点,由p指向它。若存在这样的结点,将值为e的结点(s指向它)插入到p所指结点的后面。void ListInsert(LinkNode *&L,int i,ElemType e)
{
int j=0;
LinkNode * p= L,*s; //p指向头结点,j置为0(即头结点的序号为0)
if(i<=0)return; //i错误
while(j<i-1&&p!=NULL) //查找第i-1个结点p
{
j++;
p=p->next;
}
if(p==NULL)return; //没找到i-1个结点
else
{//找到第i-1个结点p,插入新结点
s=(LinkNode *)malloc(sizeof(LinkNode)); //分配空间
s-data=e; //创建新结点s,其data域置为e
s->next=p->next; //将结点s插入到结点p之后
p->next=s;
}
}
9)删除数据元素ListDelete(&L,i,&`e)
该运算的实现过程是先在单链表L中找到第i-1个结点,由p指向它。若存在这样的结点,且也存在后继结点(由q指向它),则删除q所指向的结点void ListDelete(LinkNode *&L,int i,ElemType &e)
{
int j=0;
LinkNode * p=L,*q;//p指向头结点,j置为0(即头结点的序号为0)
if(i<=0)return;//i错误
while(j<i-1&&p!=NULL)//查找第i-1个结点p
{
j++;
p=p->next;
}
if(p==NULL)return;
else
{//找到i-1个结点p
q = p->next;//q指向第i个结点
if(q==NULL)return//若不存在第i个结点
e=q->data;//将q的data域赋值给引用型参数e
p->next=q-next;//从单链表中删除q结点
free(q);//释放q结点
}
}