数据结构——链表 原理及C语言代码实现(可直接运行版)

1.链表

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

2.链表的分类

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

①单向或者双向

②带头或者不带头

③循环或者非循环

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

  • 无头单向非循环链表

        无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

  • 带头双向循环链表

        带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。


小记tip:

全局变量或者静态存储都在数据段上,而在函数内部临时开辟的则存储在栈区(记忆技巧:栈区后来先出,生命周期短,适合存储临时使用的变量,在函数执行结束时会自动被释放),堆区存放的则是在函数运行过程中开辟出来的空间,需要程序员进行手动分区以及释放,也可能程序结束时由OS回收,所以也可能会发生内存泄漏问题

3.接口实现

<SList.h>

#pragma oncetypedef int SLNDataType;// Single List
typedef struct SListNode
{SLNDataType val;struct SListNode* next;
}SLNode;void SLTPrint(SLNode* phead); // 遍历打印
void SLTPushBack(SLNode** pphead, SLNDataType x); // 尾插
void SLTPushFront(SLNode** pphead, SLNDataType x); // 头插
SLNode* CreatNode(SLNDataType);// 新节点的创建
SLNode* SLTFind(SLNode* phead, SLNDataType x); // 查找void SLTPopBack(SLNode** pphead); // 尾删
void SLTPopFront(SLNode** pphead);  // 头删void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x); //在pos的前面插入
void SLTErase(SLNode** pphead, SLNode* pos); //删除pos位置void SLTInsertAfter(SLNode* pos, SLNDataType x); //在后面插入
void SLTEraseAfter(SLNode* pos); //在后面删除void SLTDestroy(SLNode** pphead); //销毁整个单链表

4.函数实现

<SList.c>

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include"SList.h"
#include<stdlib.h>
#include<assert.h>void SLTPrint(SLNode* phead)
{SLNode* cur = phead;while (cur != NULL){printf("%d-> ", cur->val);cur = cur->next;}printf("NULL\n");
}SLNode* CreatNode(SLNDataType x) // 新节点的创建
{SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));if (newnode == NULL){perror("malloc fail");exit(-1);}newnode->val = x;newnode->next = NULL;return newnode;
}//尾部插入
void SLTPushBack(SLNode** pphead, SLNDataType x)//pphead是plist的拷贝,改变pphead不能改变plist
{assert(pphead);SLNode* newnode = CreatNode(x);if (*pphead == NULL) // 链表为空{*pphead = newnode;}else {//找尾,最后一个节点SLNode* tail = *pphead;while (tail->next != NULL)//tail->next原因:没有把链表和新节点连接起来,不指向next的话会发生内存泄漏,出了作用域tail销毁{tail = tail->next;}tail->next = newnode;}
}//头部插入
void SLTPushFront(SLNode** pphead, SLNDataType x)
{assert(pphead);SLNode* newnode = CreatNode(x);newnode->next = *pphead;*pphead = newnode; 
}// 尾删
void SLTPopBack(SLNode** pphead) 
{//如果删完了if (*pphead == NULL)return;//或者用断言//assert(*pphead);//if --- 1个节点//else --- 多个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{//找尾,最后一个节点SLNode* prev = NULL;SLNode* tail = *pphead;while (tail->next != NULL)//tail->next原因:没有把链表和新节点连接起来,不指向next的话会发生内存泄漏,出了作用域tail销毁{prev = tail;tail = tail->next;}free(tail);tail = NULL;prev->next = NULL;}
}// 头删
void SLTPopFront(SLNode** pphead)
{//空if (*pphead == NULL)return;//或者用断言//assert(*pphead);// 第一种写法SLNode* tail = *pphead;*pphead = tail->next;free(tail);// 第二种写法/*SLNode* tmp = (*pphead)->next;free(*pphead); // 在 C 语言中是安全的,但是 *pphead = tmp; 将会使头指针 pphead 指向 NULL,这在某些情况下可能是不期望的行为,比如如果链表设计为不允许为空*pphead = tmp;*/
}// 查找
SLNode* SLTFind(SLNode* phead, SLNDataType x)
{SLNode* cur = phead;while (cur){if (cur->val == x){return cur;}else{cur = cur->next;}}return NULL;//没有此节点返回空
}// 插入,在pos的前面插入
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x)
{assert(pphead);assert(pos);assert(*pphead);//assert((!pos && !(*pphead)) || (pos && *pphead));if (*pphead == pos){SLTPushFront(pphead, x);}else{SLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLNode* newnode = CreatNode(x);newnode->next = pos;prev->next = newnode;}}//删除指定数pos
void SLTErase(SLNode** pphead, SLNode* pos)
{assert(pphead);assert(*pphead);assert(pos);if (*pphead == pos){//头插SLTPopFront(pphead);}else{//pos不是第一个SLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}}void SLTInsertAfter(SLNode* pos, SLNDataType x)
{assert(pos);SLNode* newnode = CreatNode(x);newnode->next = pos->next;pos->next = newnode;}void SLTEraseAfter(SLNode* pos)
{assert(pos);assert(pos->next);//pos->next->next;执行这句时如果pos->next为空,空指针没有next,会报错,所以先断言SLNode* tmp = pos->next;pos->next = pos->next->next;free(tmp);tmp = NULL;
}//依次释放单链表中的每个节点,直到整个链表被销毁
void SLTDestroy(SLNode** pphead)
{assert(pphead);SLNode* cur = *pphead;while (cur){SLNode* tmp = cur->next;free(cur);cur = tmp;}
}

5.调试

<Test.c>

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include"SList.h"
#include<assert.h>void TestSLT1()
{SLNode* plist = NULL; // 错误,比如函数内部实现了地址交换,形参并不会影响实参,要实现地址交换,需要传二级指针SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);
}void TestSLT2()
{SLNode* plist = NULL;SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPushFront(&plist, 4);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);SLNode* pos = SLTFind(plist, 3);SLTInsert(&plist, pos, 3);}void TestSLT3()
{SLNode* plist = NULL;SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPushFront(&plist, 4);SLTPrint(plist);SLNode* pos = SLTFind(plist,4);SLTInsert(&plist, pos, 40);SLTPrint(plist);pos = SLTFind(plist, 2);SLTInsert(&plist, pos, 20);SLTPrint(plist);}void TestSLT4()
{SLNode* plist = NULL;SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPushFront(&plist, 4);SLTPrint(plist);SLNode* pos = SLTFind(plist, 1);SLTErase(&plist, pos);SLTPrint(plist); pos = SLTFind(plist, 3);SLTErase(&plist, pos);SLTPrint(plist);
}int main()
{//TestSLT1();//TestSLT2();//TestSLT3();TestSLT4();return 0;
}

6.顺序表和链表的区别和联系

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

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

相关文章

C语言结构体,结构体指针,学了C语言到底有什么用?

#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> struct Stu { char name[20]; //结构体成员名称&#xff0c;数据类型 int age; char sex[10]; char tele[12]; }; void print(struct Stu* ps) //结构体指针 { printf("%s %d %s %s\n&…

2024.2.16每日一题

LeetCode 二叉树的锯齿层序遍历 103. 二叉树的锯齿形层序遍历 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给你二叉树的根节点 root &#xff0c;返回其节点值的 锯齿形层序遍历 。&#xff08;即先从左往右&#xff0c;再从右往左进行下一层遍历&#xff0c;以此类…

【C语言】简单贪吃蛇实现保姆级教学!!!

关注小庄 顿顿解馋૮(˶ᵔ ᵕ ᵔ˶)ა 新年快乐呀小伙伴 引言&#xff1a; 小伙伴们应该都有一个做游戏的梦吧&#xff1f;今天让小庄来用C语言简单实现一下我们的童年邪典贪吃蛇&#xff0c;顺便巩固我们的C语言知识&#xff0c;请安心食用~ 文章目录 贪吃蛇效果一.游戏前工作…

[嵌入式系统-16]:RT-Thread -2- 主要功能功能组件详解与API函数说明

目录 一、RT-Thread主要功能组件 二、内核组件 2.1 概述 2.2 API 三、设备驱动 3.1 概述 3.2 API 四、通信组件 4.1 概述 4.4 API 五、网络组件 5.1 概述 5.2 API 5.3 补充&#xff1a;MQTT协议 六、文件系统 6.1 概述 6.2 API 七、GUI 组件 7.1 概述 7.2 …

165基于matlab的各类滤波器

基于matlab的各类滤波器。汉宁窗设计Ⅰ型数字高通滤波器、切比雪夫一致逼近法设计FIR数字低通滤波器、模拟Butterworth滤波器设计数字低通滤波器、频域抽样法的FIR数字带阻滤波器设计、频域抽样法的FIR数字带通滤波器设计、汉宁窗的FIR数字高通滤波器设计、双线性法设计巴特沃斯…

各版本安卓的彩蛋一览

目录 前言前彩蛋纪Android 2.3 GingerbreadAndroid 3 HoneycombAndroid 4.0 Ice Cream SandwichAndroid 4.1-4.3 JellybeanAndroid 4.4 KitKatAndroid 5 LollipopAndroid 6 MarshmallowAndroid 7 NougatAndroid 8 OreoAndroid 9 PieAndroid 10 Queen CakeAndroid 11 Red Velvet…

备战蓝桥杯---图论之最短路dijkstra算法

目录 先分个类吧&#xff1a; 1.对于有向无环图&#xff0c;我们直接拓扑排序&#xff0c;和AOE网类似&#xff0c;把取max改成min即可。 2.边权全部相等&#xff0c;直接BFS即可 3.单源点最短路 从一个点出发&#xff0c;到达其他顶点的最短路长度。 Dijkstra算法&#x…

STM32CubeMX的下载和安装固件库详细步骤

年也过了&#xff0c;节也过了&#xff0c;接下来又要进入紧张的学习中来了。过完年后发现一个问题&#xff0c;就是我之前吃的降压药不太管用&#xff0c;每天的血压只降到了91/140左右&#xff0c;没有到安全范围内&#xff0c;从初三开始换了一种降压药&#xff0c;效果出奇…

【JAVA-Day86】守护线程

守护线程 守护线程摘要引言1. 了解守护线程&#xff1a;它是什么&#xff1f;&#x1f47b;特点和用途示例代码 2. 为何我们需要守护线程&#xff1f;&#x1f47b;辅助性任务处理不阻止程序的正常运行重要的清理工作示例代码&#x1f4da; 3. 如何创建和管理守护线程&#xff…

【51单片机】利用STC-ISP软件工具【定时器计算器】配置【定时器】教程(详细图示)(AT89C52)

前言 大家好吖&#xff0c;欢迎来到 YY 滴单片机系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过单片机的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的…

基于微信小程序的智能社区服务小程序,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

鸿蒙开发系列教程(二十三)--List 列表操作(2)

列表样式 1、设置内容间距 在列表项之间添加间距&#xff0c;可以使用space参数&#xff0c;主轴方向 List({ space: 10 }) { … } 2、添加分隔线 分隔线用来将界面元素隔开&#xff0c;使单个元素更加容易识别。 startMargin和endMargin属性分别用于设置分隔线距离列表侧…