C语言实现单链表操作详解:从基础到进阶(动图版)(附源码)

  • 🤖💻👨‍💻👩‍💻🌟🚀

🤖🌟 欢迎降临张有志的未来科技实验室🤖🌟

专栏:数据结构

👨‍💻👩‍💻 先赞后看,已成习惯👨‍💻👩‍💻

👨‍💻👩‍💻 创作不易,多多支持👨‍💻👩‍💻

🚀 启动创新引擎,揭秘C语言的密码🚀


🧠目录

✈️前言

📚对比

单链表的结构剖析

🚀准备

🔑关键操作的实现

⭕️创建与销毁

节点创建:使用malloc动态分配内存,初始化节点数据和指针。

链表销毁:遍历链表,释放每个节点的内存,确保资源的回收。

⭕️接口实现

头插:新建节点并作为头节点

头删:将原有头节点删除并让下一节点作为头节点

尾插:创建节点并插在链表尾部

尾删:删除最后一个节点,思路与尾插类似

查找:通过遍历链表,逐一比对节点数据,实现对特定值的搜索。

指定位置插入:进行查找节点并修改

指定位置删除

打印:将链表中的内容打印到控制台

🔥实战演练:功能实现示例🔥

 SList.c 

 SList.h 

main.c

💻总结


✈️前言

        在数据结构的广阔领域中,线性表是一片丰富多彩的天地,其中包括了数组、顺序表、链表和队列等诸多形态顺序表作为数组的直接延伸,其物理存储的连续性虽保证了访问的便捷,但也暴露出在动态数据管理上的短板——空间利用率低下及频繁扩容导致的效率瓶颈。

        正是基于此,链表作为一种创新的数据结构设计,凸显了其不可替代的价值。那么,我将带领大家探索链表的奥秘

📚对比

特性顺序表链表
存储结构物理存储连续,数据紧密排列物理存储非连续,节点通过指针相连,逻辑上有序
空间利用率若预分配过大,可能导致空间浪费;过小则频繁扩容按需分配,每个节点独立分配,空间利用灵活
访问速度支持随机访问,通过索引快速定位数据不支持随机访问,访问数据需从头节点遍历或从特定节点开始遍历
插入/删除插入和删除元素需要移动大量数据,效率低插入和删除只需改变指针,效率较高
扩容需要重新分配内存,可能涉及数据搬迁动态分配,易于扩展,不会造成数据迁移
实现复杂度实现相对简单,内存管理较为直接实现较为复杂,需手动管理指针,防止内存泄漏

在明确了链表与顺序表各自的优缺点后,接下来,我们将深入探究链表的具体实现与应用,尤其聚焦于单链表这一基础而又至关重要的链表形式,来一场从理论到实践的深度探索之旅。


单链表的结构剖析

单链表由一系列节点组成,每个节点包含两部分:一部分用于存储数据,另一部分存储指向下一个节点的指针。

这种结构允许我们在不连续的内存空间中灵活地组织数据,每个节点就像是数据海洋中的浮标,通过指针的指引,形成一条逻辑上的数据链。

所以我们可以这么定义链表中的节点

typedef int SLData;

typedef struct SList
{
	SLData a;//数据域
	struct SList* next;//指针域
}SList;

🚀准备

 SList.c :用于存放函数实现

 SList.h :用于声明函数和必要的头文件、结构体定义

 main.c :测试代码

🔑关键操作的实现

void SL_tail_stick(SList** p,SLData x);//尾插
void STL_pop_back(SList** p);//尾删

void SL_Front_stick(SList** phead, SLData x);//头插
void SL_Front_pop(SList** phead);//头删

SList* SL_find(SList** phead, int pos);//查找

void stick(SList** phead, int pos,SLData);//插入

void print(SList* phead);//打印

void pos_revise(SList** phead, int pos, SLData x);//指定位置修改

void pos_pop(SList** phead, int pos);//指定位置删除 

void destroy(SList** phead);//销毁单链表

⭕️创建与销毁

  • 节点创建:使用malloc动态分配内存,初始化节点数据和指针。

SList* buynode(SLData x)
{
	SList* newnode = (SList*)calloc(1, sizeof(SList));//尽量用calloc,该函数可以将申请的空间初                    
                                                      // 始化为0
	assert(newnode);//检测创建是否成功

	newnode->a = x;
	return newnode;
}
  • 链表销毁:遍历链表,释放每个节点的内存,确保资源的回收。

void destroy(SList** phead)//销毁单链表
{
	assert(phead);       //判断是否为空

	SList* cur = *phead; //指向头节点
	SList* prev = NULL;  //指向cur上一个节点

	while (cur)          
	{
		prev = cur;      //记录上一个节点地址
		cur = cur->next; //移至下一个节点
		free(prev);      //释放节点
	}

	*phead = NULL;       //不要忘记置为NULL
}

⭕️接口实现

  • 头插:新建节点并作为头节点

💡tips:需注意传址操作而非传值操作

void SL_Front_stick(SList**phead,SLData x)//头插
{
	assert(*phead);

	SList* pTemp = buynode(x);

	pTemp->next = *phead; //让新结点指向原本的头节点
	*phead=pTemp; //让原本头结点指针指向新节点
}
  • 头删:将原有头节点删除并让下一节点作为头节点

void SL_Front_pop(SList** phead)//头删
{
	assert(phead&&*phead);

	SList* cur= *phead;
	*phead = (*phead)->next;
	free(cur);
}
  • 尾插:创建节点并插在链表尾部

void SL_tail_stick(SList**phead,SLData x)//尾插
{
	assert(p);

	SList* newnode = buynode(x);
	newnode->next = NULL;
	SList* p = *phead; //将原有链表复制一遍进行操作,不会修改原有数据

	while (p->next) //这一步挺妙的,正好指向了最后一个节点
	{
		p=p->next; //千万不能用*p,这样导致下一个p将上一个覆盖掉,修改原有数据
	}

	p->next = newnode;
}
  • 尾删:删除最后一个节点,思路与尾插类似

void STL_pop_back(SList** p)//尾删
{
	assert(p && *p);
	SList* cur = *p;
	SList* prev = NULL;

	while (cur->next)//让指针停留在最后一个结点
	{
		prev = cur;//记录cur前一个结点
		cur = cur->next;
	}

	prev->next = NULL;

	free(cur);
}
  • 查找:通过遍历链表,逐一比对节点数据,实现对特定值的搜索。

SList* SL_find(SList** phead, int pos)
{
	assert(phead && *phead);

	SList* p = *phead;
	while (pos--)
	{
		p=p->next;
	}
	return p;
}
  • 指定位置插入:进行查找节点并修改

void stick(SList** phead,int pos,SLData x)//指定位置插入
{
	assert(phead && *phead);

	if (pos == 1)//如果位置为1,则头插
	{
        SL_Front_stick(phead, x);
        return;
    }

	SList* newnode = buynode(x);
	SList* cur = *phead;
	SList* prev = NULL;

	while (pos--)
	{
		prev = cur;
		cur = cur->next;
	}

	prev->next = newnode;
	newnode->next = cur;
}
  • 指定位置删除

void pos_pop(SList** phead, int pos)//指定位置删除 
{
	assert(phead && *phead);

	SList* cur = *phead;
	SList* prev = NULL;

	if (pos == 0)
	{
		SL_Front_pop(phead);
	}

	while (pos--&&cur->next)
	{
		prev = cur;
		cur = cur->next;
	}
	
	prev->next = cur->next;
	free(cur);
}
  • 打印:将链表中的内容打印到控制台

void print(SList* phead)
{
	while (phead)
	{
		printf("%d->", phead->a);
		phead = phead->next;
	}
	printf("null\n");
}

🔥实战演练:功能实现示例🔥

 SList.c 

#include"SList.h"

SList* buynode(SLData x)
{
	SList* newnode = (SList*)calloc(1, sizeof(SList));
	assert(newnode);
	newnode->a = x;
	return newnode;
}

void SL_tail_stick(SList**p,SLData x)//尾插
{
	assert(p);

	SList* newnode = buynode(x);
	newnode->next = NULL;
	SList* cur = *p; //将原有链表复制一遍进行操作,不会修改原有数据

	while (cur->next) //这一步挺妙的,正好指向了最后一个节点
	{
		cur=cur->next; //千万不能用*p,这样导致下一个p将上一个覆盖掉,修改原有数据
	}

	cur->next = newnode;
}


void STL_pop_back(SList** p)//尾删
{
	assert(p && *p);
	SList* cur = *p;
	SList* prev = NULL;

	while (cur->next)//让指针停留在最后一个结点
	{
		prev = cur;//记录cur前一个结点
		cur = cur->next;
	}

	prev->next = NULL;

	free(cur);
}



void SL_Front_stick(SList**phead,SLData x)//头插
{
	assert(*phead);

	SList* newnode = buynode(x);

	newnode->next = *phead; //让新结点指向原本的头节点
	*phead=newnode; //让原本头结点指针指向新节点
}

void SL_Front_pop(SList** phead)//头删
{
	assert(phead&&*phead);

	SList* cur= *phead;
	*phead = (*phead)->next;
	free(cur);
}

SList* SL_find(SList** phead, int pos)
{
	assert(phead && *phead);
	SList* cur = *phead;
	while (pos--)
	{
		cur=cur->next;
	}
	return cur;
}

void stick(SList** phead,int pos,SLData x)//指定位置插入
{
	assert(phead && *phead);

	if (pos == 1)
		SL_Front_stick(phead, x);

	SList* newnode = buynode(x);
	SList* cur = *phead;
	SList* prev = NULL;

	while (pos--)
	{
		prev = cur;
		cur = cur->next;
	}

	prev->next = newnode;
	newnode->next = cur;
}

void print(SList* phead)
{
	while (phead)
	{
		printf("%d->", phead->a);
		phead = phead->next;
	}
	printf("null\n");
}

//还差:修改指定位置、指定位置删除、销毁单链表

void pos_revise(SList** phead, int pos,SLData x)//指定位置修改
{
	assert(phead && *phead);

	SList* cur = *phead;

	while (pos--)
	{
		cur = cur->next;
	}
	cur->a = x;
}

void pos_pop(SList** phead, int pos)//指定位置删除 
{
	assert(phead && *phead);

	SList* cur = *phead;
	SList* prev = NULL;

	if (pos == 0)
	{
		SL_Front_pop(phead);
	}

	while (pos--&&cur->next)
	{
		prev = cur;
		cur = cur->next;
	}
	
	prev->next = cur->next;
	free(cur);
}

void destroy(SList** phead)//销毁单链表
{
	assert(phead);
	SList* cur = *phead;
	SList* prev = NULL;
	while (cur)
	{
		prev = cur;
		cur = cur->next;
		free(prev);
	}
	*phead = NULL;
}


//void destroy(SList** phead) {
//	assert(phead != NULL && *phead != NULL); // 确保头指针及其指向的地址不为空
//
//	SList* cur = *phead;
//	SList* prev = NULL;
//	do {
//		prev = cur;
//		cur = cur->next;
//		free(prev);
//	} while (cur != NULL);
//
//	*phead = NULL; // 将头指针设置为NULL,避免悬挂指针
//}

 SList.h 

#pragma once

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>



typedef int SLData;

typedef struct SList
{
	SLData a;

	struct SList* next;
}SList;



void SL_tail_stick(SList** p,SLData x);//尾插
void STL_pop_back(SList** p);//尾删

void SL_Front_stick(SList** phead, SLData x);//头插
void SL_Front_pop(SList** phead);//头删

SList* SL_find(SList** phead, int pos);//查找

void stick(SList** phead, int pos,SLData);//插入

void print(SList* phead);//打印

void pos_revise(SList** phead, int pos, SLData x);//指定位置修改

void pos_pop(SList** phead, int pos);//指定位置删除 

void destroy(SList** phead);//销毁单链表

main.c

#include <stdio.h>
#include <stdlib.h>
#include "SList.h" // 确保SList.h包含正确的结构体定义

void test01()
{
    SList* node1 = (SList*)calloc(1,sizeof(SList));
    node1->a = 0;

    SList* node2 = (SList*)calloc(1,sizeof(SList));
    node2->a = 1;

    SList* node3 = (SList*)calloc(1,sizeof(SList));
    node3->a = 2;

    SList* node4 = (SList*)calloc(1,sizeof(SList));
    node4->a = 3;

    node1->next = node2;
    node2->next = node3;
    node3->next = node4;

    SList* phead = node1;

    
    SL_tail_stick(&phead,5);//尾插
    SL_Front_stick(&phead, -1);//头插
    STL_pop_back(&phead);//尾删
    SL_Front_pop(&phead);//头删
    stick(&phead, 2,20);//指定位置插入
    pos_revise(&phead, 2, 15);//指定位置修改
    pos_pop(&phead, 2);//指定位置删除 

    print(phead);//打印

    destroy(&phead);

    printf("\n");
}

int main()
{
    test01();

    return 0;
}

💻总结

        链表作为数据结构的璀璨明珠,其灵活性高效性在处理动态数据集时大放异彩,克服了顺序表的固有局限。通过本文的解析与实战演练,相信你已掌握了链表的精髓,不论是基础的单链表操作,还是向更复杂结构的迈进,都能游刃有余。

        在未来编程与算法优化的征途中,愿链表成为你解决问题的强大利器,引领你在数据结构的王国里不断探索与创新。


🚀✨致此,旅程虽暂告一段落,但编程的世界无限宽广,愿我们都能在代码的海洋里乘风破浪,探索不息。✨🚀

  • 27
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值