C语言进阶(八)—— 链表

news2024/11/23 7:09:59

1. 链表基本概念

1.1 什么是链表

  • 链表是一种常用的数据结构,它通过指针将一些列数据结点,连接成一个数据链。相对于数组,链表具有更好的动态性(非顺序存储)。

  • 数据域用来存储数据,指针域用于建立与下一个结点的联系。

  • 建立链表时无需预先知道数据总量的,可以随机的分配空间,可以高效的在链表中的任意位置实时插入或删除数据。

  • 链表的开销,主要是访问顺序性和组织链的空间损失。

数组和链表的区别:

数组:一次性分配一块连续的存储区域。

优点:随机访问元素效率高

缺点:1) 需要分配一块连续的存储区域(很大区域,有可能分配失败)

2) 删除和插入某个元素效率低

链表:无需一次性分配一块连续的存储区域,只需分配n块节点存储区域,通过指针建立关系。

优点:1) 不需要一块连续的存储区域

2) 删除和插入某个元素效率高

缺点:随机访问元素效率低

1.2 有关结构体的自身引用

问题1:请问结构体可以嵌套本类型的结构体变量吗?

问题2:请问结构体可以嵌套本类型的结构体指针变量吗?

typedef struct _STUDENT{
    char name[64];
    int age;
}Student;

typedef struct _TEACHER{
    char name[64];
    Student stu; //结构体可以嵌套其他类型的结构体
    //Teacher stu;
    //struct _TEACHER teacher; //此时Teacher类型的成员还没有确定,编译器无法分配内存
    struct _TEACHER* teacher; //不论什么类型的指针,都只占4个字节,编译器可确定内存分配
}Teacher;
  • 结构体可以嵌套另外一个结构体的任何类型变量;

  • 结构体嵌套本结构体普通变量(不可以)。本结构体的类型大小无法确定,类型本质:固定大小内存块别名;

  • 结构体嵌套本结构体指针变量(可以), 指针变量的空间能确定,32位, 4字节、 64位, 8字节;

1.3 链表节点

大家思考一下,我们说链表是由一系列的节点组成,那么如何表示一个包含了数据域和指针域的节点呢?

链表的节点类型实际上是结构体变量,此结构体包含数据域和指针域:

  • 数据域用来存储数据;

  • 指针域用于建立与下一个结点的联系,当此节点为尾节点时,指针域的值为NULL

typedef struct Node 
{
    //数据域
    int id;
    char name[50];

    //指针域
    struct Node *next;       
}Node;

1.4 链表的分类

链表分为:静态链表和动态链表

静态链表和动态链表是线性表链式存储结构的两种不同的表示方式:

  • 所有结点都是在程序中定义的,不是临时开辟的,也不能用完后释放,这种链表称为“静态链表”。

  • 所谓动态链表,是指在程序执行过程中从无到有地建立起一个链表,即一个一个地开辟结点和输入各结点数据,并建立起前后相链的关系。

1.4.1 静态链表

typedef struct Stu
{
    int id;    //数据域
    char name[100];

    struct Stu *next; //指针域
}Stu;

void test()
{
    //初始化三个结构体变量
    Stu s1 = { 1, "yuri", NULL };
    Stu s2 = { 2, "lily", NULL };
    Stu s3 = { 3, "lilei", NULL };

    s1.next = &s2; //s1的next指针指向s2
    s2.next = &s3;
    s3.next = NULL; //尾结点

    Stu *p = &s1;
    while (p != NULL)
    {
        printf("id = %d, name = %s\n", p->id, p->name);

        //结点往后移动一位
        p = p->next; 
    }
}

1.4.2 动态链表

typedef struct Stu{
    int id;    //数据域
    char name[100];

    struct Stu *next; //指针域
}Stu;

void test(){
    //动态分配3个节点
    Stu *s1 = (Stu *)malloc(sizeof(Stu));
    s1->id = 1;
    strcpy(s1->name, "yuri");

    Stu *s2 = (Stu *)malloc(sizeof(Stu));
    s2->id = 2;
    strcpy(s2->name, "lily");

    Stu *s3 = (Stu *)malloc(sizeof(Stu));
    s3->id = 3;
    strcpy(s3->name, "lilei");

    //建立节点的关系
    s1->next = s2; //s1的next指针指向s2
    s2->next = s3;
    s3->next = NULL; //尾结点

    //遍历节点
    Stu *p = s1;
    while (p != NULL)
    {
        printf("id = %d, name = %s\n", p->id, p->name);

        //结点往后移动一位
        p = p->next; 
    }

    //释放节点空间
    p = s1;
    Stu *tmp = NULL;
    while (p != NULL)
    {
        tmp = p;
        p = p->next;

        free(tmp);
        tmp = NULL;
    }
}

1.4.3 带头和不带头链表

  • 带头链表:固定一个节点作为头结点(数据域不保存有效数据),起一个标志位的作用,以后不管链表节点如果改变,此头结点固定不变

  • 不带头链表:头结点不固定,根据实际需要变换头结点(如在原来头结点前插入新节点,然后,新节点重新作为链表的头结点)。

1.4.4 单向链表、双向链表、循环链表

单向链表:

双向链表:

循环链表:

2. 链表基本操作

2.1 创建链表

使用结构体定义节点类型:

typedef struct _LINKNODE
{
    int id; //数据域
    struct _LINKNODE* next; //指针域
}link_node;

编写函数:link_node* init_linklist()

建立带有头结点的单向链表,循环创建结点,结点数据域中的数值从键盘输入,以 -1 作为输入结束标志,链表的头结点地址由函数值返回。

typedef struct _LINKNODE{
    int data;
    struct _LINKNODE* next;
}link_node;

link_node* init_linklist(){
    
    //创建头结点指针
    link_node* head = NULL;
    //给头结点分配内存
    head = (link_node*)malloc(sizeof(link_node));
    if (head == NULL){
        return NULL;
    }
    head->data = -1;
    head->next = NULL;

    //保存当前节点
    link_node* p_current = head;
    int data = -1;
    //循环向链表中插入节点
    while (1){
    
        printf("please input data:\n");
        scanf("%d",&data);

        //如果输入-1,则退出循环
        if (data == -1){
            break;
        }

        //给新节点分配内存
        link_node* newnode = (link_node*)malloc(sizeof(link_node));
        if (newnode == NULL){
            break;
        }

        //给节点赋值
        newnode->data = data;
        newnode->next = NULL;

        //新节点入链表,也就是将节点插入到最后一个节点的下一个位置
        p_current->next = newnode;
        //更新辅助指针p_current
        p_current = newnode;
    }

    return head;
}

2.2 遍历链表

编写函数:void foreach_linklist(link_node* head)

顺序输出单向链表各项结点数据域中的内容:

//遍历链表
void foreach_linklist(link_node* head){
    if (head == NULL){
        return;
    }

    //赋值指针变量
    link_node* p_current = head->next;
    while (p_current != NULL){
        printf("%d ",p_current->data);
        p_current = p_current->next;
    }
    printf("\n");
}

2.3 插入节点

编写函数: void insert_linklist(link_node* head,int val,int data)

在指定值后面插入数据data,如果值val不存在,则在尾部插入:li

//在值val前插入节点
void insert_linklist(link_node* head, int val, int data){
    
    if (head == NULL){
        return;
    }

    //两个辅助指针
    link_node* p_prev = head;
    link_node* p_current = p_prev->next;
    while (p_current != NULL){
        if (p_current->data == val){
            break;
        }
       //两个辅助指针向后
        p_prev = p_current;
        p_current = p_prev->next;
    }

    //如果p_current为NULL,说明不存在值为val的节点
    //if (p_current == NULL){
    //    printf("不存在值为%d的节点!\n",val);
    //    return;
    //}

    //创建新的节点
    link_node* newnode = (link_node*)malloc(sizeof(link_node));
    newnode->data = data;
    newnode->next = NULL;

    //新节点入链表
    newnode->next = p_current;
    p_prev->next = newnode;
}

2.4 删除节点

编写函数: void remove_linklist(link_node* head,int val)

删除第一个值为val的结点:

//删除值为val的节点
void remove_linklist(link_node* head,int val){
    if (head == NULL){
        return;
    }

    //辅助指针
    link_node* p_prev = head;
    link_node* p_current = p_prev->next;

    //查找值为val的节点
    while (p_current != NULL){
        if (p_current->data == val){
            break;
        }
        p_prev = p_current;
        p_current = p_prev->next;
    }
    //如果p_current为NULL,表示没有找到
    if (p_current == NULL){
        return;
    }
    
    //删除当前节点: 重新建立待删除节点(p_current)的前驱后继节点关系
    p_prev->next = p_current->next;
    //释放待删除节点的内存
    free(p_current);
}

2.5 销毁链表

编写函数: void destroy_linklist(link_node* head)

销毁链表,释放所有节点的空间:

//销毁链表
void destroy_linklist(link_node* head){
    if (head == NULL){
        return;
    }
    //赋值指针
    link_node* p_current = head;
    while (p_current != NULL){
        //缓存当前节点下一个节点
        link_node* p_next = p_current->next;
        free(p_current);
        p_current = p_next;
    }
}

2.6 反转链表

编写函数: void reverse_linklist(link_node* head)

反转链表通过3个辅助指针变量实现链表的翻转:

void reverse_linklist(link_node* head){
    if (head == NULL){
        return;
    }
    //辅助指针
    link_node* p_prev = NULL;
    link_node* p_current = head->next;
    link_node* p_next = NULL;
    while (p_current != NULL){
        p_next = p_current->next;
        //更改指针指向
        p_current->next = p_prev;
        //移动辅助指针
        p_prev = p_current;
        p_current = p_next;
    }
    //更新头结点
    head->next = p_prev;
}

2.7 统计链表长度

编写函数: int size_linklist(link_node* head)

int size_linklist(link_node* head){
    if (head == NULL){
        return -1;
    }
    //临时指针变量执行第一个真实数据的结点
    link_node* p_current = head->next;
    //记录结点个数
    int num = 0;
    while (p_current != null) {
        num++;
        p_current = p_current->next;
    }
    return num;
}

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

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

相关文章

【CKA】

— k8s basic — 安装版本信息查询 命令行自动补全功能设置 01. Namespaces and Pods 02. Assigning Pods to Nodes 03. Basic Deployments 04. Advanced Deployment 05. Jobs, Init Containers and Cron Jobs 06. Manage DaemonSets 07. Manage Services 08. Manage Ingress …

【技术】雷达液位计设备安装方案

一、设备概述 雷达液位计为我司自主研发,采用FMCW技术,以24G毫米雷达波作为载波信号,该产品测量精度高、功耗低、体积小、重量轻;测量过程不受温度、气压、泥沙、灰尘、河流污染物、水面漂浮物、空气等环境因素的影响&#xff0c…

spring boot项目中i18n和META-INF.spring下的文件的作用

目录标题一、resource下的文件二、i18n下messages_zh_CN.properties三、spring.factories文件四、org.springframework.boot.autoconfigure.AutoConfiguration.imports一、resource下的文件 org.springframework.boot.autoconfigure.AutoConfiguration.imports ; - …

微信小程序日记、微信小程序个人空间、个人日记

一.简述 个人比较喜欢微信小程序,因为小程序所追求的用户体验、代码质量、美观的样式,简单方便丰富的api、样式封装等,同时又与普通的前端开发非常相似,让人很容易就上手。 这篇博客介绍的是一款记录个人/家庭日常记录的微信小程…

VsCode开发工具的入门及基本使用

VsCode开发工具的入门及基本使用一、VsCode介绍1.VsCode简介2.VsCode特点二、安装VsCode1.下载VsCode2.安装VsCode3.打开VsCode三、设置VsCode中文1.搜索中文语言插件2.安装中文语言插件四、初识VsCode1.VsCode左侧栏模块2.系统设置功能五、VsCode初始配置1.禁用自动更新2.开启…

MQTT 5协议你知道多少?

一、MQTT 5简介 MQTT协议是当今世界上最流行、接受度最高的物联网协议。自推出以来,MQTT已经成功地连接了各种规模的部署中的无数受限设备。 流行的用例包括从连接汽车、制造系统、物流和军事到企业聊天应用程序和移动应用程序。MQTT协议的广泛使用催生了进一步发…

【离线数仓-5-数据仓库环境准备】

离线数仓-5-数据仓库环境准备离线数仓-5-数据仓库环境准备1.数据仓库运行环境1.Hive环境搭建1.Hive引擎2.Hive on Spark配置2.Yarn环境配置2.数据仓库开发环境3.模拟数据准备离线数仓-5-数据仓库环境准备 1.数据仓库运行环境 数仓之外需要做的事情: 数据安全认证&…

OSCP-课外2(git泄露、SQL注入)

难度 中 主机发现&端口扫描 sudo arp-scan -l sudo nmap -p- 192.168.65.128 sudo nmap -p22,80 -sC -sV 192.168.65.128 首先,发现了.git目录,可能通过代码审计发现漏洞的存在。 其次,发现了一个描述“login.php修改的更安全”,说明以前login.php版本可能存在安全…

活动预告 | 2023 Meet TVM 开年首聚,上海我们来啦!

内容一览:从去年 12 月延期至今的 TVM 线下聚会终于来了!首站地点我们选在了上海,并邀请到了 4 位讲师结合自己的工作实践,分享 TVM 相关的开发经验,期待与大家线下相聚~ 关键词:2023 Meet TVM 线下活动 自…

操作系统(day15) -- I/O设备

I/O设备的基本概念与分类 I/O设备就是可以将数据输入到计算机,或者可以接收计算机输出数据的外部设备,属于计算机中的硬件部分。 I/O设备按使用特性可以分为以下类型: 人机交互类设备。用于与计算机用户之间交互的设备,如打印机、…

华为OD机试用Python实现 -【组合出合法最小数】(2023-Q1 新题)

华为OD机试300题大纲 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查看地址:blog.csdn.net/hihell/category_12199275.html 华为OD详细说明:https://dream.blog.csdn.net/article/details/128980730 组合出合法最小数…

原子化 CSS 实践

原子化 CSS 实践 jcLee95 的CSDN 博客 邮箱 :291148484163.com CSDN 主页:https://blog.csdn.net/qq_28550263?spm1001.2101.3001.5343 本文地址:https://blog.csdn.net/qq_28550263/article/details/129178547 目 录1. 概述 2. 原子化…

Flutter+【三棵树】

定义 在Flutter中和Widgets一起协同工作的还有另外两个伙伴:Elements和RenderObjects;由于它们都是有着树形结构,所以经常会称它们为三棵树。 这三棵树分别是:Widget、Element、RenderObject Widget树:寄存烘托内容…

代码随想录---二叉树的总结和二叉树的定义

二叉树的种类: 满二叉树:树的所有节点都是满,即都有左右孩子。 这棵二叉树为满二叉树,也可以说深度为k,有2^k-1个节点的二叉树。 完全二叉树:完全二叉树的定义如下:在完全二叉树中&#xff0c…

数据结构—堆(完全解析)

数据结构—堆(完全解析) 数据结构——堆(Heap)大根堆、小根堆 详解数据结构——堆 堆的基本存储 【从堆的定义到优先队列、堆排序】 10分钟看懂必考的数据结构——堆 【堆/排序】堆排序的两种建堆方法 【算法】排序算法之堆排序 C…

【Tips】通过背数据了解业务

学习资料:做了三年数据分析,给你的几点建议 1. 通过背数据了解业务 原文: 总结: 方法:每天早上去到公司第一件事情就是先背一遍最新的各种指标。原理: 数据敏感性就是建立在对数据的了解和熟悉上。业务的…

使用Autoware标定工具包联合标定相机和激光雷达

前面文章介绍了,安装autoware标定工具包、ros驱动usb相机、robosense-16线激光雷达的使用,本文记录使用Autoware标定工具包联合标定相机和激光雷达的过程。1.ros驱动相机,启动相机;启动激光雷达2.联合录制bag包rosbag record -a 参…

由浅入深了解超文本传输协议http

什么是超文本传输协议? 超文本传输协议(英语:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础。 通过HTTP或HTTPS协议请求…

8年测开经验面试28K公司后,吐血整理出1000道高频面试题和答案

1、python的数据类型有哪些 答:Python基本数据类型一般分为:数字、字符串、列表、元组、字典、集合这六种基本数据类型。 浮点型、复数类型、布尔型(布尔型就是只有两个值的整型)、这几种数字类型。列表、元组、字符串都是序列。 2、列表和元组的区别 答…

postgresql 数据库 主从切换 测试

postgresql 数据库 主从切换 测试 文章目录postgresql 数据库 主从切换 测试前言环境:主从切换1. 查看数据库状态:2. 备库切换主库3. 旧主库切换成备库;4 查看状态后记前言 因数据库等保需要,需要对老系统的数据库进行主从切换来…