C 语言动态链表

news2024/9/21 16:49:48

线性结构->顺序存储->动态链表

一、理论部分

从起源中理解事物,就是从本质上理解事物。 -杜勒鲁奇

动态链表是通过结点(Node)的集合来非连续地存储数据,结点之间通过指针相互连接。

动态链表本身就是一种动态分配内存的数据结构。每个结点都包含数据部分和指向下一个节点的指针。这种结构允许在运行时动态地添加、删除或修改结点,而不需要像数组那样担心容量问题。

1.1、画图的重要性

直观理解数据结构:画图可以帮助我们直观地理解链表的结构,包括每个节点的位置、存储的数据以及节点之间的连接关系。这种直观的理解有助于我们更好地编写、调试和维护链表相关的代码。

辅助算法设计:在设计链表操作的算法时,如插入、删除、查找等,画图可以帮助我们清晰地展示算法的执行步骤和过程。通过画图,我们可以模拟算法的执行,预测结果,并发现潜在的错误或问题。这种可视化的方法对于复杂算法的设计和实现尤为重要。

提高调试效率:在链表操作中,很容易出现指针错误,如野指针、空指针解引用、内存泄漏等。通过画图,我们可以跟踪链表的状态变化,检查指针的指向是否正确,从而快速定位和解决这些问题。此外,画图还可以帮助我们理解程序的执行流程,找出逻辑上的错误或不合理之处。

促进团队合作与交流:在团队开发项目中,链表等数据结构的使用和操作往往是必不可少的。通过画图,我们可以将链表的结构和算法的执行过程清晰地展示给团队成员,促进彼此之间的理解和交流。这种可视化的沟通方式有助于提高团队的工作效率,减少误解和错误。

辅助教学与学习:对于初学者来说,链表等复杂数据结构可能难以理解和掌握。通过画图的方式,教师可以更加直观地讲解链表的结构和操作方法,帮助学生建立正确的认知模型。而学生也可以通过画图来巩固所学知识,加深对链表等数据结构的理解。

二、画图+代码分析

需求:动态链表实现对整型数据的增删改查。

创建头文件,定义如下:

typedef int data_type;

struct node{
    data_type data;
    struct node *next;
};

typedef struct node listnode;

typedef enum{
    OK = 0,
    HEAD_NULL,
    LIST_MEMORY_ERROR,
    LIST_EMPTY,
    NO_SUCH_ELEMENT,

}ERRORNUM;

2.1、创建头结点

如图所示:创建头结点

步骤:
①:在堆上申请struct node大小的一片地址空间;
②:将数据域清零,下一个节点指针域指向NULL;
③:返回头结点。

listnode *create_head(void)
{
    listnode *pnode = (listnode *)malloc(sizeof(listnode));
    if(pnode == NULL){
        perror("create_head_fail");
        return NULL;
    }

    pnode->data = 0;
    pnode->next = NULL;

    return pnode;
}

2.2、插入结点

插入方式:头插

①:先创建一个结点指针变量,malloc申请空间;
②:将头结点后的结点(有效结点)赋值给当前要插入的结点的next域,目的在于保护尾部结点
        代码:pnew->next = head->next;
③:将当前插入的新节点赋值给head->next域    代码:head->next = pnew;
④:将插入的数据通过形参传值的方式,赋值给pnew->data域。

int insert_head(listnode *head, data_type data)
{
    if(head == NULL){
        return HEAD_NULL;
    }

    listnode *pnew = (listnode *)malloc(sizeof(listnode));
    if(pnew == NULL){
        return LIST_MEMORY_ERROR;
    }

    pnew->next = head->next;
    head->next = pnew;
    pnew->data = data;

    return OK;
}

2.3、遍历打印结点元素

①:将头结点通过形参传进来;

②:入参检测:<1.>判断头结点是否为空;<2.>判断头结点后的首节点是否为空(因为其存的是有效数据)。如果只有头结点,没有有效数据节点,则遍历无意义;

③:定义listnode结构体指针变量current,将头结点后的首结点赋值给它。因为:

        1、简化遍历过程:使用current指针可以在遍历链表时不必每次都从头节点开始。通过移动current指针,可以逐个访问链表中的每个节点,直到到达链表的末尾(即current为NULL)。

        2、保持链表结构的完整性:直接操作头指针(如head)来遍历链表可能会不小心改变链表的头部,尤其是在某些复杂的操作中(如删除头节点)。使用current这样的临时指针可以避免这种风险,因为它只是指向链表中的一个节点,而不是链表的头部。

        3、提高代码的可读性和可维护性:使用明确的变量名(current)来表示当前正在处理的节点,可以使代码更加清晰易懂。这对于维护代码和与他人协作非常有帮助。

④:循环打印结点元素。

int list_show(listnode *head)
{
    if(head == NULL || head->next == NULL){
        return LIST_EMPTY;
    }

    listnode *current = head->next;

    // 注意,遍历链表时,判断条件式永远是当前的结点
    // 不是当前结点的下一个结点,否则在打印时会丢失最后一个结点
    while(current != NULL){ 
        printf("%d ", current->data);
        current = current->next;
    }
    putchar(10);

    return OK;
}

2.4、删除结点

①:入参检测:<1.>判断头结点是否为空;<2.>判断头结点后的首节点是否为空(因为其存的是有效数据);

②:定义listnode结构体指针变量pnode,将头结点赋值给它。因为:保持链表结构的完整性:直接操作头指针(如head)来遍历链表可能会不小心改变链表的头部;

③:定义listnode结构体指针变量temp。因为:

        1、保护链表结构:要删除链表中的一个节点时,需要先找到该节点的前一个节点(假设不是删除头节点)。然后,需要将前一个节点的next指针绕过被删除的节点,直接指向被删除节点的下一个节点。如果直接操作被删除节点的next指针来修改链表,那么可能会丢失对被删除节点下一个节点的引用,从而可能导致内存泄漏或无法访问链表的剩余部分。

        2、安全地释放内存:在C语言中,动态分配的内存(如使用malloc或calloc等函数分配的内存)在使用完毕后应该被释放,以避免内存泄漏。通过定义一个temp指针来指向被删除的节点,可以在被删除节点从链表中移除之前安全地获取其地址,并使用free函数释放该节点的内存。

        3、避免复杂的条件语句:在某些情况下,特别是在处理头节点或尾节点删除时,直接修改链表可能会引入复杂的条件语句来区分不同的情况。通过使用temp指针,可以编写更清晰、更易于维护的代码来处理这些情况。

int list_delete(listnode *head, data_type data)
{
    if(head == NULL || head->next ==NULL){
        return LIST_EMPTY;
    }

    listnode *phead = head;
    listnode *temp;
    while(phead != NULL){
        if(phead->next->data == data){
            temp = phead->next;
            phead->next = phead->next->next;
            free(temp);
            return OK;
        }
        phead = phead->next;
    }

    return NO_SUCH_ELEMENT;
}

2.5、修改节点元素

①:入参检测:
        <1.>判断头结点是否为空;
        <2.>判断头结点后的首节点是否为空(因为其存的是有效数据)。
如果只有头结点,没有有效数据节点,则修改无意义;
②:定义listnode结构体指针变量current,将头结点后的首结点赋值给它。原因这里不再赘述;
③遍历,将旧值赋值给新值。

int list_modify(listnode *head, data_type old_data, data_type new_data)
{
    if(head == NULL || head->next == NULL){
        return LIST_EMPTY;
    }
    
    listnode *current = head->next;
    while(current != NULL){
        if(current->data == old_data){
            current->data = new_data;
            return OK;
        }
        current = current->next;
    }

    return NO_SUCH_ELEMENT;
}

2.6、查询结点元素

①:入参检测:
        <1.>判断头结点是否为空;
        <2.>判断头结点后的首节点是否为空(因为其存的是有效数据)。
如果只有头结点,没有有效数据节点,则修改无意义。
②:定义listnode结构体指针变量current,将头结点后的首结点赋值给它。
原因这里不再赘述。
③遍历,将找到的结点值打印

int list_search(listnode *head, data_type data)
{
    if(head == NULL || head->next == NULL){
        return LIST_EMPTY;
    }
    int count = 0;
    listnode *current = head->next;
    while(current != NULL){
        if(current->data == data){
            printf("The element->%d is located at the %d node\n", data, count+1);
            return OK;
        }
        count++;
        current = current->next;
    }

    return NO_SUCH_ELEMENT;
}

三、源码

3.1、目录结构

主目录Makefile

ALL:
	make -C ./src/
	make -C ./obj/

.PHONY: clean
clean:
	rm obj/*.o
	rm bin/*

3.2、include目录

test.h

#ifndef _TEST_H_
#define _TEST_H_
#include <stdio.h>
#include <stdlib.h>


typedef int data_type;

struct node{
    data_type data;
    struct node *next;
};

typedef struct node listnode;

typedef enum{
    OK = 0,
    HEAD_NULL,
    LIST_MEMORY_ERROR,
    LIST_EMPTY,
    NO_SUCH_ELEMENT,

}ERRORNUM;

listnode *create_head(void);

int insert_head(listnode *head, data_type data);

int list_show(listnode *head);

int list_delete(listnode *head, data_type data);

int list_modify(listnode *head, data_type old_data, data_type new_data);

int list_search(listnode *head, data_type data);

#endif

3.3、obj目录

Makefile

ALL:
	gcc *.o -o ../bin/app

3.4、src目录

crud.c

#include "../include/test.h"

listnode *create_head(void)
{
    listnode *pnode = (listnode *)malloc(sizeof(listnode));
    if(pnode == NULL){
        perror("create_head_fail");
        return NULL;
    }

    pnode->data = 0;
    pnode->next = NULL;

    return pnode;
}


int insert_head(listnode *head, data_type data)
{
    if(head == NULL){
        return HEAD_NULL;
    }

    listnode *pnew = (listnode *)malloc(sizeof(listnode));
    if(pnew == NULL){
        return LIST_MEMORY_ERROR;
    }

    pnew->next = head->next;
    head->next = pnew;
    pnew->data = data;

    return OK;
}



int list_show(listnode *head)
{
    if(head == NULL || head->next == NULL){
        return LIST_EMPTY;
    }

    listnode *current = head->next;

    // 注意,遍历链表时,判断条件式永远是当前的结点
    // 不是当前结点的下一个结点,否则在打印时会丢失最后一个结点
    while(current != NULL){ 
        printf("%d ", current->data);
        current = current->next;
    }
    putchar(10);

    return OK;
}

int list_delete(listnode *head, data_type data)
{
    if(head == NULL || head->next ==NULL){
        return LIST_EMPTY;
    }

    listnode *phead = head;
    listnode *temp;
    while(phead != NULL){
        if(phead->next->data == data){
            temp = phead->next;
            phead->next = phead->next->next;
            free(temp);
            return OK;
        }
        phead = phead->next;
    }

    return NO_SUCH_ELEMENT;
}

int list_modify(listnode *head, data_type old_data, data_type new_data)
{
    if(head == NULL || head->next == NULL){
        return LIST_EMPTY;
    }
    
    listnode *current = head->next;
    while(current != NULL){
        if(current->data == old_data){
            current->data = new_data;
            return OK;
        }
        current = current->next;
    }

    return NO_SUCH_ELEMENT;
}

int list_search(listnode *head, data_type data)
{
    if(head == NULL || head->next == NULL){
        return LIST_EMPTY;
    }
    int count = 0;
    listnode *current = head->next;
    while(current != NULL){
        if(current->data == data){
            printf("The element->%d is located at the %d node\n", data, count+1);
            return OK;
        }
        count++;
        current = current->next;
    }

    return NO_SUCH_ELEMENT;
}

main.c

#include "../include/test.h"

int main()
{
    int ret = 0;
    listnode *head = NULL;
    head = create_head();
    if(head == NULL){
        return -1;
    }

    head = create_head();

    ret = insert_head(head, 15);
    ret = insert_head(head, 10);
    ret = insert_head(head, 5);

    ret = list_show(head);


    //ret = list_delete(head, 5);
    //et = list_show(head);

    //ret = list_modify(head, 30, 8);
    //ret = list_show(head);

    ret = list_search(head, 20);

    switch(ret){
    /* 通常不需要打印"成功"信息,但可以根据需要添加
    case SHOW_OK:
        printf("SHOW_OK\n");
    break;
    */
    case HEAD_NULL:
        printf("HEAD_NULL\n");
    break;

    case LIST_MEMORY_ERROR:
        printf("LIST_MEMORY_ERROR\n");
    break;

    case LIST_EMPTY:
        printf("LIST_EMPTY\n");
    break;

    case NO_SUCH_ELEMENT:
        printf("NO_SUCH_ELEMENT\n");
    break;

    }

    return 0;
}

Makefile

ALL:../obj/main.o ../obj/crud.o
../obj/main.o:main.c
	gcc -c $< -o $@
../obj/crud.o:crud.c
	gcc -c $< -o $@

四、ReadMe

比较简单的顺序存储,动态链表
    返回值是通过枚举实现,删改查的函数在main函数里有个小bug,比如删除的元素不存在,错误信息会正常返回,但要是调用显示函数的话,会把错误码覆盖掉,
这就导致看不到错误原因。改查同样如此,如果此程序改为与用于交互的话,这个bug可以很好解决掉。
    程序当中有很多变量名需要优化,不是很见名知意。

五、源码下载

链接:https://pan.baidu.com/s/1ifx7ZCO7mt_HTQ76TUS33w 
提取码:ckr8

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++】C++应用案例-翻转数组

翻转数组&#xff0c;就是要把数组中元素的顺序全部反过来。比如一个数组{1,2,3,4,5,6,7,8}&#xff0c;翻转之后就是{8,7,6,5,4,3,2,1}。 &#xff08;1&#xff09;另外创建数组&#xff0c;反向填入元素 数组是将元素按照顺序依次存放的&#xff0c;长度固定。所以如果想要…

全网最详细Gradio教程系列5——Gradio Client: javascript

全网最详细Gradio教程系列5——Gradio Client: javascript 前言本篇摘要5. Gradio Client的三种使用方式5.2 使用Gradio JavaScript Client5.2.1 安装1. npm方式2. CDN方式3. 在线运行环境&#xff1a;PLAYCODE 5.2.2 连接到Gradio程序1. 通过URL或SpaceID连接2. 辅助&#xff…

RuoYi-Vue-Plus (多数据源注解使用、【手动、拦截器】切换数据源)

接上文多数据源配置&#xff1a; RuoYi-Vue-Plus (多数据源配置)-CSDN博客 一、功能演示 代码生成菜单页面&#xff0c; 展示数据源切换 查询主库 查询从库 二、前端传参切换数据源 页面路径&#xff1a; src/views/tool/gen/index.vue 搜索框如下&#xff1a;下面4发送请求时…

SPICE | 常见电路SPICE模型总结

Ref. 1. CMOS VLSI Design: A Circuits and Systems Perspective 目录 0 基础 1 反相器 inverter 2 缓存器 buffer 3 NAND 4 NOR 5 传输门 Transmission gate 6 三态反相器 Tristate Inverter 7 选择器 Multiplexers 8 D锁存器 D Latch 9 D触发器 D Flip-Flop 0 基础…

Linux文件描述符

前言 我们以前就听过"Linux下一切皆文件"&#xff0c;但是说实话我们只是记住了这句话&#xff0c;实质是不理解的&#xff01;本期我们就会解释&#xff01; 本期内容介绍 • 回顾C语言文件操作 • 系统I/O操作接口 • 文件描述符fd • 理解Linux下一切皆文件 • …

如何设置postgresql数据库的账户密码

说明&#xff1a;在我的云服务器上&#xff0c;postgres是使用yum的方式安装的&#xff0c;不需要设置postgres账户的密码&#xff0c;本文介绍安装后如何手动设置postgres账户的密码&#xff1b; postgres数据库安装&#xff0c;参考下面这篇文章&#xff1a; PostgreSQL安装…

构建基于Spring Boot的SaaS应用

引言 在设计和实现SaaS系统时&#xff0c;安全性是至关重要的考虑因素。一个全面的安全策略不仅能保护系统免受恶意攻击&#xff0c;还能确保用户数据的机密性、完整性和可用性。本文将探讨在SaaS架构中实现数据加密、敏感信息保护以及应用安全的最佳实践和技术方案&#xff0…

【大模型】基于LoRA微调Gemma大模型(1)

文章目录 一、LoRA工作原理1.1 基本原理1.2 实现步骤 二、LoRA 实现2.1 PEFT库&#xff1a;高效参数微调LoraConfig类&#xff1a;配置参数 2.2 TRL库SFTTrainer 类 三、代码实现3.1 核心代码3.2 完整代码 参考资料 大模型微调技术有很多&#xff0c;如P-Tuning、LoRA 等&#…

Vue3计算属性终极实战:可媲美Element Plus Tree组件研发之节点勾选

前面完成了JuanTree组件的节点编辑和保存功能后&#xff0c;我们把精力放到节点勾选功能实现上来。**注意&#xff0c;对于组件的开发者来说&#xff0c;要充分考虑用户的使用场景&#xff0c;组件提供的多个特性同时启用时必须要工作良好。**就拿Tree组件来说&#xff0c;用户…

数据库(MySQL)-视图、存储过程、触发器

一、视图 视图的定义、作用 视图是从一个或者几个基本表&#xff08;或视图&#xff09;导出的表。它与基本表不同&#xff0c;是一个虚表。但是视图只能用来查看表&#xff0c;不能做增删改查。 视图的作用&#xff1a;①简化查询 ②重写格式化数据 ③频繁访问数据库 ④过…

如何学习Doris:糙快猛的大数据之路(从入门到专家)

引言:大数据世界的新玩家 还记得我第一次听说"Doris"这个名字时的情景吗?那是在一个炎热的夏日午后,我正在办公室里为接下来的大数据项目发愁。作为一个刚刚跨行到大数据领域的新手,我感觉自己就像是被丢进了深海的小鱼—周围全是陌生的概念和技术。 就在这时,我的…

江苏科技大学24计算机考研数据速览,有专硕复试线大幅下降67分!

江苏科技大学&#xff08;Jiangsu University of Science and Technology&#xff09;&#xff0c;坐落在江苏省镇江市&#xff0c;是江苏省重点建设高校&#xff0c;江苏省人民政府与中国船舶集团有限公司共建高校&#xff0c;国家国防科技工业局与江苏省人民政府共建高校 &am…

pyqt designer使用spliter

1、在designer界面需要使用spliter需要父界面不使用布局&#xff0c;减需要分割两个模块选中&#xff0c;再点击spliter分割 2、在分割后&#xff0c;再对父界面进行布局设置 3、对于两边需要不等比列放置的&#xff0c;需要套一层 group box在最外层进行分割

Linux系统:date命令

1、命令详解&#xff1a; date 命令可以用来显示或设定系统的日期与时间。 2、官方参数&#xff1a; -d, --dateSTRING 通过字符串显示时间格式&#xff0c;字符串不能是now。-f, --fileDATEFILE 类似 --date 在 DATEFILE 的每一行生效-I[FMT], --iso-8601[FMT…

Redis的使用场景、持久化方式和集群模式

1. Redis的使用场景 热点数据的缓存 热点数据&#xff1a;频繁读取的数据 限时任务的操作。比如短信验证码 完成session共享的问题。因为前后端分离 完成分布式锁 商品的销售量 2. Redis的持久化方式 2.1 什么是持久化 把内存中的数据存储到磁盘的过程。同时也可以把磁盘中…

Python中的Numpy库使用方法

numpy Ndarry和创建数组的方式 NumPy数组&#xff08;ndarray&#xff09;是NumPy库的核心数据结构&#xff0c;它是一系列同类型数据的集合&#xff0c;以 0 下标为开始进行集合中元素的索引。 ndarray本质上是一个存放同类型元素的多维数组&#xff0c;其中的每个元素在内存…

TransformerEngine

文章目录 一、关于 TransformerEngine &#xff1f;亮点 二、使用示例PyTorchJAXFlax 三、安装先决条件Dockerpip从源码使用 FlashAttention-2 编译 四、突破性的变化v1.7: Padding mask definition for PyTorch 五、FP8 收敛六、集成七、其它贡献论文视频最新消息 一、关于 Tr…

美团大众点评字符验证码

声明(lianxi a15018601872) 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 前言(…

为什么优秀员工往往最先离职?

在企业管理中有很多误区&#xff0c;令企业流失优秀员工和人才&#xff0c;根据优思学院过往的经验&#xff0c;大致可以分为以下几个情况。 1. 忽视帕累托法则&#xff08;80/20法则&#xff09; 帕累托法则&#xff08;80/20法则&#xff09;是六西格玛管理的基本原则&…

好的STEM编程语言有哪些?

STEM是科学&#xff08;Science&#xff09;&#xff0c;技术&#xff08;Technology&#xff09;&#xff0c;工程&#xff08;Engineering&#xff09;&#xff0c;数学&#xff08;Mathematics&#xff09;四门学科英文首字母的缩写&#xff0c;STEM教育简单来说就是在通过在…