第二章 线性表(2.5.1-2.5.2)
2.5 线性表的链式表示和实现
2.5.1单链表的定义的表示
线性表链式存储结构的特点是:用一组任意的存储单元存储存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。因此,为了表示每个数据元素ai与其后继数据元素ai+1之间的逻辑关系,对数据元素ai来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。这两部分信息组成数据元素ai的存储映像,称为结点(node)。它包括两个域:其中存储数据元素信息的域称为数据域;存储直接后继存位置的域称为指针域。指针域中存储的信息称作指针或链。n个结点链结成一个链表,即为线性表的链式存储结构。又由于此链表的每个结点中只包含一个指针域,故又称线性链表或单链表。
1、非顺序映像、链式映像:
用单链表表示线性表时,数据元素之间的逻辑关系是由结点中的指针指示的。换句话说,指针为数据元素之间的逻辑关系的映像,则逻辑上相邻的两个数据元素其存储的物理位置不要求紧邻,由此,这种存储结构为非顺序映像或链式映像。
通常将链表画成下图形式,这是因为在使用链表时,关心的只是它所表示的线性表中数据元素之间的逻辑顺序,而不是每个数据元素在存储器中的实际位置。
2.首元结点、头结点、头指针的区分:
(1)首元结点是指链表中存储第一个数据元素ai的结点。如上两图中的“ZHOU”。
(2)头结点是在首元结点之前附设的一个结点,其指针域指向首元结点。头结点的数据域可以不存储任何信息,也可以存储与数据元素类型相同的其他附加信息。例如,当数据元素为整数型时,头结点的数据域中可存放该线性表的长度。
(3)头指针是指向链表中第一个结点的指针。若链表设有头结点,则头指针所指结点为线性表的头结点;若链表不设头结点,则头指针所指结点为该线性表的首元结点。
3、链表增加头结点的作用:
(1)便于首元结点的处理
增加了头结点后,首元结点的地址保存在头结点的指针域中。则对链表的第一个数据元素的操作与其他数据元素相同,无需进行特俗处理
(2)便于空表和非空表的统一处理
当链表不设头结点时,假设L为单链表的头指针,他应该指向首元结点,则当单链表为长度n为0的空表时,L指针为空(L==NULL)。
4、总结:
在顺序表中,由于逻辑上相邻的两个元素在物理位置上紧邻,则每个元素的存储位置都可从线性表的起始位置计算得到。而在单链表中,各个元素的存储位置都是随意的。然而,每个元素的存储位置都包含在其直接前驱结点的信息之中。假设p是指向单链表中第i个数据元素的指针,则p->next是指向第i+1个个数据元素的指针。换句话说,若p->date=ai.则p->next->date=ai+1。由此,单链表是非随机存取的存储结构,要取得第i个数据元素必须从头指针出发顺链进行寻找。也称为顺序存取的存取结构。因此,其基本操作的实现不同于顺序表。
2.5.2 单链表基本操作的实现
1.初始化
1.1算法步骤
生成新的结点作为头结点,用头指针L指向头结点
头结点的指针域置空
1.2算法描述
Status InitList(LinkList L)
{
L=(LinkList)malloc(sizeof(Node));
L-next=NULL;
}
2、取值
2.1算法步骤
用指针p指向首元结点,用 j 做计数器初值赋为1。
从首元结点开始依次顺着链域next向下访问,只要指向当前结点的指针 p 不为空(NULL),并且没有达到序号为i的结点,则循环执行一下操作:1)p指向下一个结点
2)计数器 j++3}退出循环时,如果指针p为空,或者计数器 j 大于 i ,说明指定的序号 i 值不合法(i大于表长n 或 i 小于等于0),取值失败返回ERROR;否则取值成功,此时 j=i时,p所指的结点就是要找到第 i 个结点,用参数e保存当前结点的数据域,返回OK.
2.2算法描述
Status GetElem(LinkList L,int i,Elemtpye &e)
{//在带头结点的单链表L中根据序号i获取元素的值,用e返回L中第i个数据元素的值
LNode *p=L->next; //初始化,p指向首元结点
int j=1; //计数器初值赋为1
while(p&&j<i) //当p不为空,j<i时顺链域向后扫描
{
p=p->next; //p指向下一个结点
j++; //计数器加一
}
if(!p||j>i) //i值不合法i大于表长n或i<=0
return ERROR;
e=p->data; //取第i个结点的数据域
return OK;
}
3、查找
3.1算法步骤
1、用指针p指向首元结点。
2、从首元结点开始依次顺着链域next向下查找,只要指向当前结点的指针p不为空,并且p所指结点的数据域不等于给定值e,则循环执行以下操作:p指向下一个结点
3、返回p。若查找成功,p此时即为结点的地址值,若查找失败,p的值即为NULL。
3.2算法描述
LNode *LocateElem(LinkList L,ElemType e)
{//在带头结点的单链表L中查找值为e的元素
LNode *p=L->next; //初始化,p指向首元结点
while(p && p->data != e) //顺链域向后扫描,直到p为空或者p所指结点的数据域等于e
{
p=p->next;
return p; //查找成功返回值为e的结点地址p,查找失败p为NULL
}
}
4、插入
4.1算法步骤
要实现:将值为e的新结点插入到表的第i个结点的位置上,即插入到结点ai-1与ai之间
1、声明一个结点p指向链表的第一个结点,初始化j=1
2、 当j<i时,遍历链表,p的指针向后移动,j++;
3、若链表末尾p为空时,则说明第i个元素不存在
4、 否则查找成功,在系统中生成一个空结点s
5、将数据元素e赋值给s->data
6、s->next=p->next; p->next=s;
7、返回成功
4.2算法步骤
Status ListInsert(LinkList &L,int i,ElemType e)
{//在带头结点的单链表L中第i个位置插入值为e的新结点
LNode *p = L;
int j=0;
while(p&&(j<i-1))
{
p = p->next; //查找第i-1个结点,p指向该结点
j++;
}
if(!p||j>i-1) //i>n+1也就是表长或者i<1
reutrn ERROR;
LNode *s=new LNode; //生成新结点 *s
s->data=e; //将结点 *s的数据域置为e
s->next=p->next; //将结点 *s的指针域指向结点ai
p->next = s; //经结点 *p 的指针域指向结点 *s
return OK;
}
5、删除
5.1算法步骤
1、声明结点p指向空结点,初始化计数器j=0;
2、当j<i时,遍历链表,让p的指针向后移动,不断指向下一个结点,j++;
3、若链表末尾为空,则说明第i个结点不存在返回ERROR
4、否则将待删除的结点地址赋给q,以备释放。
5、q=p->next;p->next=q->next;
6、释放q,返回OK.
5.2算法描述
Status ListDelete(LinkList &L,int i)
{//在带头结点的单链表L中,删除第i个元素
LNode *p = L;
int j = 0;
while(p->next && j<i-1) //查找第i-1个结点,p指向该结点
{
p=p->next;
j++;
}
if(!(p->next)||j>i-1) //i大于n或者i<1时,删除位置不合理
return ERROR;
LNode *q=p->next; //临时保存被删除结点以备释放
p->next=q->next; //改变删除结点前驱结点的指针域
delete q; //释放被删除结点空间
return OK;
}
6、创建单链表
6.1前插法创建单链表
6.1.1描述:
前插法:将n个新结点逐个插入链表的头部(头结点结束之后),每次申请一个新结点,读入数据元素值,然后将新结点插入到头结点之后,例如,要插入a、b、c、d、e
,那么用前插法插入到链表中后,他们从头到尾的内容为:e、d、c、b、a,即输入顺序与线性表的逻辑顺序相反
6.1.2算法步骤:
1、创建一个只有头结点的空链表
2、根据待创建链表包括的元素个数n,循环n次执行以下操作:
1)生成一个新结点 p。
\2) 输入元素值赋给新结点p的数据域。
3)将新结点*p插入到头结点之后。
6.1.3算法描述
void CreateList_H(LinkList &L,int n)
{//逆位序输入n个元素的值,建立带 表头结点 的单链表L
L=new LNode //分配空间,建立一个带头结点的空链表
L->next = NULL;
for(i=0;i<n;i++)
{
LNode *p = new LNode; //生成新结点*p
cin>>p->data; //输入元素值赋给新结点 *p 的数据域
//将新结点 *p 插入到头结点之后
p->next = L->next; //将新结点*p的指针域指向下一结点
L->next = p; //将头结点的指针域指向新结点*p
}
}
6.2后插法创建单链表
6.2.1描述
后插法是通过将新结点逐个插入到链表尾部来创建链表,同前插法一样,每次申请一个新结点,读入数据元素值,不同的是,为了使新结点能插入到表尾,需增加一个尾指针r指向链表的尾结点。
6.2.2算法步骤
1、创建一个只有头结点的空链表
2、尾指针初始化,指向头结点。
3、根据创建链表包括的元素个数n,循环n次执行以下操作:1)生成一个新结点p
2)输入元素值赋给新结点p的数据域。
3)将新结点 *p插入到尾结点 *r之后;
4)尾指针r指向新的尾结点 *p;后插法的创建过程,读入数据的顺序和线性表中的逻辑顺序是相同的,读入1、2、3、4则输出顺序1、2、3、4。
6.2.2算法描述
void CreateList_R(LinkList &L,int n)
{//正位序输入n个元素的值,建立带表头结点的单链表L
L=new LNode;
L->next = NULL; //先建立一个带头结点的空链表
r=L; //尾指针r指向头结点
for(i = 0;i<n;i++)
{
LNode *p=new LNode; //生成新结点
cin>>p->data; //输入元素值赋给新结点*p的数据域
p->next = NULL;
r->next = p; //将新结点*p插入尾结点*r之后
r=p; //r指向新的尾结点*p
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)