数据结构之单链表
一、线性表的链式存储结构——链表
线性表中每个结点都有唯一的前驱结点和后继结点。 设计链式存储结构时,每个逻辑点存储单独存储,为了表示逻辑关系,增加指针域。 上图能清晰表示顺序表和链表之间的不同,之后还会详细介绍两者的区别与联系。
-
每个物理结点增加一个指向后继结点的指针域
?
\Rightarrow
?单链表 -
每个物理节点增加一个指向后继节点的指针域和一个指向前驱结点的指针域
?
\Rightarrow
?双链表 -
若一个节点中某哦个指针域不需要指向其他任何节点,则它的位置将为空,用常量NULL表示。 -
通常每个链表带有一个头节点,并通过头结点的指针唯一标识该链表,称之为头指针,相应地指向首结点或开始结点的指针称为首指针,指向尾结点的指针称为尾指针。
二、存储密度
存储密度是指结点数据本身所占的存储量和整个结点结构中所占的存储量之比。 一般地,存储密度越大,存储空间利用率就越高。由此可得,顺序表的存储空间为1,链表的存储空间小于1。
三、链表与顺序表的区别与联系
1.在顺序表中,逻辑上相邻的元素对应的存储位置也相邻,所以在进行插入或删除操作时,平均需要移动半个表的元素。而在链表中,逻辑上相邻的元素的存储位置是通过指针建立的,因而每个结点的存储位置可以任意安排,插入和删除相关结点时只需修改相关结点的指针域即可,相较于顺序表,这样更加方便。 2.顺序表具有随机选取的特性,而链表则不具有。 3.顺序表的存储密度为1,而链表则不为1。
四、单链表
在单链表中,每个结点(LinkNode)类型包括存储元素的数据域(data),还包括存储后继结点的指针域(next)。 下面将用代码定义一个单链表:
typedef struct LNode{
Elemtype data;
struct LNode 8next;
}LinkNode;
使用typedef的原因(C语言): 这样在后续写代码时就不用再带struct了。在C++中可不必这样操作。
五、单链表的特点
当访问过一个结点后,只能接着访问它的后继节点,而无法访问它的前驱结点。
六、单链表代码操作
1.插入结点和删除结点操作
(1).插入结点
在表L第i个位置插入指定元素e。 插入操作:将值为x的新结点s插入到p结点之后。 特点:只需修改相关结点的指针域,不需要移动结点。 C/C++语句描述如下:
s->next=p-<next;
p->next=s;
(2).删除结点
删除操作:删除p结点后的一个结点。 特点:只需修改相关结点的指针域,不需要移动结点。 删除操作语句:
p->next=p->next->next;
但其实上面操作并没有释放b结点所占用的内存。 因此,为释放被删除结点所占用的空间,采用如下方式:
q=p->next;
p->next=q->next;
free(q);
由上述操作可知,在单链表中插入 和删除一个结点时,需要先找到其前驱结点,在做相应的指针域修改。
2.建立单链表
此处为整体创建单链表。
(1).头插法建表
- 从一个空表开始,创建一个头结点。
- 依次读取字符数组a中的元素,生成新结点。
- 将新结点插入到;当前链表的表头上,直到结束为止。
注意:链表的逻辑顺序与逻辑次序相反。
算法如下:
void CreatListF(LinkNode *L,ElemType a[],int n)
{
LinkNode *s;
int i;
L=(LinkNode *)malloc(sizeof(LinkNode));
L-next=NULL;
for(i=0;i<n;i++)
{
s=(LinkNode *)malloc(sizeof(LinkNode);
s->data=a[i];
s-next=L->next;
L-next=s;
}
}
(2).尾插法建表
- 从一个空表开始,创建一个头结点。
- 依次读取数组a中的元素,生成新的结点。
- 将新结点插入到当前链表的表尾上,直到结束为止。
注意:链表的结点顺序与逻辑次序相等。
算法如下:
void CreateListR(LinkNode *&L,ElemType a[],int n)
{
LinkNode *s,*r;
int i;
L=(LinkNode *)malloc(sizeof(LinkNode));
r=L;
for(i=0;i<n;i++)
{
s->data=a[i];
t->next=s;
r=s;
}
r->next=NULL;
}
3.线性表基本运算在单链表上的实现
(1).初始化线性列表InitList(L)
建立一个空的单链表,即创建一个头结点。
void InitList(LinkNode *&L)
{
L=(LinkNode *)malloc(sizeof(LinkNode));
L-next=NULL;
}
(2).销毁线性表DestoryList(L)
释放单链表L占用的内存空间。即逐一释放全部结点的空间。
void DestoryList(L)
{
LinkNode *pre=L,*p=L->next;
while(p!=NULL)
{
free(pre);
pre=p;
p=pre->next;
}
free(pre);
该算法时间复杂度为O(n),其中n为单链表中数据结点的个数。
(3).判断线性表是否为空表ListEmpty(L)
若单链表L没有数据结点,则返回真,否则返回假。
bool ListEmpty(LinkNode *L)
{
return(L->next==NULL);
}
(4).求线性表的长度ListLenght(L)
返回单链表L中数据结点的个数。
void ListLenght(*&L)
{
int n=0;
LinkNode *p=L;
while(p->next!=NULL)
{
n++;
p=p->next;
}
return(n);
(5).输出线性表DispList(L)
逐一扫描单链表L的每个数据结点,并显示各结点的data域值。
void DispList(LinkNode *L)
{
LinkNode *p=L->next;
while(p!=NULL)
{
printf("%d",p->data);
p=p->next;
}
printf("\n");
}
(6).求线性表L中位置i的数据元素GetElem(L,i,&e)
在单链表L中从头结点开始找到第i个结点,若存在第i个数据结点,则将其data域值赋给变量e。
bool GetElem(LinkNode *L,int i,ElemType &e)
{
int j=0;
LinkNode *p=L;
while(j<i&&p!=NULL)
{
j++;
p=p->next;
if(p==NULL)
return false;
else
{
e=p->data;
return true;
}
}
(7).按元素查找LocateElem(L,e)
在单链表L中从开头找第一个值域与e相等的结点,若存在这样的结点,则返回位置,否则返回0。
int LocateElem(LinkNode *&L,ElemType e)
{
int i;
LinkNode *p=L->next;
while(p!=NULL&&p->data!=e)
{
p=p->next;
i++;
}
if(p==NULL)
return 0;
else
return i;
}
(8).插入数据元素ListInsert(&L,i,e)
先在单链表L中找到第i-1个结点p,若存在这样的结点,将其值为e的结点s插入到其后。
bool ListInsert(LinkNode *&L,int i,Elemtype e)
{
int j=0;
LinkNode *p=L,*s;
while(j<i-1&&p!=NULL)
{
j++;
p=p->next;
}
if(p==NULL)
return false;
else
{
s=(LinkNode *)malloc(sizeof(LinkNode));
s->data=e;
s-next=p-next;
p->next=s;
return true;
}
}
(9).删除数据元素ListDelete(&L,i,&e)
先在单链表L中找到第i-1个结点p,若存在这样的结点,且也存在后继节点,则删除该后继结点。
bool ListDelete(LinkNode *&L,int i,ElemType &e)
{
int j=0;
LikNode *p=L,*q;
while(j<i-1&&p!=NULL)
{
j++;
p=p->next;
}
if(p==NULL)
return false;
else
{
q=p->next;
if(q==NULL)
return false;
e=q->data;
p->next=q-next;
free(q);
return true;
4.单链表的算法设计方法
a.以建表算法为基础的算法设计 b.以查找为基础的算法设计
|