单链表的基本操作

线性表的链式存储基本概念

线性表链式存储结构的特点:用一组任意的存储单元存储线性表的数据元素(存储单元可连续,可不连续)。

结点:为了表示每个数据元素ai与其直接后继数据元素ai+1之间的逻辑关系,对数据元素ai来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置),这两部分信息组成数据元素ai的存储映像,称为结点,节点包括数据域(存储数据元素信息的域)和指针域(存储直接后继存储位置的域),指针域中存储的信息称作指针
线性表的链式存储结构:n个结点(ai的存储映像)链结成一个链表,即为线性表的链式存储结构,因为每个结点中只包含一个指针域,所以又称线性链表或单链表。

链式存储结构图:
在这里插入图片描述

结点的组成示意图
在这里插入图片描述
区分三个容易混淆的概念:
1、首元结点:链表中存储第一个数据元素a1的结点。
2、头结点:头结点是在首元结点之前附设的一个结点,其指针域指向首元结点。头结点的数据域可以不存任何信息,也可存与数据元素类型相同的其他附加信息,如存放线性表的长度等。
3、头指针:指向链表中第一个结点的指针。

结合下图理解:
在这里插入图片描述
由于最后一个元素没有直接后继,所以单链表最后一个结点的指针为空(NULL).

单链表的存储结构描述

typedef struct LNode
{
	ElemType data;//结点的数据域,ElemTpye是通用类型标识符,可换为其他数据,如int
	struct LNode *next;//结点的指针域
}LNode,*LinkList;//LinkList为指向结构体LNode的指针类型


说明: 1)单链表中每个结点的存储结构包括两括:
存储结构的数据域data,其类型自定义,int,char,double等。
存储后继结点位置的指针域next,其类型为指向结点的指针类型LNode*. 2)为了提高程序的可读性,对同一结构体指针类型起了两个别名:LinkList
和LNode
*,两者本质上是等价的。通常习惯上用LinkList
定义单链表
,强调定义的是某个的单链表的头指针如(LinkList
L
,L为单链表的头指针);用LNode*p定义指向单链表中任意结点的指针变量,若定义LNode
*p,则p为指向单链表中某个结点的指针,用 *p代表该结点。当然也可使用定义LinkList p,这种定义形式完全等价于LNode *p. 3)单链表有表头指针唯一确定,因此单链表可以用头指针名字命名,若头指针名为L,则该链表为表L; 4)区分指针变量结点变量两个不同概念,若定义LinkList p或LNode
*p,则p为指向某结点的指针变量表示该结点的地址,而 *p为对应的结点变量,表示该结点的名称。

从上述说明中得出,每个结点都是由存放数据元素的数据域和存方后继结点地址的指针域组成。假设p是指向第i个元素的指针,则该结点ai的数据域可以用p->data来表示,该节点的指针域(下一个结点的存储地址)可以用p->next来表示。p->next是指向第i+1个节点的指针,即如果p->data=ai,则p->next->data=ai+1,如图:
在这里插入图片描述

单链表的基本操作

单链表的初始化

就是构造一个空表
【算法步骤】
1、生成新结点作为头结点,用头指针L指向头结点。
2、头结点的指针域置空。
【算法描述】

Status InitList(LinkList &L)
{
	L=new LNode;	//生成新结点作为头结点,用头指针L指向头结点(内存的动态分配用new 数据类型)
	L->next=NULL	//头结点的指针域置空
	return OK;
}

单链表的取值

从链表首地址出发,顺着链域next逐个结点向下访问。
【算法步骤】
1、用指针p指向首元结点,用 j 做计数器初值赋为1。
2、从首元结点开始依次顺着链域next向下访问,只要指向当前结点的指针 p 不为空(NULL),并且没有达到序号为i的结点,则循环执行一下操作:

1)p指向下一个结点
2)计数器 j++

3、退出循环时,如果指针p为空,或者计数器 j 大于 i ,说明指定的序号 i 值不合法(i大于表长n 或 i 小于等于0),取值失败返回ERROR;否则取值成功,此时 j=i时,p所指的结点就是要找到第 i 个结点,用参数e保存当前结点的数据域,返回OK.

【算法描述】

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;
}

单链表的按值查找

【算法步骤】
1、用指针p指向首元结点。
2、从首元结点开始依次顺着链域next向下查找,只要指向当前结点的指针p不为空,并且p所指结点的数据域不等于给定值e,则循环执行以下操作:

p指向下一个结点

3、返回p。若查找成功,p此时即为结点的地址值,若查找失败,p的值即为NULL。
【算法描述】

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
	}
}

单链表的插入

假设要在两个数据元素a,b之间插入一个数据元素e,p为指向a结点的指针,如图
在这里插入图片描述

为插入数据元素e,要先生成一个新的结点,其数据域为e,然后插入链表中,(这里先假设s为指向结点e的指针),再把原来链子的右端与新链子结合(即s->next = p->next ),最后再把左端链子接上新的结点(即p->next = s);

【算法步骤】
要实现:将值为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、返回成功

【算法描述】

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;

}

说明:如果表中有n个结点,则插入操作中合法的插入位置有n+1个,即1<=i<=n+1;当i=n+1时,新结点则插在链表尾部。

单链表的删除

同插入元素一样,首先应找到该位置的前驱结点,如图
在这里插入图片描述
要删除单链表中第i个结点,先找到被删除结点的前驱,然后将第i个结点的的前驱结点指针绕过,指向它的后继,即p->next = p->next->next,因为在删除结点时,除了修改结点ai-1的指针域,还有释放结点ai所占的空间,所以在修改指针前用一个新的指针p,临时保存结点ai的地址以备释放

【算法步骤】
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.

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;


}

要查找第i个结点的位置,需要遍历链表,所以插入‘删除操作的时间复杂的为O(n).

创建单链表

初始化操作只是创建一个只有一个头结点的空链表,而上面链表的其他算法都是假定已经存在多个结点,我们如何建立一个包括若干结点的链表?即从空表初始状态起,依次建立各元素结点,并逐个插入链表

单链表的创建即给定n个元素,然后插入到链表当中。
根据结点插入位置不同,链表的创建方法可分为前插法和后插法。

前插法创建单链表

前插法:将n个新结点逐个插入链表的头部(头结点结束之后),每次申请一个新结点,读入数据元素值,然后将新结点插入到头结点之后,例如,要插入a、b、c、d、e
,那么用前插法插入到链表中后,他们从头到尾的内容为:e、d、c、b、a,即输入顺序与线性表的逻辑顺序相反

【算法步骤】
1、创建一个只有头结点的空链表
2、根据待创建链表包括的元素个数n,循环n次执行以下操作:

1)生成一个新结点 p。
2) 输入元素值赋给新结点
p的数据域。
3)将新结点*p插入到头结点之后。

【算法描述】

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
	}
	
}

后插法创建单链表

后插法是通过将新结点逐个插入到链表尾部来创建链表,同前插法一样,每次申请一个新结点,读入数据元素值,不同的是,为了使新结点能插入到表尾,需增加一个尾指针r指向链表的尾结点。

【算法步骤】
1、创建一个只有头结点的空链表
2、尾指针初始化,指向头结点。
3、根据创建链表包括的元素个数n,循环n次执行以下操作:

1)生成一个新结点p
2)输入元素值赋给新结点
p的数据域。
3)将新结点 *p插入到尾结点 *r之后;
4)尾指针r指向新的尾结点 *p;

后插法的创建过程,读入数据的顺序和线性表中的逻辑顺序是相同的,读入1、2、3、4则输出顺序1、2、3、4。

【算法描述】

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

	}
}

单链表的整表删除

【算法步骤】
1、声明结点p和q.
2、将第一个结点赋值给p
3、循环:将下一个结点赋给q,释放p,将q的值赋给p

Status ClearList(LinkList &L)
{
	LNode *p,*q;
	p=L->next;	//p指向第一个结点
	while(p)	//未到表尾
	{
		q=p->next;
		delete p;
		p=q;
	}
	L->next = NULL;		//头结点指针域为空
	return OK;
	
}

递归遍历单链表

【算法步骤】
1、若p为NULL,递归结束返回
2、否则输出p->data,p指向后继结点继续递归

void TraverseList(LinkList p)
{
	if(p)
	{
		cout<<p->data<<endl;
		TraverseList(p->next);
	}
}

单链表的实现:单链表的实现

格式、内容参考:

1、博客:https://blog.csdn.net/lesileqin/article/details/88088051
2、《数据结构》 严蔚敏

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值