首页 > 其他分享 >【数据结构】用C语言实现单链表及其常见操作

【数据结构】用C语言实现单链表及其常见操作

时间:2024-03-30 21:55:07浏览次数:27  
标签:结点 单链 NULL C语言 链表 SL 数据结构 plist 指针

【数据结构】用C语言实现单链表及其常见操作

链表是一种常用的基础数据结构,可以快速插入和删除数据,但是不能随机访问。

那么它在内存中是怎么存储的呢?它和数组不同,数组在内存中是连续存储的,而链表不一定是连续的,它们之间是通过指针来连接的。

指针 是C语言中最重要的特性之一。那么,什么是指针?说白了就是数据在内存中存放的地址,可以理解为数据在内存中住的哪一栋,几零几。

链表中的每个元素都有数据域和指针域,数据域用于存放数据,指针域用于存放指向下一个元素的指针,即下一个元素在内存中的位置。

在链表中,指向第一个元素的指针,称为头指针。

链表的结束标记是空指针 NULL,当我们遍历表时发现当前指针为 NULL,那就说明,这里是链表的结尾。

好,接下来,我们来一步一步地实现链表。

结构

链表的数据结构如下:

typedef int SLDataType;
typedef struct LinkedList {
    SLDataType data;
    struct LinkedList * next;
}SL;

其中 struct LinkedList * next 这个套娃语句可能有点糊涂人。这就是上文中提到的指针域,如你所见,它指向了和自己相同的数据类型,请看下图。

图中的每一个小框框就是链表中的一个元素(常被叫结点,也有叫节点的),然后我们发现每个小框框里面都有两个部分,一个是 data,一个是 next,这就是数据域和指针域。它们对应我们在结构体中定义的成员变量。

图中的每个 next 都指向了下一个元素,末尾是 NULL,这样是不是就比较清楚了?

然后我们来看看这个 typedeftypedef 是C语言的关键字,用于给数据类型起别名,其语法如下:

typedef <数据类型> <别名>

例如:

typedef int mydata;

这样操作下来就可以用mydata来代替int了。

mydata a = 1;int a = 1; 效果相同。

初始化

现在我们来把一个链表初始化一下,把它的头指针置为空。

void SLInit(SL ** pphead)
{
    *pphead = NULL;
}

这个 ** 是什么呢?这个叫做二级指针,是指向一级指针的指针,这么说有点抽象,来看一个实例:

int a = 114514;
int *p = &a;
int ** pp = &p;

printf("a = %d, p = %p, pp = %p\n", a, p , pp);
printf("&a = %p, p = %p, &p = %p, pp = %p\n", &a, p, &p, pp);
a = 114514, p = 00000000005ffe84, pp = 00000000005ffe78
&a = 00000000005ffe84, p = 00000000005ffe84, &p = 00000000005ffe78, pp = 00000000005ffe78

其中 p 是指向整型变量 a 的一级指针,pp 是指向指针变量 p 的二级指针。

输出的结果是多少不重要,重要的是我们发现,&a == p&p = pp,用文字描述一下大致就是:变量 p 中存放着变量 a 的地址,而变量 pp 存放着变量 p 的地址。这也就意味着 *pp == p

接下来说一下不用二级指针传递参数会怎么样:

void TestSLInit(SL * phead)
{
    phead = NULL;
}

表面上看起来没有问题,可是实际上,当我调用 TestSLInit()函数的时候是这样的:

调用:

TestSLInit(plist);

此时函数内部:

void TestSLInit(SL * phead)
{
    //等价于phead = plist; phead = NULL;
    phead = NULL;
}

由于C语言默认是使用 “值传递” 的。也就是说传入参数的时候,C语言会在函数内部创建一个临时变量来接收这个参数,既然是临时变量,那么它的作用域就只能在函数内部。

也就是上述的 phead 在程序执行完 TestSLInit()函数后就被销毁了,所以,该函数并没有对我的参数 plist 做出任何改变。我们来通过打印直观感受一下。

void Test1()
{
    SL * plist;
    printf("plist = %p\n", plist);      //打印一个随机值
    TestSLInit(plist);
    printf("plist = %p\n", plist);      //值不会变
}

输出结果:

plist = 000001f928431350
plist = 000001f928431350

可以看出,此时的TestSLInit()函数确实没有直到任何作用。

我们来换上 SLInit() 函数试试:

void Test1()
{
    SL * plist;
    printf("plist = %p\n", plist);      //打印一个随机值
    SLInit(&plist);                     //注意,这里要传递plist的指针
    printf("plist = %p\n", plist);      //成功置空
}

输出:

plist = 000001418f2d1350
plist = 0000000000000000

此时,我们成功地把链表置空了。

尾插

尾插,顾名思义就是从链表的尾部插入数据,所以要在插入之前找到尾结点,然后再把元素接在尾结点的后面。(尾结点就是指针域指向空的那个结点。)

这里我们需要分类讨论,当链表没有元素的时候,即 plist == NULL 此时我们要给它分配一个结点,当链表没有元素的时候,我们通过遍历找到它的尾结点,然后将要插入的结点接在后面。

那么问题来了,怎么分配结点呢?怎么找到尾结点呢?

下面是分配结点的函数:

通过参数 x 来分配一个数据域为 x 指针域为 NULL 的结点。再通过 newNode 把新结点返回。

SL * SLBuyCapacity(SLDataType x)
{
    SL * newNode = (SL *)malloc(sizeof(SL));        //给新结点分配空间
    
    if (newNode == NULL)                            //判断是否分配成功
    {
        printf("Malloc Failed!\n");
        exit(-1);
    }
    else
    {
        newNode->data = x;                         //给结点的数据域赋值
        newNode->next = NULL;                      //给结点的指针域赋值
    }

    return newNode;                                //返回该结点
}

尾插实现

void SLPushBack(SL ** pphead, SLDataType x)
{
    //没有节点的情况
    if (*pphead == NULL)
    {
        *pphead = SLBuyCapacity(x);     //将头指针指向当前分配的新结点
    }
    //其他情况
    else
    {
        SL * tail = *pphead;            //通过tail遍历链表,找到尾结点
        SL * newNode = SLBuyCapacity(x);//分配一个新结点
        while (tail->next != NULL)      //遍历
        {
            tail = tail->next;          //使tail指向下一个结点
        }
        tail->next = newNode;           //把新分配的结点接在表尾
    }
}

未完,待更新……

标签:结点,单链,NULL,C语言,链表,SL,数据结构,plist,指针
From: https://www.cnblogs.com/codels/p/18106087

相关文章

  • C语言day01
    C语言入门环境搭建①mingw64的安装和配置环境变量②vscode安装③vscode配置,需要c/c++扩展,将mingw与vscode联系起来基本代码结构头文件、主函数、返回值程序代码分析#include导入标准库文件主函数main主函数的返回值和返回类型运行流程机制编写源文件——>预......
  • 【C语言】结构体
    个人主页点这里~结构体一、结构体类型的声明1、结构的声明2、结构体变量的创建和初始化3、声明时的特殊情况4、自引用二、结构体内存对齐1、对齐规则2、存在内存对齐的原因3、修改默认对齐数三、结构体传参四、结构体实现位段一、结构体类型的声明我们在指针终......
  • 自学-C语言-基础-注释、变量、运算符、判断、循环
    运行环境DevC++DevC++官网认识C语言C语言是一种通用的、面向过程式的计算机程序设计语言。1972年,为了移植与开发UNIX操作系统,丹尼斯·里奇在贝尔电话实验室设计开发了C语言。C语言是一种广泛使用的计算机语言,它与Java编程语言一样普及,二者在现代软件程序员......
  • C语言学习笔记day17
    1.结构体类型得定义      struct结构体名{         数据类型1成员变量1;         数据类型2成员变量2;         数据类型3成员变量3;         ...      };2.结构体变量得定义      存......
  • 使用C语言在VS 环境下基本实现贪吃蛇游戏
    使用C语言在VS环境下基本实现贪吃蛇游戏一丶实现前的准备工作1.设置vs运行环境为window控制台而非window终端1.正确的运行环境页面2.设置正确的运行环境2.了解句柄(下面代码能看明白会照葫芦画瓢用就行)3.利用system函数丶cmd命令设置window控制台窗口的尺寸4.了......
  • C语言中char字符型数据的存取形式:ASCII码之间的转换
    unsignedcharchannelNum=49;则编译器会将ASCII码49存入变量channelNum,实际channelNum表示字符1,所以下次如果以%c形式打印出来,则输出1。e.g:查看代码unsignedcharchannelNum=49;#include"bsp_seg.h"#include"bsp_Init.h"//------------------------------------//将s......
  • CHC5223数据结构和算法
    CHC5223数据结构和算法2023-2024第2学期课业1价值40%的课程个人工作学习成果学生将能够理解:1.1数据结构1.2数据结构的应用1.3面向对象编程概念1.4程序测试方法学生将掌握以下方面的技能:2.1数据抽象2.2数据结构的使用2.3使用高级面向对象语言进行更高级的编程2.4程序测试......
  • 椋鸟数据结构笔记#4:栈与队列
    萌新的学习笔记,写错了恳请斧正。目录栈栈的实现队列队列的实现循环队列栈栈是一种特殊的线性表,是一种遵循后进先出(LIFO,LastInFirstOut)原则的数据结构。想象一下一摞盘子,你最后放上去的盘子会是你第一个拿掉的;同样地,在栈中,最后存入的数据会是第一个被取出来的。......
  • c语言:用do-while输出前40项的斐波那契数值
    求Fibonacci数列的前40个元素。该数列的特点是第1、2两个数为1、1。从第3个数开始,每数是其前两个数之和。  分析:从题意可以用如下等式来表示斐波那契数列:     1,1,2,3,5,8,13,21…     f1=1     (n=1)     f2=1   ......
  • 深入理解C语言宏定义
    目录一、前言二、宏的相关语法2.1#define2.2#undef2.3#运算符2.4##运算符三、宏替换的规则四、宏与函数一、前言        我们都知道#define语句可以定义常量,在编译器预处理时会全部将名字替换为常量。与此同时,#define也允许把参数替换到文本中,这就是本......