链表的结构及其特点

本文详细介绍了单链表的结构,包括物理和逻辑结构,以及链表的初始化、插入(头插、尾插、指定位置)、删除(头删、尾删)和查找/修改操作。同时讨论了链表在内存中的特点及其优点和缺点。
摘要由CSDN通过智能技术生成

一、链表的结构

       线性表的链式存储又称单链表,它是指通过一组任意的存储单元来存储线性表中的数据元素。为了建立数据元素之间的线性关系,对每个链表节点,除存放元素自身的信息外,还需要存放一个指向其后继节点的指针。单链表的节点类型定义如下:

typedef struct LINKLIST
{
	int data;//存放数据
	struct LINKLIST* next;//存放下一节点的指针
}link;

        对于单链表的结构,主要分为物理结构和逻辑结构。

1.1物理结构

        在物理上,每一个单链表节点在内存中都是随机的,并不像顺序表那样是连续存储的,两两链表之间是通过指针域存放的指针来寻找下一节点的位置。

        链表的的本质就是结构体,结构体内分别有data数据域和next下一节点的指针域,其中data中存放的是链表中所存放的数据,next指针所指向的是下一个节点的指针。

        所以在物理上,链表不是连续的,彼此之间的联系是通过指针维系的,本质上是一个结构体,结构体内存放着数据和下一节点的指针。

1.2逻辑结构

        在逻辑上,我们为了更好的去理解链表,所以会有像下方图片中所描绘的逻辑结构图:

        如图所示,在数据域中,data中存储着数据,指针域则是指向下一个节点,如果最后一个链表后方没有了链表,则它的指针置为空,如此一来,我们就能直观的理解链表的结构了。

二、单链表的相关操作

2.1初始化单链表

        链表的初始化,需要我们将链表的指针即数据全部置为空,也可以将数据域置为你要的数据,将指针域置为空,如下图所示:

代码实现如下:

//初始化链表
void LinkListInit(link* pf)
{
	assert(pf);
	pf->data = 4;
	pf->next = NULL;
}


int main()
{
	link pf;
	LinkListInit(&pf);
	return 0;
}
2.2单链表的插入

        单链表的插入分为多种,有头插、尾插、还有给定位置插入,这三种插入所要对指针的改动均不相同,需要在不影响原来单链表的相对位置下将新的节点插入其中。

2.2.1头插法

        头插法顾名思义就是在单链表的头部插入数据,从而使插入的数据成为单链表的表头,位居第一的位置。

        头插法的思想是直接将新的节点的next指针指向原来单链表的头部,然后再将单链表的表头更改为新的节点上,如此一来就可以将节点顺利插入到表头位置了,但是需要注意的是,如果是在无头节点的情况下,想要更改内存中头节点的位置,必须使用二级指针进行传参,方可达到改变头节点位置的效果。但是如果在有头节点的情况下,因为不论是单链表的增、删、查、改,都只需调动一下指针即可,无需调用二级指针。

        对于无头节点要调用二级指针的问题,不明白的话可以参考一下两个数据的交换函数,对于形参来说,它只是实参的一份临时拷贝,并不能在本质上解决问题。

如上图片,要使用头插法插入节点,其步骤图如下:

代码如下:

//建立节点
link* CreatNode(int n)
{
	link* pf = (link*)malloc(sizeof(link));
	assert(pf);
	pf->data = n;
	pf->next = NULL;
	return pf;
}


//头插
void LinkListPushHead(link** head, int n)
{
	assert(*head);
	link* phead = CreatNode(n);//建立新节点
	phead->next = *head;//将新节点的next指针指向原先的头节点
	*head = phead;//更新头节点指针
}

int main()
{
	link pf;
	link* pt = &pf;
	link** ph = &pt;
	LinkListInit(pt);//初始化单链表
	LinkListPushHead(ph, 3);//头插
	return 0;
}
2.2.2尾插

        相比于头插,尾插不需要移动头节点的指针,只需在单链表尾部直接插入数据即可,将原先单链表尾部的next指针指向新建的节点即可,不需要传二级指针。

        但在插入之前要找到链表的表尾位置,所以要进行一趟从前往后的遍历。

尾插逻辑图如下:

代码如下:

//尾插
void LinkListPushTail(link* pf, int n)
{
	assert(pf);
	link* tail = CreatNode(n);
	while (pf->next)
		pf = pf->next;
	pf->next = tail;
}


int main()
{
	link pf;
	link* pt = &pf;
	link** ph = &pt;
	LinkListInit(pt);//初始化
	LinkListPushHead(ph, 3);//头插
	LinkListPushTail(pt, 5);//尾插
	return 0;
}
2.2.3指定节点前后插入

        如果说要求在节点数据为4之前插入一个值为3的节点,我们首先要找到值为4节点的前一个节点,然后再改变前一个节点和新节点的指针,这样就能完成插入操作。(以第一个出现的4为例)

具体逻辑操作图如下所示:

 代码如下:

//指定节点之前插入
void AppointPush(link** pt, int n, int m)//在n之前插入值为m的新节点
{
	assert(*pt);
	//如果头节点的数据为n则是进行头插
	if ((*pt)->data == n)
	{
		link* newnode = CreatNode(m);
		newnode->next = *pt;
		*pt = newnode;
		return;
	}
	link* phead = *pt;
	while (phead->next->data != n)
	{
		phead = phead->next;
	}
	link* newnode = CreatNode(m);
	newnode->next = phead->next;
	phead->next = newnode;
}


int main()
{
	link pf;
	link* pt = &pf;
	link** ph = &pt;
	LinkListInit(pt);//初始化
	LinkListPushHead(ph, 3);//头插
	LinkListPushTail(pt, 5);//尾插
	AppointPush(ph,4, 3);//在4之前插入值为3的新节点
	return 0;
}

        目前经过初始化、头插、尾插、指定位置插入,链表中的节点为3-> 3-> 4-> 5-> NULL。

           

2.3单链表的删除

        单链表的删除同插入一样,也大致分为头删、尾删、指定位置删除。

2.3.1头删

        头删就是对表头节点进行删除,但是删除时必须要注意不能使后面的节点断开或丢失指针,必须在删除前先将头指针向后移动,再进行删除操作。

        与头插操作一样,头删也需要传入二级指针进行头节点指针的改变。

代码如下:

//头删
void LinkListDeleteHead(link** pt)
{
	assert(*pt);
	if ((*pt)->next == NULL)//如果单链表中只有一个节点,则直接释放,头指针置空
	{
		free(*pt);
		*pt = NULL;
		return;
	}
	link* phead = *pt;//如果不只有一个节点,头节点先行赋给新节点,再向后移动一位
	*pt = (*pt)->next;
	free(phead);
	phead = NULL;
}


int main()
{
	link pf;
	link* pt = &pf;
	link** ph = &pt;
	LinkListInit(pt);//初始化
	LinkListPushHead(ph, 3);//头插
	LinkListPushTail(pt, 5);//尾插
	AppointPush(ph,4, 3);//指定位置插入
	LinkListDeleteHead(ph);//头删
	PrintNode(pt);
	return 0;
}
2.3.2尾删

        尾删的操作十分简单,如果单链表中只有一个节点,直接删除释放即可,如果不止一个,则要从前往后遍历,找到倒数第二个节点,然后删除尾节点。

逻辑图如下所示:

代码如下:

//尾删
void LinkListDeleteTail(link* pf)
{
	assert(pf);
	if (pf->next == NULL)
	{
		free(pf);
		pf = NULL;
		return;
	}
	while (pf->next->next)
	{
		pf = pf->next;
	}
	free(pf->next);
	pf->next = NULL;
}


int main()
{
	link pf;
	link* pt = &pf;
	link** ph = &pt;
	LinkListInit(pt);//初始化
	LinkListPushHead(ph, 3);//头插
	LinkListPushTail(pt, 5);//尾插
	AppointPush(ph,4, 3);//指定位置插入
	//LinkListDeleteHead(ph);//头删
	LinkListDeleteTail(pt);//尾删
	PrintNode(pt);
	return 0;
}
2.4单链表的查找、改正

        对于查改,只需遍历链表即可,如若找到该节点则操作,找不到就显示false。

三、链表的特点

        链表在内存中是分散存储的,因此他适合较为分散且密度小的数据。

优点:

        (1)对数据的插入和删除操作较为方便,无需移动大量的数据,只需改变指针即可。

        (2)当遇到难以估计线性表长度或存储规模时,链表较为符合。

缺点:

        (1)在内存中占用空间大,且相比于顺序表来说,它的读取速度要小于顺序表。

        (2)不能随机访问数据,要访问某个数据,只能从头节点依次向后遍历。

        无论是对于顺序表还是链表,我们在实际中应当以具体的环境和情况为主,两种结构均有优劣,只有在深刻了解顺序表和链表的操作后,才能理解它们各自的优缺点。

  • 18
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值