0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

链表的基础知识

汽车电子技术 来源:wenzi嵌入式软件 作者: wenzid 2023-01-20 17:00 次阅读

定义

在学习数据结构的时候,最开始接触到的一种数据结构就是线性表,对于线性表的定义是: 零个或多个数据元素的有限序列 ,那对于线性表来讲,又分为顺序存储结构和链式存储结构,对于顺序存储结构来说,也就是数组,数组的每个元素之间的地址是连续的;对于链式存储来说,也就是平常所说的链表,链表每个元素之间的地址并不是连续的,而是分散的,他们之间的联系通过结点的 next 指针来建立。本文尽可能地将链表的知识详细地叙述,所涉及的链表类型包括:单链表,双链表,循环链表,每个链表的操作涉及到创建链表,删除链表,插入链表结点,删除链表结点。

单链表

何为单链表呢,看定义往往让人一时摸不到头脑,直接通过图的形式来展示:

图片

image-20210725104003036

可以看到结点与结点之间都是通过一个指针来建立联系的,所以对于链表结点的定义往往遵循如下的形式:

typedef struct Node
{
    int data;
    struct Node *next;
}ListNode,*LinkList;

而对于单链表来说,其还可以进行细分,可以分为带头结点的单链表和不带头结点的单链表,具体是什么意思呢?我们下面分别对这两种形式进行叙述。

带头结点的单链表

说到头结点,就必须要与另外一个概念进行对比阐述,就是头指针,头指针并不是一个结点,它的作用是指向链表的第一个结点,也就是说我们是通过头指针来找到链表的;那头结点的意思是什么呢?头结点是一个结点,但是这个结点的数据域是没有值的,它的存在是方便我们对于链表的操作,比方说如果要往链表中插入一个结点,而这个结点插入的位置就是第一个结点(如果有头结点,那么头结点就是第0个结点),如果没有头结点的存在,那么就需要更改头指针的值,而如果有头结点的存在,头指针的值是一直不用变的。下图是带有头结点的链表的示意图:

图片

image-20210725103816693

单链表的创建

在知道了单链表的基本形式之后,那自然也就需要创建一个单链表了,在创建一个单链表时,主要分为两种创建方法,分别是头插法和尾插法,下面分别就这两种方法进行叙述。

头插法创建单链表

其创建链表所遵循的一个基本步骤如下所示:

图片

image-20210725110252272

从上图可以看出来头插法创建单链表的一个基本过程,同时可以看到,因为有头结点的存在,在每次新增结点的时候,头指针的值也是不变的,依据上述原理,写出创建单链表的代码,如下所示:

void AddNodeHead(LinkList *head, int value)
{
    ListNode* Node = (ListNode*)malloc(sizeof(ListNode));
    if (Node == NULL)
            return;

    /* 如果是首次插入结点,那么应该创建头结点 */
    if (*head == NULL)
    {
        *head = (ListNode*)malloc(sizeof(ListNode));
        if (*head == NULL)
                return;     
        (*head)->next = NULL;
    }
    Node->data = value;
    Node->next = NULL;
    (*head)->next = Node;
}

头插法创建链表有一个特点就是,它所形成的链表的顺序是反的,也就是说后插入的链表结点反而在前面,如果从第一个结点开始遍历的话,那遍历得到的元素的顺序是倒过来的;那怎么样才能使得链表的插入的顺序和遍历的顺序一致呢?这个时候就需要引入尾插法创建单链表了。

尾插法创建单链表

尾插法也就正如其名字所表征的含义一样,它的意思是从尾部逐渐将结点插入,其所遵循的一个基本过程如下图所示:

图片

image-20210725111844263

代码如下所示:

void AddNodeTail(LinkList *head,int value)
{
    LinkList Node = (LinkList)malloc(sizeof(ListNode));
    if (Node == NULL)
            return;
    /* 创建一个临时结点 */
    LinkList TempNode = NULL;

    /* 先创建头结点 */
    if (*head == NULL)
    {
        *head = (LinkList)malloc(sizeof(ListNode));
        (*head)->next = NULL;
    }

    TempNode = (*head);
    Node->data = value;
    Node->next = NULL;
    while (TempNode->next)
    {
        TempNode = TempNode->next;
    }
    TempNode->next = Node;
}

按照顺序插入一个结点

如果按照上述两种方式构建的链表是每个元素都是从前往后依次递减的,现在要将一个数按照顺序插入到链表中,那么其所遵循的基本原理示意图如下所示:

图片

image-20210725203214469

根据上述的结点插入示意图,写出如下所示的代码:

void IncertNode(LinkList head, int value)
{
    if (head == NULL)
        return;

    LinkList temp = head;
    LinkList Node = (LinkList)malloc(ListNode);
    if (Node == NULL)
        return;

    while (temp->next && value > temp->next->data)
    {
        temp = temp->next;
    }
    Node->data = value;
    Node->next = temp->next;
    temp-> next = Node;
}

通过上述的代码我们可以看到,我们在进行遍历找到要插入的那个结点的时候,引入了一个临时变量来实现这个功能;实际上可以将这个地方进行简化,通过函数调用传递给形参的是值拷贝,那么对于传进去的 head 变量来说,其变量的地址是不会改变的。

void IncertNode(LinkList head, int value)
{
    if (head == NULL)
        return;

    LinkList Node = (LinkList)malloc(ListNode);  

    while (head->next && value > head->next->data)
    {
        head = head->next;
    }
    Node->data = value;
    Node->next = head->next;
    head->next = Node;
}

可以看到,代码比最初那个版本要少了一个临时变量,但是其功能是没有变化的。

删除链表结点

在叙述了插入结点之后,那如何进行删除结点操作呢?删除一个结点的操作所遵循的基本步骤如下如图所示:

图片

image-20210725204142199

根据上述示意图的原理,删除结点的代码如下所示:

void DeleteNode(LinkList head, int target)
{
    LinkList temp = NULL;

    if (head == NULL || head->next == NULL)
            return;

    while (head->next && head->next->data != target)
    {
        head = head->next;
    }

    temp = head->next;
    head-next = temp->next;
    free(temp);
}

删除链表

删除链表的意思也就是将链表的每个结点都释放掉,变成一个空链表,而对于带头结点的链表来说,空链表是包含头结点在内的,删除链表就需要将其他结点释放掉,具体的代码如下所示:

void DeleteList(LinkList* head)
{
    if (*head == NULL || (*head)->next == NULL)
        return;

    LinkList tempNodeP = (*head)->next;
    LinkList tempNodeQ = NULL;

    while (tempNodeP)
    {
        tempNodeQ = tempNodeP->next;
        free(tempNodeP);
        tempNodeP = tempNodeQ;
    }
    (*head)->next = NULL;
}

打印链表结点

最后,就是打印当前链表的元素了,原理也比较简单,直接给出源代码:

void PrintLinkList(LinkList head)
{
    if (head->next == NULL || head == NULL)
    {
        printf("No Element\\r\\n");
        return;
    }
    while (head->next)
    {
        printf("%d ",head->next->data);
        head = head->next;
    }
    printf("\\r\\n");
}

不带头结点的单链表

知道了带头结点的单链表,那么不带头结点的单链表也就显而易见了,示意图如下所示:

图片

image-20210725104153366

通过上图可以知道,如果插入的结点或者是删除的结点是第一个结点的话,那么就需要改变头指针的值。

单链表的创建

上述中,叙述了关于带头结点的单链表的创建,本小节叙述的是不带头结点的单链表的创建,不带头结点的单链表创建的原理和上述一致,就不在这里给出具体的步骤图了,直接给出操作的代码。

头插法创建单链表

void AddNodeHeadWithNh(LinkList *head, int value)
{
    LinkList NodeTemp = (LinkList)malloc(sizeof(ListNode)); 
    if (NodeTemp == NULL)
        return;

    NodeTemp->data = value;
    NodeTemp->next = *head;
    *head = NodeTemp;
}

可以看到,如果不带头结点的单链表,相对于带头结点的单链表,要简单很多,最突出的一点就是不用再创建头结点了。

尾插法创建单链表

void AddNodeTailWNh(LinkList *head, int value)
{
    LinkList temp = NULL;

    LinkList Node = (LinkList)malloc(sizeof(ListNode));
    if (Node == NULL)
        return;

    Node->data = value;
    Node->next = NULL;

    if (*head == NULL)
        *head = Node;
    else
    {
        temp = *head;
        while (temp->next)
        {
            temp = temp->next;  
        }
        temp->next = Node;  
    }
}

尾插法创建单链表不带头节点,要稍微复杂一点,就是涉及到如果最开始是空链表,那么插入第一个结点的时候,需要更改头指针的值,如果不是第一次插入,那么也就不需要改变了;上述代码中,引入了一个临时结点用于遍历,回顾上述中的一个点,就是说在遍历的时候,可以通过不引入临时结点的方式来简化代码,因为函数调用传入形参的是值传递,改进的代码如下所示:

void AddNodeTailWNH(LinkList *head, int value)
{
    LinkList Node = (LinkList)malloc(sizeof(ListNode));
    if (Node == NULL)
            return;

    Node->data = value;
    Node->next = NULL;

    /* 如果是第一次插入 */
    if (*head == NULL)
        (*head) = Node;
    else
    {
        while ((*head)->next)
        {
            head = &(*head)->next;
        }
        (*head)->next = Node;
    }
}

按照顺序插入一个结点

void IncertNodeWNH(LinkList *head, int value)
{
    LinkList Node = (LinkList)malloc(sizeof(ListNode));
    if (Node == NULL)
        return;
     Node->data = value;

    if (*head == NULL)
    {
        *head = Node;
        Node->next = NULL;
    }

    LinkList Curr = *head;
    LinkList Pre = NULL;
    /* 假设链表是从小到大排列的 */
    while (Curr && value > Curr->data)
    {
        Pre = Curr;
        Curr = Curr->next;
    }
    /* 如果要插入的结点就是第一个结点 */
    if (Pre == NULL)
    {
        Node->next = *head;
        *head = Node; 
    }
    else
    {
        Node->next = Curr;
        Pre->next = Node;
    }
}

在上述代码中,如果链表一开始就是为空的,那么就将头指针指向第一个结点就好了,如果链表有元素,但是要插入的元素位于第一个元素之前,那么也需要将头指针进行更改,这里通过引入一个当前结点和前一个结点的方式来完成这个功能。同样的,依据前面对于程序的改进思路,也可以减少定义两个结点的方式来完成,只不过就是说需要增加一个标志位,来记录是插入的位置是不是第一个结点,代码如下所示:

void IncertNode(LinkList *head, int value)
{
    LinkList Node = (LinkList)malloc(sizeof(ListNode));
    if (Node == NULL)
        return;

    LinkList Temp = NULL; 
    Node->data = value;

    int count = 0; 

    /* 假设链表的结点是按照从小到大 */
    while ((*head)->next && value > (*head)->next->data)
    {
        head = &(*head)->next;
        count++;
    }
    if (!count)
    {
        Node->next = *head;
        *head = Node;
    }
    else
    {
        Node->next = (*head)->next;
        (*head)->next = Node;   
    } 
}

删除链表结点

删除链表结点和插入链表结点两种对链表的操作是差不多的,在删除链表结点的时候,我们采用的方式同样是定义一个当前结点,一个上一个结点,代码如下所示:

void DeleteNode(LinkList *head, int target)
{
    LinkList Pre = NULL;
    LinkList Cur = *head;

    if (*head == NULL)
        return;

    while (Cur != NULL && Cur->data != target)
    {
        Pre = Cur;
        Cur = Cur->next;
    }

    /* 如果要删除的结点就是第一个结点 */
    if (Pre == NULL)
    {
        *head = Cur->next;
    }
    else
    {
        Pre->next = Cur->next;
    }   

    free(Cur);
}

跟上面一样的思路,我们同样可以依据上面的想法来简化我们的代码,简化之后的代码如下所示:

void DeleteNode(LinkList *head, int target)
{
    LinkList Temp = NULL;

    for (; *head != NULL ; head = &(*head)->next)
    {
        if ((*head)->data == target)
        {
            Temp = (*head);
            (*head) = (*head)->next;

            free(Temp);
            break; 
        }   
    } 
}

删除链表

在删除链表的操作上和带头结点的链表基本一致,差别就在于说是带头结点的不删除头结点,下面是删除链表的代码:

void ClearLinkList(LinkList *head)
{
    if (*head == NULL)
        return;

    LinkList tempNode  = NULL;
    LinkList tempNodeQ = *head; 

    while (tempNodeQ)
    {
        tempNode = tempNodeQ->next;
        free(tempNodeQ);
        tempNodeQ = tempNode;
    }
    *head = NULL;   
}

打印链表

打印链表的操作也较为简单,具体代码如下所示:

void PrintLinkListWithNh(LinkList head)
{
    if (head == NULL)
        return;

    while (head)
    {
        printf("%d ",head->data);
        head = head->next;
    }
    printf("\\r\\n");
    return;
}

小结

上述就是关于单链表的一个简单的叙述,当然,链表的知识不仅仅是当链表,还有双向链表,循环链表,双向循环链表等等,剩余的 内容在后期的博客中将进行叙述,这次的分享就到这里啦。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 数据结构
    +关注

    关注

    3

    文章

    564

    浏览量

    39900
  • 链表
    +关注

    关注

    0

    文章

    80

    浏览量

    10464
  • 线性表
    +关注

    关注

    0

    文章

    7

    浏览量

    3508
收藏 人收藏

    评论

    相关推荐

    C语言基础知识科普

    C语言是单片机开发中的必备基础知识,本文列举了部分STM32学习中比较常见的一些C语言基础知识
    发表于 07-21 10:58 1700次阅读

    运放-2. 基础知识

    运放基础知识
    的头像 发表于 08-18 10:59 1833次阅读
    运放-2. <b class='flag-5'>基础知识</b>

    C语言链表知识点(2)

    C语言链表知识点(2)
    发表于 08-22 10:38 181次阅读
    C语言<b class='flag-5'>链表</b><b class='flag-5'>知识</b>点(2)

    labview基础知识

    labview基础知识labview基础知识labview基础知识labview基础知识
    发表于 03-08 17:56

    EMC基础知识

    EMC基础知识EMC基础知识---华为.pdf (440.74 KB )
    发表于 06-06 04:36

    WiFi基础知识解析

    一、wifi基础1、详细见如下链接(1)WiFi基础知识解析(2)WiFi基本知识(3)11种物联网协议简介,如WiFi、蓝牙、ZigBee、蜂窝等 二、wifi模块浅析1、WiFi模块浅析 三
    发表于 08-05 08:10

    示波器基础知识

    第1章 示波器基础知识本章的内容整理自网络,主要讲解示波器的基础知识。如果初学的话非常有必要对这部分知识有一个了解。因为示波器是硬件调试必不可少的设备。1.1 什么是示波器1.2 示波器的发展史1.3 示波器的基础概念1.4 触
    发表于 08-09 07:21

    通信基础知识教程

    通信基础知识 1、电信基础知识2、通信电源技术3、配线设备结构、原理与防护4、防雷基础知识5、EMC基础知识6、防腐蚀原理与技术7、产品安
    发表于 03-04 16:48 33次下载

    电池基础知识(集全版)

    电池基础知识(集全版)  电池基础知识
    发表于 11-10 14:19 2175次阅读

    计算机基础知识介绍

    计算机基础知识计算机基础知识计算机基础知识
    发表于 12-03 16:13 0次下载

    使用Eclipse基础知识

    使用Eclipse 基础知识 使用Eclipse 基础知识 适合初学者学习使用
    发表于 02-26 10:30 0次下载

    嵌入式系统基础知识

    关于嵌入式系统基础知识关于嵌入式系统基础知识关于嵌入式系统基础知识
    发表于 03-03 16:58 5次下载

    电源管理基础知识电源管理基础知识电源管理基础知识

    电源管理基础知识电源管理基础知识电源管理基础知识
    发表于 09-15 14:36 75次下载
    电源管理<b class='flag-5'>基础知识</b>电源管理<b class='flag-5'>基础知识</b>电源管理<b class='flag-5'>基础知识</b>

    C语言_链表总结

    本篇文章介绍C语言链表相关知识点,涉及链表的创建、单向链表、循环链表、双向链表、单向循环
    的头像 发表于 08-14 09:53 1482次阅读

    优质LDO基础知识分享

    本节分享下LDO的基础知识,主要来源于Ti的文档《LDO基础知识》。
    的头像 发表于 03-26 11:03 990次阅读