<数据结构> 链表 - 单链表(c语言实现)

这篇具有很好参考价值的文章主要介绍了<数据结构> 链表 - 单链表(c语言实现)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

(关于哨兵位结点)

哨兵位结点也叫哑节点。哨兵位结点也是头结点 。该节点不存储有效数据,只是为了方便操作 (如尾插时用带哨兵位的头结点很爽,不需要判空)。

有哨兵位结点的链表,第一个元素应该是链表第二个节点(head -> next,head为哨兵位结点)对应的元素。

有哨兵位结点的链表永不为空 (因为至少有一个结点——哨兵位结点),这样可以避免判断头是否为空,起到简化代码、减少出错的作用。

一、不带哨兵位单链表结点的创建

🚩
下面的自定义类型、函数名里SLT:
来源于单链表的英文:Single Linked List

1.1 typedef 链表的数据类型

typedef 一下链表数据域的数据类型,目的 是如果以后需要改变链表数据类型直接在typedef后改一下即可,否则要在程序中一个个的改,麻烦并且易出错

typedef int SLTDataType;

1.2 结点的结构体创建

凡是有多个数据的 → 创建结构体。
数据域: 存储的数据data,类型是SLTDataType
指针域: 存下一个结点的地址next,类型是结构体指针 struct SListNode*

typedef struct SListNode	//line1
{	//line2
	SLTDataType data;//数据域	//line3
	struct SListNode* next;//指针域	//line4
}SLTNode;	//line5

🔺 注意:指针域的结构体指针不可以是SLTNode*
编译器的查找规则:编译的时候,如果要用到一个函数或者一个类型,它不会向下查找,只能向上查找。具体来说,SLTNode*在第五行以后才起作用,在第四行的时候还没有定义“SLTNode*

二、单链表要实现的功能

1、打印链表:将链表各个结点数据域中的数据按顺序打印出来

2、创建一个新结点:插入一个结点的时候要创建一个新结点,干脆封装成一个函数,后面直接调用即可

3、在链表尾部插入一个数据(尾插)

4、删除链表尾部的结点(尾删)

5、在链表头部插入一个数据(头插)

6、删除链表头部的结点(头删)

7、查找某个结点:返回结点地址

8、删除某个结点

9、单链表中插入结点

10、销毁链表

三、需要包含的头文件

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

四、函数接口一览

//打印 单链表
void SLTPrint(SLTNode* phead);
//动态申请一个节点
SLTNode* BuySLTNode(SLTDataType x);
//尾插(并给节点中的data赋值)
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//尾删
void SLTPopBack(SLTNode** pphead);
//头插(并给节点中的data赋值)
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//头删
void SLTPopFront(SLTNode** pphead);
//查找并返回结点地址
SLTNode* SLFind(SLTNode* phead, SLTDataType x);
//删除某个结点
void SLErase(SLTNode** pphead, SLTNode* pos);
//pos前 插入结点
void SLInsert(SLTNode** pphead, SLTNode* pos, SLDataType x);
//销毁
void SLDestroy(SLTNode** pphead);

为什么有些函数参数传递的是二级指针,有些是一级指针?

因为有些函数需要改变传入的结点

phead可能为空:链表一开始为空(main()函数中定义SLTNode* phead = NULL),对于插入类的函数,第一次插入时phead为空,那么就要改变phead指向的空间(要在函数中创建一个新结点,phead改变为该结点的地址),即需要改变phead,而phead是一级指针,因为要改变指针需要传递指针的指针——二级指针,即传递指针变量phead的地址——&phead

但是像打印这样的函数,不需要改变phead,只需要遍历一遍链表,打印出各结点的数据即可,所以传phead(一级指针)就好,不需要二级指针。

五、功能的实现

1)打印单链表

//打印 单链表
void SLTPrint(SLTNode* phead);
void SLTPrint(SLTNode* phead)
{
	SLTNode* cur = phead;//①
	while (cur != NULL)//②
	{
		printf("%d -> ",cur->data);
		cur = cur->next;//③
	}
	printf("NULL\n");
}

① 这里也可以直接用phead进行循环,但是像这样创建一个 “当前节点” (cur源自单词current)会比较“美观”。(But 如果这个函数内部后面还需要用头节点的话就不能直接用phead,否则会找不到头)
② 控制结束的条件
③ 遍历

2)创建新节点

链表的结点:按需分配,随用随创
链表的头插、尾插(只要是插入)都需要创建一个新节点,然后插到对应位置。所以我们可以直接把“创建新节点”封装成一个函数,以便后面直接调用:👇

SLTNode* BuySLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//①
	if(newnode == NULL)//②
	{
		perror("malloc newnode fail: ");
		return;
	}
	//③给新节点赋值
	newnode->data = x;
	newnode->next = NULL;
	return newnode;//别忘了返回newnode
}

①动态开辟一个新节点,.h头文件里要包含 <stdlib.h>
②判断开辟是否成功,如果不成功则输出错误并返回
③给新节点的数据域赋值,指针域赋为空:NULL,这样做的好处是: 不需要最后对链表尾结点的指针域置空。

3)尾插

在链表尾部插入一个节点
先看这段代码:

void SLTPushBack(SLTNode** pphead, SLTDataType x);
{
    SLTNode* newnode = BuySLTNode(x);
    SLTNode* tail = *pphead;
    while (tail->next != NULL)
    {
        tail = tail->next;
    }
    tail->next = newnode;
}

上面这段代码的前提是,我们已经假定这个链表有>=1个节点,但是如果*pphead为空呢?
如果为空,则只执行:

SLTNode* newnode = BuySLTNode(x);
SLTNode*tail = *pphead;

初始化:SLTNode* phead = NULL;
传参: SLTPushBack(&phead, x);
pheadNULL时,也就不存在tail->next

所以切记要先判断*pphead是否为空:

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = BuySLTNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;
		//找原链表的尾结点
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

<数据结构> 链表 - 单链表(c语言实现)

4)尾删

尾删比较麻烦,因为要判断链表是否为空以及分情况讨论结点个数。

先看这段代码:

void SLTPopBack(SLTNode** pphead)
{
    assert(pphead && *pphead);//①
    SLTNode* tail, * tailpre;//②
    tail = *pphead->next;
    tailpre = *pphead;
    while (tail->next)
    {
        tail = tail->next;
        tailpre = tailpre->next;
    }
    free(tail);//③
    tailpre->next = NULL;//④
}

pphead(二级指针)和*pphead绝对不可以为空,最好断言一下
②定义tail和tail前一个结点tailpre,目的是释放tail后,直接得到新的尾结点,方便置空
③没必要再把tail置空:tail = NULL;因为tail是局部变量,函数结束就自动销毁了
④释放后,新的尾结点的next置空
看似没什么毛病·······

但是,上面没有考虑只有一个结点的情况!!
⚡如果只有一个结点, tail = *pphead->next;后,tailNULL,下面的执行就出大问题了

解决办法是判断一下:

void SLTPopBack(SLTNode** pphead)
{
    assert(pphead && *pphead);
    if ((*pphead)->next == NULL)//只有一个结点
    {
        free(*pphead);
        *pphead = NULL;
    }
    else//    >=2个结点
    {
        SLTNode* tail, * tailpre;
        tail = *pphead->next;
        tailpre = *pphead;
        while (tail->next)
        {
            tail = tail->next;
            tailpre = tailpre->next;
        }
        free(tail);
        tailpre->next = NULL;
    }
}

5)头插

按照尾插的路子,可能会这样写:

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
    assert(pphead);
    SLTNode* newnode = BuySLTNode(x);
    if (*pphead == NULL)
    {
        *pphead = newnode;
    }
    else
    {
        newnode->next = *pphead;
        *pphead = newnode;
    }
}

当然没有错,但是仔细想一想,其实没有必要判断*pphead是否为空,因为即使*pphead为空,执行else部分依然没毛病!

化简为:

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
    assert(pphead);
    SLTNode* newnode = BuySLTNode(x);
    newnode->next = *pphead;
    *pphead = newnode;
}

6)头删

头删相比较尾删很简单,因为不需要像尾删一样找tail前一个结点。

头删可以直接删:

void SLPopFront(SLTNode** pphead)
{
    assert(pphead && *pphead);
    SLTNode* next = (*pphead)->next;//临时存一下第二个元素的结点
    free(*pphead);
    *pphead = next;
}

7)查找

查找链表中的某个元素,只需遍历一遍链表。返回data == 要查找的元素第一次出现的节点的地址;如果链表中没有要查找的元素,返回NULL

<注意,空链表也可以查找,返回NULL即可>↓

SLTNode* SLFind(SLTNode* phead, SLDataType x)
{
    SLTNode* cur = phead;
    while (cur)
    {
        if (cur->data == x)
            return cur;
        cur = cur->next;
    }
    return NULL;
}

8)删除

分两种情况:链表只有一个节点、链表有多个节点。
1、只有一个节点:如果*pphead == pos,相当于头删,直接调用前面的函数即可。
2、有多个节点:遍历链表,直到找到地址为pos的结点,按照尾删的思路,删除即可。

void SLErase(SLTNode** pphead, SLTNode* pos)
{
    assert(pphead && *pphead && pos);//都不能为空
    if (*pphead == pos)
    {
        SLPopFront(pphead);
    }
    else
    {
        SLTNode* cur = *pphead;
        while (cur->next != pos)
        {
            cur = cur->next;
        }
        SLTNode* next = cur->next->next;
        cur->next = next;
        free(pos);//一定要free
    }
}

9)插入结点

在pos前插入:

void SLInsert(SLTNode** pphead, SLTNode* pos, SLDataType x)
{
    assert(pphead && pos);
    if (pos == *pphead)
    {
        SLPushFront(pphead, x);
    }
    else
    {
        SLTNode* cur = *pphead;
        while (cur->next != pos)
        {
            cur = cur->next;
        }
        SLTNode* insnode = BuyNode(x);
        cur->next = insnode;
        insnode->next = pos;
    }
}

10)销毁

对于销毁链表,如果只free掉 *pphead行么?
当然不行!!

因为单链表由一个一个的结点连接起来的。如果只free(*pphead),头结点是释放了,但是后面的节点没被释放,还占用着空间但是已经找不到他们的地址了。

所以应该逐个释放👇

void SLTDestroy(SLTNode** pphead)
{
    SLTNode* cur = *pphead;
    while (cur)
    {
        SLTNode* next = cur->next;
        free(cur);
        cur = next;
    }
    *pphead = NULL; 
}

销毁完后 最好把*pphead 置空,防止销毁链表后对链表误操作而导致的野指针问题。文章来源地址https://www.toymoban.com/news/detail-410258.html

到了这里,关于<数据结构> 链表 - 单链表(c语言实现)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构】链表(单链表与双链表实现+原理+源码)

    博主介绍:✌全网粉丝喜爱+、前后端领域优质创作者、本质互联网精神、坚持优质作品共享、掘金/腾讯云/阿里云等平台优质作者、擅长前后端项目开发和毕业项目实战✌有需要可以联系作者我哦! 🍅附上相关C语言版源码讲解🍅 👇🏻 精彩专栏推荐订阅👇🏻 不然下次找

    2024年01月24日
    浏览(143)
  • 链表的总体涵盖以及无哨兵位单链表实现——【数据结构】

     😊W…Y:个人主页 在学习之前看一下美丽的夕阳,也是很不错的。 如果觉得博主的美景不错,博客也不错的话,关注一下博主吧💕 在上一期中,我们说完了顺序表,并且提出顺序表中的问题 1. 中间/头部的插入删除,时间复杂度为O(N) 2. 增容需要申请新空间,拷贝数据,释

    2024年02月14日
    浏览(68)
  • 一起学数据结构(3)——万字解析:链表的概念及单链表的实现

    上篇文章介绍了数据结构的一些基本概念,以及顺序表的概念和实现,本文来介绍链表的概念和单链表的实现,在此之前,首先来回顾以下顺序表的特点: 1. 顺序表是一组地址连续的存储单元依次存储的线性表的数据结构,逻辑上:顺序表中相邻的数据元素,其物理次序也是

    2024年02月13日
    浏览(54)
  • 单链表—C语言实现数据结构

    本期带大家一起用C语言实现单链表🌈🌈🌈 链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的;简单来说,线性表的链式存储结构生成的表,称作“链表”。 每个元素本身由两部分组成: 1、本身的信息,称为

    2024年02月07日
    浏览(83)
  • 【数据结构】—C语言实现单链表(超详细!)

                                         食用指南:本文在有C基础的情况下食用更佳                                     🔥 这就不得不推荐此专栏了:   C语言                                      ♈️ 今日夜电波:  あなたは煙草

    2024年02月14日
    浏览(64)
  • 数据结构:单链表的实现(C语言)

    个人主页 : 水月梦镜花 个人专栏 : 《C语言》 《数据结构》 本博客将要实现的无头单向不循环链表。 我们将节点定义为如下结构: 其成员变量有data,next。 将int重命名为STLDataType,方便我们以后修改数据域的内容。 动态申明一个空间,来放置数据。如下: 将data的内容置

    2024年02月14日
    浏览(59)
  • 【数据结构】C语言实现单链表(带头结点)

    Single linked list with leading nodes 关于不带头结点的单链表:不带头结点的单链表 结点定义: 接口定义: 初始化需要申请头结点,让头指针指向空的头结点。 将申请结点的代码进行封装: 需要越过头结点 找到尾结点,然后插入到尾结点的后面。 对比不带头结点的单链表的尾插

    2024年02月02日
    浏览(219)
  • 数据结构-链表-单链表(3)

    目录 1. 顺序表的缺陷 2. 单链表 2.1 单链表的基本结构与接口函数 2.2 重要接口 创建新节点的函数: 2.2.1 尾插 2.2.2 头插 2.2.3 尾删 2.2.4 头删 2.2.5 查找 2.2.6 插入 2.2.7 删除 2.2.8 从pos后面插入 2.2.9 从pos后面删除 3. 链表的缺陷与优势: 4. 链表与顺序表比较 写在最后: 为什么

    2024年01月17日
    浏览(109)
  • 数据结构——单链表的实现(c语言版)

    前言           单链表作为顺序表的一种,了解并且熟悉它的结构对于我们学习更加复杂的数据结构是有一定意义的。虽然单链表有一定的缺陷,但是单链表也有它存在的价值, 它也是作为其他数据结构的一部分出现的,比如在图,哈希表中。 目录 1.链表节点的结构 2.头插

    2024年02月13日
    浏览(60)
  • 【算法与数据结构】 C语言实现单链表队列详解

    前面我们学习了队列的顺序表的实现,本节将用单链表实现队列。 队列也可以数组和链表的结构实现, 使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低 。下面我们先复习一下队列的基本概念: 队列:只允许在一端进行插入

    2024年04月11日
    浏览(60)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包