数据结构 链表 -- C语言实现

news/2025/3/13 21:54:03/文章来源:https://www.cnblogs.com/DSCL-ing/p/18344048

链表

链表的概念

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。

链表的分类

实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:

  1. 单向或者双向

image-20240805205154867

  1. 带头或者不带头

image-20240805205209467

  1. 循环或者非循环

image-20240805205222718

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:

  1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。
  2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都
    是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带
    来很多优势,实现反而简单了。

掌握了这两种链表,其他的就容易推导出来了;

image-20240805205233380

代码实现

无头单向非循环链表

SingleLink.h

#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int SLDataType;typedef struct SListNode//SingleList
{SLDataType data;struct SListNode*next;//自己定义自己类型的指针}SListNode;//SListNode* phead = NULL;//非哨兵卫,只是起始,调用插入函数后变成表头// 要改变传过来的指向第一个节点的指针就传二级
// 不改变传过来的指向第一个节点的指针就传一级
// 只读的函数接口传一级//创建节点
//开辟空间、返回地址:销毁变量,但地址有效
SListNode * CreateSListNode(SLDataType x);//---------------------------一级指针----------////打印链表元素
void PrintSList(SListNode *phead);//计算链表大小
int SizeSList(SListNode *phead);//查找
SListNode *FindSList(SListNode *phead, SLDataType x);//----------------------------------------------////---------------------------二级指针----------////尾插
//判断是否空表:
//是空表->调用CreateNode;
//非空->找尾插入
void PushBackSList(SListNode **phead, SLDataType x);//头插
//不用判断
void PushFrontSList(SListNode **pphead,SLDataType x);//尾删
void PopBackSList(SListNode **pphead);//头删
void PopFrontSList(SListNode **pphead);//释放空间
void DestroySListNode(SListNode**phead);//----------------------------------------------///*
单链表前插必须要知前节点
*///pos位置前插x
//需要配合Find使用
void InsertSList(SListNode **pphead, SListNode *pos, SLDataType x);//pos位置后插x;
//需要配合Find使用;
void InsertAfterSList(SListNode **pphead, SListNode *pos, SLDataType x);//擦除
void EraseSList(SListNode **pphead, SListNode *pos);void SListEraseAfter(SListNode* pos);SListNode* FindElement(SListNode*L, SLDataType x);

SingleLink.c

#define _CRT_SECURE_NO_WARNINGS 1#include"SingleLinkList.h"//断言目的:防传参出错SListNode* CreateSListNode(SLDataType x)
{SListNode* node = (SListNode*)malloc(sizeof(SListNode));//注意不要sizeof(...*),犯病,后面会free错误,其他bug不明显if (node == NULL){perror("malloc fail\n ");exit(-1);}node->data = x;node->next=NULL;//初始化为空return node;
}void PrintSList(SListNode *phead)
{SListNode *cur = phead;//current:当前  ->记忆当前的节点地址while (cur != NULL){printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}int SizeSList(SListNode *phead)
{SListNode*cur = phead;int size = 0;while (cur){size++;cur = cur->next;}return size;
}SListNode *FindSList(SListNode *phead, SLDataType x)
{SListNode *cur = phead;while (cur){if (cur->data == x){return cur;}else{cur = cur->next;}}return NULL;
}SListNode* FindElement(SListNode*L, SLDataType x)
{if (!L){return NULL;}if (L->data == x){return L;}return FindElement(L->next, x);
}//pphead=&phead  //目的要改变phead的值
//*pphead==phead
//**pphead==结构体变量
void PushBackSList(SListNode** pphead, SLDataType x)
{//为什么要有这一步,不能合并的原因:当head==NULL,tail->next对NULL解引用,错误if (*pphead == NULL){SListNode* newnode = CreateSListNode(x);*pphead = newnode;//将newnode的值(尾节点的地址)赋给指针变量head}else{SListNode* tail = *pphead;//用于寻找、记忆尾节点的指针变量while (tail->next != NULL){tail = tail->next;//得到下个节点的地址}SListNode *newnode = CreateSListNode(x);tail->next = newnode;//连接上尾节点,将newnode的值(尾节点的地址)赋给指针域}
}void PushFrontSList(SListNode **pphead, SLDataType x)
{SListNode *newnode = CreateSListNode(x);newnode->next = *pphead;//将新节点链接在头节点前。*pphead = newnode;//将newnode的值(首节点的地址)赋给head,使head成为首节点。
}void PopBackSList(SListNode **pphead)
{//1.头空//2.只有头//3.有多个if (*pphead == NULL)//判断空表{return;}if ((*pphead)->next == NULL)//不能合并,因为找尾节点需要对next解引用{*pphead = NULL;free(*pphead);//释放头节点return;//可以不用return,换成if-else}SListNode *tail, *prev;//previoustail = prev = *pphead;while (tail->next != NULL)//找尾节点{prev = tail;  //先记录当前节点tail = tail->next;//再到下个节点}free(tail);//释放尾节点prev->next = NULL;
}void PopFrontSList(SListNode **pphead)
{//1.头空//2.只有头//3.有多个if (*pphead == NULL){return;}//-------可以合并,将next放到头指针//if ((*pphead)->next == NULL)//{//	*pphead = NULL;//	free(*pphead);//释放头节点//	//return;//可以不用return,换成if-else//}//else//{//--方法一SListNode *cur = *pphead;//记录下头节点地址*pphead = (*pphead)->next;//丢掉头节点:将下一个节点放到头指针free(cur);//释放空间//--方法二//SListNode* next = (*pphead)->next;//记录下后一个节点//free(*pphead);//释放掉头节点//(*pphead) = next;//将下一个节点放到头指针
}void InsertSList(SListNode **pphead, SListNode *pos, SLDataType x)
{SListNode *newnode = CreateSListNode(x);SListNode *prev;prev = *pphead;if (pos == *pphead){//you can also call the Head insertion function  :你也可以使用头插函数newnode->next = *pphead;*pphead = newnode;}else{//--------方法一while (prev->next){if (prev->next == pos){newnode->next = prev->next;prev->next = newnode;return;}prev = prev->next;}printf("Insert position not find\n");//--------方法二/*while(prev-> != pos){prev=prev->next;}newnode->next = prev->next;prev->next = newnode;*/}
}void InsertAfterSList(SListNode **pphead, SListNode *pos, SLDataType x)
{SListNode *newnode = CreateSListNode(x);//方法三:newnode->next = pos->next;pos->next = newnode;//方法一//	SListNode *cur = *pphead;//	while (cur)//	{//		if (cur == pos)//		{//			newnode->next = cur->next;//			cur->next = newnode;//			return ;//		}//		cur = cur->next;//	}//	printf("InsertAfter fail\n");/*方法二while(cur!=pos){cur=cur->next;}newnode->next=cur->next;cur->next=newnode;*/}void EraseSList(SListNode **pphead, SListNode *pos)
{SListNode *prev = *pphead;if (*pphead == pos){*pphead = prev->next;free(prev);}while (prev->next != pos){prev = prev->next;}prev->next = prev->next->next;free(pos);pos = NULL;//没用,但是习惯
}void SListEraseAfter(SListNode* pos)
{assert(pos);assert(pos->next);SListNode* next = pos->next;pos->next = next->next;free(next);next = NULL;
}void DesrtoySList(SListNode **pphead)
{SListNode *cur = *pphead;SListNode *next = cur->next;while (cur){next = cur->next;free(cur);cur = next;}*pphead = NULL;//指针变量要置空
}

带头双向循环链表

DoubleLink.h

#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h>
#include<stdlib.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>#pragma once//有头双向循环链表
//结构复杂,操作反而简单typedef int ListDataType;typedef struct ListNode
{struct ListNode *prev;ListDataType data;struct ListNode *next;
}ListNode;void Print(ListNode *phead);//void ListNode(ListNode **pphead);  //改变头地址---不太合适了
ListNode *InitList(void);          //返回值,返回哨兵卫//保持接口一致性,用一级指针//创建新节点
ListNode *CreateListNode(ListDataType x);//初始化
//创建哨兵卫
//初始化成员
ListNode *InitList(void);//尾插
void PushBackList(ListNode *phead, ListDataType x);//头插
void PushFrontList(ListNode *phead, ListDataType x);//尾删
void PopBackList(ListNode *phead);//头插
void PopFrontList(ListNode *phead);ListNode *FindList(ListNode *phead, ListDataType x);//--------------------带头双向循环链表好处
//pos就是所在节点的地址,不用头指针phead
void InsertList(ListNode *pos, ListDataType x);//用不到phead
//Insert和Erase 效率很高,直接插直接删,不用查找,且包含头尾插删
void EraseList(ListNode *pos);//用不到phead//销毁
void DestroyList(ListNode *phead);size_t ListSize(ListNode *phead);

DoubleLink.c

#define _CRT_SECURE_NO_WARNINGS 1#include"DoubleList.h"void Print(ListNode *phead)
{assert(phead);ListNode *cur = phead->next;while (cur != phead){printf("%d ", cur->data);cur = cur->next;}printf("\n");
}//void ListNode(ListNode *phead)ListNode *InitList(void)
{//ListNode *prev, *tail;//哨兵卫头节点//哨兵卫的特点是不存储数据,由此,可以不初始化,随机值都行ListNode *phead = (ListNode*)malloc(sizeof(ListNode));if (phead == NULL){printf("malloc fail\n ");exit(-1);}phead->prev = phead;phead->next = phead;return phead;
}ListNode *CreateListNode(ListDataType x)
{ListNode *newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL){perror("malloc fail\n");exit(-1);}newnode->data = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}void PushBackList(ListNode *phead, ListDataType x)
{ListNode *newnode = CreateListNode(x);ListNode *tail = phead->prev;//这样定义更加灵活,可以互换位置tail->next = newnode;newnode->prev = tail;phead->prev = newnode;newnode->next = phead;//phead->prev->next = newnode;//尾节点指向新节点//newnode->prev = phead->prev;//新节点指向尾节点//newnode->next = phead;//新节点指向头节点//phead->prev = newnode;//头节点指向新节点//Insert(phead, x);
}void PushFrontList(ListNode *phead,ListDataType x)
{ListNode *newnode = CreateListNode(x);ListNode *next = phead->next;//这样定义更加灵活,可以互换位置newnode->next = next;//下个节点的位置给新节点next->prev = newnode;//新节点地址给下节点phead->next = newnode;newnode->prev = phead;//Insert(phead->next, x);
}void PopBackList(ListNode *phead)
{ListNode *tail = phead->prev;ListNode *tailPrev = tail->prev;tailPrev->next = phead;phead->prev = tailPrev;free(tail);tail = NULL;//EraseList(phead->prev);
}void PopFrontList(ListNode *phead)
{ListNode *next = phead->next;ListNode *nextNext = next->next;free(next);phead->next = nextNext;nextNext->prev = phead;next = NULL;//EraseList(phead->next);
}ListNode *FindList(ListNode *phead, ListDataType x)
{assert(phead);ListNode *pos = phead->next;while (pos!=phead){if (pos->next = x){return pos;}pos = pos->next;}printf("not find");return NULL;}void InsertList(ListNode *pos,ListDataType x)//用不到phead
{ListNode *newnode = CreateListNode(x);ListNode *prev = pos->prev;prev->next = newnode;newnode->prev = prev;newnode->next = pos;pos->prev = newnode;
}void EraseList(ListNode *pos)
{ListNode *posPrev = pos->prev;ListNode *posNext = pos->next;free(pos);pos = NULL;posPrev->next = posNext;posNext->prev = posNext;
}void DestroyList(ListNode *phead)
{assert(phead);ListNode *cur = phead->next;ListNode *next = NULL;while (cur){if (cur != phead){next = cur->next;free(cur);cur = next;}}free(phead);
}
//head = NULL;//在外界置空bool ListEmpty(ListNode *phead)
{assert(phead);return phead->next == phead;
}size_t ListSize(ListNode *phead)
{assert(phead);size_t n = 0;ListNode *cur = phead->next;while (cur!=phead ){n++;cur = cur->next;}return n;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/778559.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI编程助手那些事儿

最近跟身边的程序员老杆子讨论需求时,惊奇的发现,他居然没使用AI编程助手。一时间有2个想法从大脑闪过,然后心里还带了一丝轻蔑。最近跟身边的程序员老杆子讨论需求时,惊奇的发现,他居然没使用AI编程助手。一时间有2个想法从大脑闪过,然后心里还带了一丝轻蔑:AI编程助手…

【靓小虫】2024抖音采集软件,支持关键词搜索、主页作品、评论采集等一键批量爬取!

马哥原创: 用python开发的抖音采集软件工具,支持:爬取搜索关键词结果、爬取主页作品并下载无水印MP4视频、爬取评论内容。目录一、背景洞察1.1 爬取目标1.2 结果演示1.2.1 关键词搜索结果1.2.2 采集:指定博主主页作品1.2.3 采集:指定作品的评论1.3 演示视频二、功能介绍2.0…

读零信任网络:在不可信网络中构建安全系统10认证身份

认证身份1. 用户所知道的信息 1.1. 只有用户本人知道的信息 1.2. 密码1.2.1. 密码是常用的认证机制1.2.2. 密码验证就是确认用户“所知”性的较好途径1.2.3. 用户可以利用密码管理器来便捷地管理多个高强度密码,从而有效降低数据泄露风险1.2.4. 长度足够长1.2.4.1. 最近的NIST…

代码随想录算法训练营day04|24.两两交换链表中的节点,19.删除链表的倒数第N个节点,面试题 02.07.链表相交,142.环形链表II

24.两两交换链表中的节点 题目链接:https://leetcode.cn/problems/swap-nodes-in-pairs/description/ 我的代码: /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNod…

For SALE: State-Action Representation Learning for Deep Reinforcement Learning

发表时间:2023(NeurIPS 2023) 文章要点:这篇文章提出,在强化学习里,对于特征向量表示的任务(low-level states),而不是图像表示的任务(image-based tasks),做表征学习也是有必要的。作者认为一个任务的困难在于底层的dynamic,而不是状态空间的大小,对state-action做…

活动回顾 | 2024 搜索客社区 Meetup 首期线上直播活动圆满结束,附 PPT 下载

2024 年 7 月 31 日,由搜索客社区和极限科技(INFINI Labs)联合举办的首期线上 Meetup 技术交流直播活动圆满结束。本次直播活动吸引了超过 300 人次的技术爱好者参与,共同探讨了 Easysearch、大模型、RAG 等前沿技术和实践应用,为广大搜索技术爱好者提供了一个宝贵的学习和…

HTML概述1

HTML概述 HTML中文是超文本标记语言,它是 HyperText Markup Language首字母简称,是一种用来结构化web网页及其内容的标记语言。网页的内容是由段落、列表、图片和文字等组成。 HTML组成 HTML由一系列元素组成。元素和标签不是一个概念,典型的元素就是由开始标签。文本和结束…

VS远程调试NatApp

目录远程调试用户机器程序1. 调试工具1.1 VS远程调试服务1.2 服务路径1.3 拷贝与启动服务1.4 配置选项2. 外网穿透隧道工具2.1 工具:natapp.exe2.2 官网2.3 购买隧道:2.4 配置隧道:2.5 修改配置:3. 启动调试3.1 附加进程3.2 配置进程 远程调试用户机器程序 1. 调试工具 1.1…

R语言入门笔记:第一节,快速了解R语言——文件与基础操作

上一期 R 语言入门笔记里面我简单介绍了 R 语言的安装和使用方法,以及各项避免踩坑的注意事项。我想把这个系列的笔记持续写下去。这份笔记主要是针对 R 语言学习过程中各个容易掉进去的坑进行规避,以及根据我自身的经验提供一些学习思路。目录关于 R 语言的简单介绍到什么地…