- 🤖💻👨💻👩💻🌟🚀
🤖🌟 欢迎降临张有志的未来科技实验室🤖🌟
专栏:数据结构
👨💻👩💻 先赞后看,已成习惯👨💻👩💻
👨💻👩💻 创作不易,多多支持👨💻👩💻
🚀 启动创新引擎,揭秘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;
}
💻总结
链表作为数据结构的璀璨明珠,其灵活性与高效性在处理动态数据集时大放异彩,克服了顺序表的固有局限。通过本文的解析与实战演练,相信你已掌握了链表的精髓,不论是基础的单链表操作,还是向更复杂结构的迈进,都能游刃有余。
在未来编程与算法优化的征途中,愿链表成为你解决问题的强大利器,引领你在数据结构的王国里不断探索与创新。
🚀✨致此,旅程虽暂告一段落,但编程的世界无限宽广,愿我们都能在代码的海洋里乘风破浪,探索不息。✨🚀