2026/7/23 数据结构2 (单向链表)

一、链表的原理与应用

顺序表的优点是:由于顺序表数据元素的内存地址都是连续的,所以可以实现随机访问,而且不需要多余的信息来描述相关的数据,所以存储密度高。

顺序表的缺点是:顺序表的数据在进行增删的时候,需要移动成片的内存,另外,当数据元素的数量较多的时候,需要申请一块较大的连续的内存,同时当数据元素的数量的改变比较剧烈,顺序表不灵活。

可以利用链式存储的线性表存储数据,链式存储指的是采用离散的内存单元来存储数据元素,用户需要使用某种方式把所有的数据元素连接起来,这样就可以变为链式线性表,简称为链表,链表可以高效地使用碎片化内存。由于链表中的每个数据元素的地址是不固定的,所以每个数据元素都应该使用一个指针指向直接后继的内存地址,当然最后一个数据元素没有直接后继,所以最后一个数据元素指向NULL即可,作为用户只需要知道第一个数据元素的内存地址,就可以访问后继元素了。即线性表中每一个数据元素除了存储自身数据之外,还需要额外存储直接后继的地址,所以链表中的每一个数据元素都是由两部分组成:存储自身数据的部分被称为数据域,存储直接后继地址的部分被称为指针域,数据域和指针域组成的数据元素被称为结点(Node)。

一般把链表分为两种:一种是不带头结点的链表,一种是带头结点的链表,头结点指的是管理结构体,只不过头结点只存储第一个元素的内存地址,头结点并不存储有效数据,头结点的意义只是为了方便管理链表。可以知道,头指针是必须的,因为通过头指针才可以访问链表的元素,头结点是可选的,只是为了方便管理链表而已。

头结点:是不存储有效数据的,只存储第一个数据元素的地址,头指针只指向头结点。

首结点:是存储有效数据的,也存储直接后继的内存地址,首结点就是第一个结点,首结点是唯一一个只指向别的结点,不被别的结点指向的结点。

尾结点:是存储有效数据的,尾结点就是链表的最后一个结点,所以尾结点中存储的地址一般指向NULL,尾结点是唯一一个只被别的结点指向,不能指向别的结点的结点。

根据链表的结点的指针域的数量以及根据链表的首尾是否相连,把链式线性表分为以下几种:单向链表、单向循环链表、双向链表、双向循环链表、内核链表。这几种链表的使用规则差不多,只不过指针域数量不同。

单向链表的内部每一个结点都保存了一个地址,每个地址都是逻辑上相邻的下一个结点的地址,只不过末尾结点的指针指向NULL。

对于单向链表,编写如下c语言代码实现对其增删改查等操作并验证

/********************************************************************************************************
 * @File Name		:LinkedList.c
 * @Author			:bbzhu04@gmail.com
 * @Date			:2026/07/23
 * @Function		:构造函数实现链表元素的增删改查等操作
 * @Notes			:为了提高可移植性,所以链表中元素的数据类型为DataType_t,用户可以根据实际情况修改链表中元素的类型
 * 				 	 另外,为了方便管理链表,所以用户设计LList_t结构体,该结构体中包含两个成员:结点的数据域和指针域
* Copyright (c)  2025-2026   bbzhu04@gmail.com   All right Reserved
* ******************************************************************************************************/

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>

//指的是单向链表中的结点有效数据类型,用户可以根据需要进行修改
typedef int  DataType_t;

//构造链表的结点,链表中所有结点的数据类型应该是相同的(与顺序表不同,链表中每个结点都是一个独立的结构体,而顺序表中所有元素都是一个连续的内存空间)
typedef struct LinkedList
{
	DataType_t  		 data; //结点的数据域
	struct LinkedList	*next; //结点的指针域

}LList_t;

/********************************************************************************************************
 * @Function Name		:LList_Create
 * @Functionality		:创建链表并对链表进行初始化
 * @Function Parameters	:None
 * @Return result		:指向链表管理结构体的指针
 * @Notes				:None
 * @Author				:bbzhu04@gmail.com
 * @Revision History		:None
 * @Function version		:V1.0
 * *****************************************************************************************************/
LList_t * LList_Create(void)
{
	//1.创建一个头结点并对头结点申请内存
	LList_t *Head = (LList_t *)calloc(1,sizeof(LList_t));
	if (NULL == Head) 
	{
		perror("Calloc memory for Head is Failed");
		exit(-1);
	}

	//2.对头结点进行初始化,头结点是不存储有效内容的!!!
	Head->next = NULL;

	//3.把头结点的地址返回即可
	return Head;
}

/********************************************************************************************************
 * @Function Name		:LList_NewNode
 * @Functionality		:创建新的结点,并对新结点进行初始化(数据域 + 指针域)
 * @Function Parameters	:
 *                          @data:要插入到新结点中的数据
 * @Return result		:指向链表新结点的指针
 * @Notes				:None
 * @Author				:bbzhu04@gmail.com
 * @Revision History		:None
 * @Function version		:V1.0
 * *****************************************************************************************************/
LList_t * LList_NewNode(DataType_t data)
{
	//1.创建一个新结点并对新结点申请内存
	LList_t *New = (LList_t *)calloc(1,sizeof(LList_t));
	if (NULL == New)
	{
		perror("Calloc memory for NewNode is Failed");
		return NULL;
	}

	//2.对新结点的数据域和指针域进行初始化
	New->data = data;
	New->next = NULL;

	return New;
}

/********************************************************************************************************
 * @Function Name		:LList_HeadInsert
 * @Functionality		:在链表的头部插入新的结点
 * @Function Parameters	:
 *                          @Head:指向链表头结点的指针
 *                          @data:要插入到新结点中的数据
 * @Return result		:bool型变量,插入成功时返回true,否则返回false
 * @Notes				:None
 * @Author				:bbzhu04@gmail.com
 * @Revision History		:None
 * @Function version		:V1.0
 * *****************************************************************************************************/
bool LList_HeadInsert(LList_t *Head,DataType_t data)
{
	//1.创建新的结点,并对新结点进行初始化
	LList_t *New = LList_NewNode(data);
	if (NULL == New)
	{
		printf("can not insert new node\n");
		return false;
	}

	//2.判断链表是否为空,如果为空,则直接插入即可
	if (NULL == Head->next)
	{
		Head->next = New;
		return true;
	}

	//3.如果链表为非空,则把新结点插入到链表的头部
	New->next  = Head->next;
	Head->next = New;

	return true;
}

/********************************************************************************************************
 * @Function Name		:LList_TailInsert
 * @Functionality		:在链表的尾部插入新的结点
 * @Function Parameters	:
 *                          @Head:指向链表头结点的指针
 *                          @data:要插入到新结点中的数据
 * @Return result		:bool型变量,插入成功时返回true,否则返回false
 * @Notes				:None
 * @Author				:bbzhu04@gmail.com
 * @Revision History		:None
 * @Function version		:V1.0
 * *****************************************************************************************************/
bool LList_TailInsert(LList_t *Head,DataType_t data)
{
	//1.创建新的结点,并对新结点进行初始化
	LList_t *New = LList_NewNode(data);
	if (NULL == New)
	{
		printf("can not insert new node\n");
		return false;
	}

	//2.找到链表的尾结点
	LList_t *Phead = Head;//对链表的头结点的地址进行备份
	while (Phead->next)//指针域不为空,则说明当前结点不是尾结点
	{
		Phead = Phead->next;
	}

	//3.将新结点插入到链表的尾部
	Phead->next = New;

	return true;
}
    
/********************************************************************************************************
 * @Function Name		:LList_DestInsert
 * @Functionality		:在链表中指定值的后面插入新的结点
 * @Function Parameters	:
 *                          @Head:指向链表头结点的指针
 *                          @destval:目标结点的值
 *                          @data:要插入到新结点中的数据
 * @Return result		:bool型变量,插入成功时返回true,否则返回false
 * @Notes				:None
 * @Author				:bbzhu04@gmail.com
 * @Revision History		:None
 * @Function version		:V1.0
 * *****************************************************************************************************/   
bool LList_DestInsert(LList_t *Head, DataType_t destval, DataType_t data)
{
    // 1. 创建新结点
    LList_t *New = LList_NewNode(data);
    if (NULL == New)
    {
        printf("can not insert new node\n");
        return false;
    }

    // 2. 找到目标结点(Phead 指向目标结点本身)
    LList_t *Phead = Head->next;  // 从首结点开始找
    while (Phead && Phead->data != destval)
    {
        Phead = Phead->next;
    }

    // 3. 如果找到了目标结点,在其后面插入新结点
    if (Phead)
    {
        New->next = Phead->next;
        Phead->next = New;
        return true;
    }

    printf("dest node not found\n");
	free(New);
    return false;
}

/********************************************************************************************************
 * @Function Name		:LList_HeadDel
 * @Functionality		:删除链表首结点
 * @Function Parameters	:
 *                          @Head:指向链表头结点的指针
 * @Return result		:void
 * @Notes				:None
 * @Author				:bbzhu04@gmail.com
 * @Revision History		:None
 * @Function version		:V1.0
 * *****************************************************************************************************/  
void LList_HeadDel(LList_t *Head)
{
	
	//1.判断链表是否为空,如果为空,则直接返回即可
	if (NULL == Head->next)
	{
		printf("list is empty\n");
		return;
	}

	//2.删除链表的首结点
	LList_t *Phead = Head->next;//对链表的首结点的地址进行备份
	Head->next = Phead->next;
	Phead->next = NULL;
	free(Phead);

}

/********************************************************************************************************
 * @Function Name		:LList_DelNode
 * @Functionality		:删除链表中指定的元素(第一个匹配的元素)
 * @Function Parameters	:
 *                          @Head:指向链表头结点的指针
 *                          @data:要删除的元素的值
 * @Return result		:bool型变量,删除成功时返回true,否则返回false
 * @Notes				:None
 * @Author				:bbzhu04@gmail.com
 * @Revision History		:None
 * @Function version		:V1.0
 * *****************************************************************************************************/  
bool LList_DelNode(LList_t *Head, DataType_t data)
{
	//1.找到要删除的结点的前驱结点
	LList_t *Phead = Head;//对链表的头结点的地址进行备份
	while (Phead->next && Phead->next->data != data)//指针域不为空,则说明当前结点不是尾结点,并且当前结点的直接后继的值不等于要删除的值
	{
		Phead = Phead->next;
	}

	//2.如果找到了要删除的结点,则删除它
	if (Phead->next)//指针域不为空,则说明当前结点不是尾结点,并且当前结点的直接后继的值等于要删除的值
	{
		LList_t *Temp = Phead->next;
		Phead->next = Temp->next;
		Temp->next = NULL;
		free(Temp);
		return true;
	}

	printf("node not found\n");
	return false;
}


/********************************************************************************************************
 * @Function Name		:LList_Print
 * @Functionality		:遍历输出链表的元素
 * @Function Parameters	:
 *                          @Head:指向链表头结点的指针
 * @Return result		:void
 * @Notes				:None
 * @Author				:bbzhu04@gmail.com
 * @Revision History		:None
 * @Function version		:V1.0
 * *****************************************************************************************************/  
void LList_Print(LList_t *Head)
{
	//对链表的头文件的地址进行备份
	LList_t *Phead = Head;
	
	while(Phead->next)//指针域不为空,则说明当前结点不是尾结点
	{
		//把头的直接后继作为新的头结点
		Phead = Phead->next;

		//输出头结点的直接后继的数据域
		printf("data = %d\n",Phead->data);
	}

}




int main(int argc, char const *argv[])
{
	LList_t *	LList = LList_Create();
	//验证向头部插入结点
	LList_HeadInsert(LList, 10);  //10
	LList_HeadInsert(LList, 20);  //20 10
	LList_Print(LList);
	printf("\n");

	//验证向尾部插入结点
	LList_TailInsert(LList, 30);	//20 10 30
	LList_Print(LList);
	printf("\n");

	//验证在指定结点后插入结点
	LList_DestInsert(LList, 20, 25); //20 25 10 30
	LList_Print(LList);
	printf("\n");

	//验证删除首结点
	LList_HeadDel(LList); //25 10 30
	LList_Print(LList);	
	printf("\n");

	//验证删除指定结点
	LList_DelNode(LList, 10); //25 30	
	LList_DelNode(LList, 30); //25 
	
	LList_TailInsert(LList, 30);//25 30

	LList_DelNode(LList, 25); //30
	LList_Print(LList);
	printf("\n");



	return 0;
}

输出结果:image

posted @ 2026-07-23 03:02  !城北徐公.!  阅读(6)  评论(0)    收藏  举报