首页 > 其他分享 >Day03 链表概念与单向不循环链表的实现

Day03 链表概念与单向不循环链表的实现

时间:2024-06-15 16:02:46浏览次数:28  
标签:head LinkedList Day03 单向 next 链表 节点 指针

目录

1、顺序表的优缺点

2、链式存储的线性表

3、单向不循环链表实现


1、顺序表的优缺点

顺序表的优点是:

        由于顺序表数据元素的内存地址都是连续的,所以可以实现随机访问,而且不需要多余的信息来描述相关的数据,所以存储密度高。

顺序表的缺点是:

        顺序表的数据在进行增删的时候,需要移动成片的内存,另外,当数据元素的数量较多的时候,需要申请一块较大的连续的内存,同时当数据元素的数量的改变比较剧烈,顺序表不灵活。

2、链式存储的线性表

        链式存储指的是采用离散的内存单元来存储数据元素,用户需要使用某种方式把所有的数据元素连接起来,这样就可以变为链式线性表,简称为链表,链表可以高效的使用碎片化内存。

顺序表和链式表的区别:顺序表使用连续的内存,链式表使用离散的内存空间。

链表中的每个数据元素的地址是不固定的,所以每个数据元素都应该使用一个指针指向直接后继的内存地址,当然最后一个数据元素没有直接后继,所以最后一个数据元素指向NULL即可,作为用户只需要知道第一个数据元素的内存地址,就可以访问后继元素了。

        链式存储,则线性表中每一个数据元素除了存储自身数据之外,还需要额外存储直接后继的地址,所以链表中的每一个数据元素都是由两部分组成:存储自身数据的部分被称为数据域,存储直接后继地址的部分被称为指针域,数据域和指针域组成的数据元素被称为结点(Node)。

链表中的数据元素=数据域+指针域

根据链表的结点的指针域的数量以及根据链表的首尾是否相连,把链式线性表分为以下几种:

  • 单向链表
  • 单向循环链表
  • 双向链表
  • 双向循环链表
  • 内核链表

这几种链表的使用规则差不多,只不过指针域数量不同。

3、单向不循环链表实现

/*******************************************************
 * @file:LinkedList.c
 * @brief:单向不循环链表封装
 * @author:[email protected]
 * @date:2024/06/15
 * @version:V1.0
 * @attention:
 *          头节点不存放数据
 *          链表中所有节点的指针域都指向下一个节点
 *          链表中最后一个节点的指针域指向NULL
 *          链表中所有节点是动态分配内存的
 *          链表中所有节点的数据域类型是相同的
 * @history
 *        Date        Version    Author            Notes
 *      2024-6-15      0.0.1    demon_xing    first version
 * Copyright (c) 2024, [email protected]  All Rights Reserved.
 * *******************************************************/

/*****************************************************************
 * @brief:头文件
 ******************************************************************/
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

/*****************************************************************
 * @brief:构建数据结构体和节点结构体
 ******************************************************************/
// 1.构建数据类型(或结构体)
typedef int ElemType;

// 2.构建链表节点结构体
typedef struct LinkedList
{
    // 数据域
    ElemType data;
    // 指针域
    struct LinkedList *next;
} LinkedList_t;

/*****************************************************************
 * @brief:单向不循环链表函数声明
 ******************************************************************/
// 3.创建链表
LinkedList_t *creatList();

// 4.判断链表是否为空
bool isEmpty(LinkedList_t *head);

// 5.创建节点
LinkedList_t *creatNode(ElemType data);

// 6.插入节点--头插与尾插
bool insertNode(LinkedList_t *head, ElemType data);

// 7.删除节点
bool DeleteNode(LinkedList_t *head, ElemType data);

// 8.遍历显示链表
void displayNode(LinkedList_t *head);

// 9.销毁链表
bool distroyNode(LinkedList_t *head);

/***********************creatList()************************************
 * @functionname:creatList()
 * @function:创建链表
 * @brief:该函数用于创建链表
 * @param
 *      head:头节点指针
   @return:
           Sqlist_t* 返回顺序表结构体指针
 * @author:demon_xing
 * @date:2024/06/15
 * @version:V1.0
 * @history
 *        Date        Version    Author            Notes
 *      2024-6-15      0.0.1    demon_xing    first version
 * @note:头节点不存放数据,尾节点指向NULL
 **************************END*************************************/

// 1.创建链表
struct LinkedList *creatList()
{
    // (1)为链表申请堆内存(头节点)
    LinkedList_t *head = (LinkedList_t *)calloc(1, sizeof(LinkedList_t));
    // 判断是否申请成功,如果申请失败则返回NULL
    if (NULL == head)
    {
        perror("calloc head is failed");
        return NULL;
    }
    // (2)若申请成功,则初始化成员
    // 头节点不存放数据
    // head->data = 0;
    head->next = NULL;
    // (3)返回链表头节点指针
    return head;
}
/**************************creatNode()***************************************
 * @functionname:creatNode()
 * @function:创建节点
 * @param:
 *          data:节点数据
 * @return: LinkedList_t* 返回节点指针
 * @author:demon_xing
 * @date:2024/06/15
 * @version:V1.0
 * @history
 *        Date        Version    Author            Notes
 *      2024-6-15      0.0.1    demon_xing    first version
 * @attention:
 *          (1)为节点申请堆内存
 *          (2)初始化节点成员
 *          (3)返回节点指针
 *************************END*****************************************/
// 2.创建节点
LinkedList_t *creatNode(ElemType data)
{
    // (1)为新节点申请堆内存
    LinkedList_t *node = (LinkedList_t *)calloc(1, sizeof(LinkedList_t));
    // 判断是否申请成功,如果申请失败则返回NULL
    if (NULL == node)
    {
        perror("calloc node is failed");
        return NULL;
    }
    // (2)若申请成功,则初始化成员
    node->data = data;
    node->next = NULL;
    // (3)返回新节点指针
    return node;
}
/***********************isEmpty()******************************************
 * @functionname:isEmpty()
 * @function:判断链表是否为空
 * @param:
 *          head:头节点指针
 * @return: bool
 * @author:demon_xing
 * @date:2024/06/14 16:49:56
 * history
 *        Date        Version    Author            Notes
 *      2024-6-14      0.0.1    demon_xing    first version
 * @note:
 *      如果链表为NULL,则返回true;否则返回false
 *************************END*****************************************/
// 3.链表是否为空
bool isEmpty(LinkedList_t *head)
{
    if (NULL == head)
    {
        perror("LinkedList_t is NULL");
        return true;
    }
    return false;
}

/***********************insertNode*******************************
 * @functionname:insertNode()
 * @function:插入节点
 * @param:
 *          head:头节点指针
 *          data:要插入的数据
 * @return: bool
 * @author:demon_xing
 * @date:2024/06/14 16:49:56
 *    history
 *         Date        Version    Author            Notes
 *      2024-6-14      0.0.1    demon_xing    first version
 * @note:
 *      插入节点时,要判断链表是否为空,如果为空则返回false;否则插入节点
 *      插入节点时,要先将新节点的next指针指向头节点的next指针所指向的节点
 *      然后将头节点的next指针指向新节点,防止数据丢失,最后返回true
 * ***********************END***************************************/
// 4.插入节点
bool insertNode(LinkedList_t *head, ElemType data)
{
    // 判断链表是否为空,如果为空则返回false
    if (isEmpty(head))
    {
        perror("LinkedList_t is NULL");
        return false;
    }
    // 如果链表不为空,则插入节点
    // 创建一个节点
    LinkedList_t *node = creatNode(data);

    // // 头插法
    // node->next = head->next;
    // head->next = node;

    // 尾插法
    LinkedList_t *p = head;
    while (!isEmpty(head) && p->next)
    {
        p = p->next;
    }
    p->next = node;
    node->next = NULL;
    // 插入完成返回true
    return true;
}
/**************************deleteNode**********************************
 * @functionname:deleteNode()
 * @function:删除节点
 * @param:
 *          head:头节点指针
 *          data:要删除的数据
 * @return:
 *          bool  删除成功返回true,删除失败返回false
 * @author:demon_xing
 * @date:2024/06/14 16:49:56
 *     history
 *         Date        Version    Author            Notes
 *      2024-6-14      0.0.1    demon_xing    first version
 * @note:
 *      删除节点时,要判断链表是否为空,如果为空则返回false;否则删除节点
 *      删除节点时,要遍历查找要删除的数据,如果找到则删除节点,否则返回false
 *      删除节点时,通常是将要删除的节点的前一个节点的next指针指向要删除的节点的下一个节点
 *      然后释放要删除的节点的内存,最后返回true
 *
 *      如果删除节点时,要删除头节点,则需要先将头节点的next指针指向要删除的节点的下一个节点,然后释放要删除的节点的内存,最后返回true
 *      如果删除节点时,要删除尾节点,则需要先将尾节点的前一个节点的next指针指向NULL,然后释放要删除的节点的内存,最后返回true
 *      如果删除节点时,要删除中间节点,则需要先将尾节点的前一个节点的next指针指向要删除的节点的下一个节点,然后释放要删除的节点的内存,最后返回true
 *      删除节点时,通常需要通过指针变量指向头节点,然后通过遍历查找要删除的数据,如果找到则删除节点,否则返回false
 *      原因是:如果通过头节点直接删除节点,则需要先将头节点的next指针指向要删除的节点的下一个节点,然后释放要删除的节点的内存,最后返回true
 *      在删除节点时,需要修改链表的头指针,使得链表的头指针指向新的头节点。如果不使用指针变量指向头节点,直接在函数内部修改头节点,会导致修改不生效。
 *      遍历链表时,需要使用指针变量指向当前节点,然后通过遍历查找要删除的数据,如果找到则删除节点,否则返回false。
 * ***************************END*********************************/
// 5.删除节点
bool deleteNode(LinkedList_t *head, ElemType data)
{
    // 判断链表是否为空
    if (isEmpty(head))
    {
        perror("LinkedList is NULL");
        return false;
    }
    // 如果链表不为空,则删除节点
    if (isEmpty(head) == false)
    {
        // 定义一个指针指向头节点
        LinkedList_t *p = head;
        // 遍历查找要删除的数据,如果未找到要删除的数据,则将p指向p->next继续查找数据,直到找到该数据为止
        while (p->next->data != data)
        {
            p = p->next;
        }
        // 找到该数据之后,定义一个指针q指向要删除的节点
        LinkedList_t *q = p->next;

        // p指向要删除的数据的前驱,p->next指向要删除的数据,q指向要删除的节点,则q->next指向要删除的节点的下一个节点
        //  p  q=p->next   q->next
        p->next = q->next;
        q = NULL; // 防止野指针
    }
} /**************************displayNode********************************************
   * @functionname:displayNode()
   * @function:遍历链表
   * @param:
   *          head:头节点指针
   * @return:
   *          void
   * @author:demon_xing
   * @date:2024/06/14 16:49:56
   *     history
   *         Date        Version    Author            Notes
   *      2024-6-14      0.0.1    demon_xing    first version
   * @note:
   *         遍历链表,如果链表为空则返回false,否则遍历链表,将链表中的数据依次输出
   * ***************************END*****************************************/
// 6.遍历显示链表
void displayNode(LinkedList_t *head)
{
    if (isEmpty(head))
    {
        perror("LinkedList is NULL");
        return;
    }
    // 定义一个结构体指针变量p指向头节点的下一个节点
    LinkedList_t *p = head->next;
    // 如果头节点的下一个节点不为空,则遍历链表,将链表中的数据依次输出
    while (NULL != p)
    {
        printf("%d ", p->data);
        p = p->next;
    }
    printf("\n");
}
/*************************distroyNode****************************************
 * @functionname:distroyNode()
 * @function:销毁链表
 * @param:
 *          head:头节点指针
 * @return:
 *          bool  销魂成功返回true,销魂失败返回false
 * @author:demon_xing
 * @date:2024/06/14 16:49:56
 * history
 *         Date        Version    Author            Notes
 *      2024-6-14      0.0.1    demon_xing    first version
 * @note:
 *      notes
 * *************************END************************************************/

// 7.销毁链表
bool distroyNode(LinkedList_t *head)
{
    // 判断链表是否为空,如果为空则返回false
    if (isEmpty(head))
    {
        perror("LinkedList_t is NULL");
        return false;
    }
    // 如果链表不为空,则释放节点内存和链表内存
    free(head);
}

// 8.测试代码
int main(int argc, char *argv[])
{
    // (1)创建链表
    LinkedList_t *head = creatNode(10);
    // (2)插入节点
    insertNode(head, 50);
    insertNode(head, 40);
    insertNode(head, 30);
    insertNode(head, 20);
    // (3)遍历显示
    displayNode(head);

    // (4)删除节点
    deleteNode(head, 50);
    // (5)遍历显示
    displayNode(head);

    // (6)销毁节点
    distroyNode(head);
    // 遍历显示,如果段错误则说明销毁成功
    // 也可以用valgrind内存检测工具或内存泄漏检测器来证所有的内存已经被正确释放,确保没有内存泄漏。
    displayNode(head);

    return 0;
}

标签:head,LinkedList,Day03,单向,next,链表,节点,指针
From: https://blog.csdn.net/qq_59111928/article/details/139664444

相关文章

  • 链表经典题目:环形链表问题(LeetCode141.环形链表、LeetCode142.环形链表Ⅱ)
    ......
  • FreeRTOS简单内核实现2 双向链表
    FreeRTOSKernelV10.3.1FreeRTOS的list.c/list.h文件中有3个数据结构、2个初始化函数、2个插入函数、1个移除函数和一些宏函数,链表是FreeRTOS中的重要数据结构,下述“1、数据结构”和“2、操作链表”两个小节内容主要对其原理进行讲解1、数据结构1.1、xLIST_IT......
  • 链表的概述
    目录链表的组成:头节点(HeadNode):链表头节点的概念使用头节点进行链表操作尾节点(TailNode):链表尾节点的概念链表尾节点的一般形式链表的分类:静态链表:动态链表:单链表(SinglyLinkedList):双链表(DoublyLinkedList):循环链表(CircularLinkedList):链表的操作:插入(Insert......
  • 数据结构(C/C++)专题一:顺序表与链表
    今天开始一个新的专题:数据结构当然,不仅仅适用于学习也适用于408考研。那么提起数据结构思维导图:总结如下:·1.初识顺序表与链表首先呢我们要明白,数据结构有物理结构,也有逻辑结构物理结构就是电脑实际的结构,链式,非链式,索引,散列eg:链式结构(LinkedStructure)例子:火车车......
  • 代码随想录——链表1——基本介绍与3种语言实现
    ......
  • 每日一练——随机链表的复制
    138.随机链表的复制-力扣(LeetCode)关键点:通过“相互插入”式的复制方法来把源链表和目标链表的random联系起来。  /***DefinitionforaNode.*structNode{*intval;*structNode*next;*structNode*random;*};*/typedefintLD......
  • 带头+双向+循环链表的实现
    目录1.链表1.1带头双向循环链表2.链表的实现2.1结构体2.2初始化2.3打印2.4判断空不能删2.5尾插2.6头插2.7尾删2.8头删2.9查找2.10在pos之前插入2.11删除pos位置的值2.12销毁2.13创建节点3.test主函数4.List.c文件5.List.h文件1.链表1.1带头......
  • 单向链表————遍历、查找、插入结点 (基于C语言实现)
    #include<stdio.h>#include<stdbool.h>#include<stdlib.h>#include<stdbool.h>//指的是单向链表中的结点有效数据类型,用户可以根据需要进行修改typedefintDataType_t;//构造链表的结点,链表中所有结点的数据类型应该是相同的typedefstructLinkedList{Dat......
  • 【云岚到家】-day03-2-门户缓存实现实战
    【云岚到家】-day03-2-门户缓存实现实战5缓存实现5.2定时任务更新缓存5.2.1分布式调度平台5.2.1.1jdk提供的Timer定时器5.2.1.2使用第三方Quartz方式5.2.1.3使用分布式调度平台XXL-JOB5.2.2XXL-JOB5.2.2.1介绍5.2.2.2部署调度中心5.2.2.3执行器5.2.2定义缓......
  • C语言数据结构实现-静态链表2-基本操作
    上节,我们初步创建了一个静态链表,本节学习有关静态链表的一些基本操作,包括对表中数据元素的添加、删除、查找和更改。本节是建立在已能成功创建静态链表的基础上,因此我们继续使用上节中已建立好的静态链表学习本节内容,建立好的静态链表如图1所示:静态链表添加元素例如,在图1......