数据结构-单链表(C语言简单实现)

news2025/1/22 9:24:42

简介

以顺序结构进行数据存储时,它的特点就是可以用一组任意的存储单元存储数据元素,这组存储单元可以是连续的,也可以是不连续的,这些数据可以存在内存未被占用的任意位置。它也是有缺点的,就是在插入和删除时需要移动大量的元素,需要耗费一点时间

以下是它的一个示意图

单链表

想要创建这样的数据结构,首先需要使用结构体先定义一个节点:

typedef struct node
{
    int data;  // 数据
    struct node *next;  // 指向下一个节点的地址
}node_t;

然后需要使用结构体定义单链表:

typedef struct list
{
    struct node *head;  // 头部
    struct node *tail;  // 尾部
}list_t;

相关函数

  • 首先需要定义一个静态函数用于创建节点
static node_t *create_node(int data)
{
    node_t *pnew = malloc(sizeof(node_t));  // 分配内存空间
    pnew->data  = data;  // 分配数据
    pnew->next = NULL;  // 默认指向空
    return pnew;  // 返回地址
}
  • 初始化函数

图片.png

void list_init(list_t *plist)
{
    plist->head = create_node(0);  // 为头部创建一个节点
    plist->tail = create_node(0);  // 为尾部创建一个节点
    plist->head->next = plist->tail;  // 头部的next指向尾部
    plist->tail->next = NULL;  // 尾部的next指向NULL
}
  • 释放单链表的函数

图片.png

void list_deinit(list_t *plist)
{
    node_t *pnode = plist->head;  // 将pnode指向头部
    // 当pnode非空时,进行循环,说明还有数据
    while(pnode)
    {
        node_t *ptmp = pnode->next;  // 备份pnode的next
        free(pnode);  // 释放pnode
        pnode = ptmp;  // 将之前pnode的next指向现在的pnode
    }
}
  • 遍历单链表

图片.png

void list_travel(list_t *plist)
{
    // 遍历: 首先将pnode指向head,直到pnode指向tail结束,pnode每次往后移一位
    for(node_t *pnode = plist->head; pnode != pnode->tail; pnode = pnode->next)
    {
        // 三个游标
        node_t *pfirst = pnode;
        node_t *pmid = pfirst->next;
        node_t *plast = pmid->next;
        if(pmid != pnode->tail)
            printf("%d ", pmid->data);
    }
    printf("\n");
}
  • 按照顺序添加数据到单链表中

图片.png

void list_add(list_t *plist, int data)
{
    // 1. 创建一个新的节点
    node_t *pnew = create_node(data);
    // 2. 遍历链表
    for(node_t *pnode = plist->head; pnode != plist->tail; pnode = pnode->next)
    {
        // 2.1 三个游标
        node_t *pfirst = pnode;
        node_t *pmid = pfirst->next;
        node_t *plast = pmid->next;
        // 2.2 当找到比data大的数据,就插入到它后面,或者找到最后都没找到比它大的,就插到最后
        if(pmid->data > pnew->data || pmid == plist->tail)
        {
            pfirst->next = pnew;
            pnew->next = pmid;
            break;
        }
    }
}
  • 前插函数(将数据插到最前面)

图片.png

void list_add_first(list_t *plist, int data)
{
    // 1. 创建一个新的节点
    node_t *pnew = create_node(data);
    // 2. 备份头部的next
    node_t *ptmp = plist->head->next;
    // 3. 将头部的next指向新节点
    plist->head->next = pnew;
    // 4. 将新节点的next指向之前头部指向next
    pnew->next = ptmp;
}
  • 后插函数(将数据插到最后面)

图片.png

void list_add_last(list_t *plist, int data)
{
    // 1. 创建一个新的节点
    node_t *pnew = create_node(data);
    // 2. 遍历链表
    for(node_t *pnode = plist->head; pnode != plist->tail; pnode = pnode->next)
    {
        // 2.1 三个游标
        node_t *pfirst = pnode;
        node_t *pmid = pfirst->next;
        node_t *plast = pmid->next;
        // 2.2 当pmid执行plist->tail时,插入
        if(pmid == plist->tail)
        {
            pfirst->next = pnew;
            pnew->next = pmid;
            break;
        }
    }
}
  • 删除指定数据所在的节点

图片.png

void list_del(list_t *plist, int data)
{
    // 1. 遍历链表
    for(node_t *pnode = plist->head; pnode != plist->tail; pnode = pnode->next)
    {
        // 1.1 三个游标
        node_t *pfirst = pnode;
        node_t *pmid = pfirst->next;
        node_t *plast = pmid->next;
        // 1.2 当找到数据相等的,并且此数据不是尾节点,因为尾节点是初始化定义的
        if(data == pmid->data && pmid != plist->tail)
        {
            pfirst->next = plast;
            free(pmid);  // 此时pmid就是要删除的那个节点
            break;
        }
    }
}

示例代码

创建三个文件: list.c、list.h、main.c,实现上面的相关函数

  • list.c
#include "list.h"

// 定义分配节点内存函数
static node_t *create_node(int data)
{
    node_t *pnew = malloc(sizeof(node_t));
    pnew->data = data;
    pnew->next = NULL;
    return pnew;
}

// 初始化
void list_init(list_t *plist)
{
    // 1. 给首尾分配内存
    plist->head = create_node(0);
    plist->tail = create_node(0);
    // 2. 头指向尾
    plist->head->next = plist->tail;
    // 3. 尾指向空
    plist->tail->next = NULL;
}

// 释放
void list_deinit(list_t *plist)
{
    // 1. 取到单链表的头部
    node_t *pnode = plist->head;
    // 2. 当头部不为空的时候,说明还有数据,继续循环
    while (pnode)
    {
        // 2.1 备份pnode->next
        node_t *ptmp = pnode->next;
        // 2.2 释放pnode
        free(pnode);
        // 2.3 重新指定pnode是ptmp
        pnode = ptmp;
    }
}

// 遍历数据单链表
void list_travel(list_t *plist)
{
    for (node_t *pnode = plist->head; pnode != plist->tail; pnode = pnode->next)
    {
        // 创建三个游标
        node_t *pfirst = pnode;
        node_t *pmid = pfirst->next;
        node_t *plast = pmid->next;
        // 判断pmid是有效节点
        if (pmid != plist->tail)
        {
            printf("%d ", pmid->data);
        }
    }
    printf("\n");
}

// 按顺序添加数据到单链表中
void list_add(list_t *plist, int data)
{
    // 1. 创建新的节点
    node_t *pnew = create_node(data);
    // 2. 遍历单链表
    for (node_t *pnode = plist->head; pnode != plist->tail; pnode = pnode->next)
    {
        // 2.1 创建三个游标
        node_t *pfirst = pnode;
        node_t *pmid = pfirst->next;
        node_t *plast = pmid->next;
        // 2.2 判断当找到pmid的数据大于等于data或者找到最后都没找到比data大的,就插到最后
        if (pmid->data >= pnew->data || pmid == plist->tail)
        {
            // 2.2.1 放在pmid后面,pfirst的前面
            pfirst->next = pnew;
            pnew->next = pmid;
            break;
        }
    }
}

// 前插函数
void list_add_first(list_t *plist, int data)
{
    // 1. 创建新的节点
    node_t *pnew = create_node(data);
    // 2. 备份头部的next
    node_t *ptmp = plist->head->next;
    // 3. 将头部的next指向新的节点
    plist->head->next = pnew;
    // 4. 将新的节点next指向之前头部的next
    pnew->next = ptmp;
}

// 后插函数
void list_add_last(list_t *plist, int data)
{
    // 1. 创建新的节点
    node_t *pnew = create_node(data);
    // 2. 遍历节点,找到tail前面的节点
    for (node_t *pnode = plist->head; pnode != plist->tail; pnode = pnode->next)
    {
        // 2.1 创建三个游标
        node_t *pfirst = pnode;
        node_t *pmid = pfirst->next;
        node_t *plast = pmid->next;
        // 2.2 当pmid==tail时,说明pfirst是tail前面的节点
        if (pmid == plist->tail)
        {
            // 2.2.1 将pfirst的next指向pnew
            pfirst->next = pnew;
            // 2.2.2 将pnew的next执行tail(pmid)
            pnew->next = pmid;
            break;
        }
    }
}

// 删除指定数字所在所在的节点
void list_del(list_t *plist, int data)
{
    // 1. 遍历单链表
    for (node_t *pnode = plist->head; pnode != plist->tail; pnode = pnode->next)
    {
        // 1.1 三个游标
        node_t *pfirst = pnode;
        node_t *pmid = pfirst->next;
        node_t *plast = pmid->next;
        // 1.2 当找到数据相等的,并且此数据不是尾节点,因为尾节点是初始化定义的
        if (data == pmid->data && pmid != plist->tail)
        {
            // 1.2.1 将pfirst的next指向plast
            pfirst->next = plast;
            // 1.2.2 释放pmid
            free(pmid);
            break;
        }
    }
}

  • list.h声明单链表的相关函数和定义节点和单链表
#ifndef __LIST_H
#define __LIST_H

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

// 定义节点
typedef struct node
{
    int data;
    struct node *next;
} node_t;

// 声明单链表的结构体
typedef struct list
{
    node_t *head; // 保存头节点的地址
    node_t *tail; // 保存尾节点的地址
} list_t;

extern void list_init(list_t *plist);
extern void list_deinit(list_t *plist);
extern void list_travel(list_t *plist);
extern void list_add(list_t *plist, int data);
extern void list_add_first(list_t *plist, int data);
extern void list_add_last(list_t *plist, int data);
extern void list_del(list_t *plist, int data);

#endif
  • main.c主函数使用单链表
#include "list.h"

int main(void)
{
    // 1. 创建单链表
    list_t list;
    // 2. 初始化单链表
    list_init(&list);
    // 3. 插入三个数据10 30 20
    printf("插入三个数据10 30 20,结果应该排好顺序的: ");
    list_add(&list, 10);
    list_add(&list, 30);
    list_add(&list, 20);
    // 4. 循环遍历输出单链表
    list_travel(&list);
    // 5. 在头部插入一个15
    printf("在头部插入15: ");
    list_add_first(&list, 15);
    // 6.遍历输出单链表
    list_travel(&list);
    // 7. 在尾部插入一个1
    printf("在尾部插入一个1: ");
    list_add_last(&list, 1);
    // 8. 遍历输出
    list_travel(&list);
    // 9. 删除一个20
    printf("删除一个20: ");
    list_del(&list, 20);
    // 10. 遍历输出
    list_travel(&list);
    // 11. 释放整个链表
    list_deinit(&list);
    return 0;
}

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

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

相关文章

SpringBoot、Java 使用 Jsoup 解析 HTML 页面

使用 Jsoup 解析 HTML 页面 什么是 Jsoup&#xff1f; Jsoup 是一个用于处理 HTML 页面的 Java 库&#xff0c;它提供了简单的 API&#xff0c;使得从 HTML 中提取数据变得非常容易。无论是获取特定标签的内容还是遍历整个页面的元素&#xff0c;Jsoup 都能轻松胜任。 如何使…

CI/CD流水线实战

不知道为什么&#xff0c;现在什么技术都想学&#xff0c;因为我觉得我遇到了技术的壁垒&#xff0c;大的项目接触不到&#xff0c;做的项目一个字辣*。所以&#xff0c;整个人心浮气躁&#xff0c;我已经得通过每天的骑行和长跑缓解这种浮躁了。一个周末&#xff0c;我再次宅在…

echarts图表的应用

1、echarts的简介 echarts 是国内的一个图表应用插件&#xff0c;只需要下载echarts的js 在js中引入。 echarts 的官网&#xff1a;https://echarts.apache.org/zh/index.html 进去之后点击这里&#xff0c; 点击下载按钮&#xff0c;下载文件&#xff0c;下载后解压&#xf…

如何卖 Click to WhatsApp 广告最有效

2022年&#xff0c;大多数直接面向消费者的品牌都面临相同挑战—— Facebook 和 Instagram 的广告成本大幅增加。Business Insider 报导指出&#xff0c;2021年 Facebook 广告每次点击的平均成本&#xff08;average cost per click&#xff09;达到0.974美元&#xff0c;按年升…

概念解析| 压缩感知:在稀疏的世界中寻找完整的信息

注1:本文系“概念解析”系列之一,致力于简洁清晰地解释、辨析复杂而专业的概念。本次辨析的概念是:压缩感知(Compressed Sensing)。 压缩感知:在稀疏的世界中寻找完整的信息 TI - Research - Applications - Compressed Sensing 一、背景介绍 在信息爆炸的时代,我们每天都…

AWS EKS 集群自动扩容 Cluster Autoscaler

文章目录 一&#xff0c;需求工作需求说明 二&#xff0c;部署精简命令执行1&#xff0c;要求2&#xff0c;查看EC2 Auto Scaling groups Tag3&#xff0c;创建Serviceaccount需要的Policy&#xff0c;Role4&#xff0c;部署Cluster Autoscaler5&#xff0c;验证6&#xff0c;常…

Verilog同步FIFO设计

同步FIFO(synchronous)的写时钟和读时钟为同一个时钟&#xff0c;FIFO内部所有逻辑都是同步逻辑&#xff0c;常常用于交互数据缓冲。 异步FIFO&#xff1a;数据写入FIFO的时钟和数据读出FIFO的时钟是异步的(asynchronous) 典型同步FIFO有三部分组成: &#xff08;1&#xff0…

vue项目的实用性总结

1、mockjs 基本使用 ★ 安装&#xff1a;npm i mockjs。 在src/mock/index.js内容如下&#xff1a; import Mock from mockjs //制订拦截规则 Mock.mock(http://www.0313.com,get,你好啊)记得在main.js中引入一下&#xff0c;让其参与整个项目的运行。 只要发出去的是get类型…

印度货代专线【我国到印度专线有哪些方式】

随着全球贸易的不断发展&#xff0c;我国与印度之间的贸易往来也日益频繁。作为两个人口最多的国家之一&#xff0c;中国和印度之间的货物运输需求不断增长。为了满足这一需求&#xff0c;印度货代专线应运而生&#xff0c;为进出口商提供高效、可靠的货物运输服务。本文将探索…

零零信安:暗网分析报告——Part 4 商业黑客组织,“流星街”的原住

暗网&#xff0c;作为互联网的一部分&#xff0c;充满了神秘而又复杂的活动。更重要的是&#xff0c;其背后的主要参与者——商业黑客和各种有组织的犯罪集团&#xff0c;揭示了这是一个怎样的世界。本报告将试图带您了解这些原住民的身份、行为方式、商业逻辑、受害者以及他们…

JMeter接口自动化测试实例—JMeter引用javaScript

Jmeter提供了JSR223 PreProcessor前置处理器&#xff0c;通过该工具融合了Java 8 Nashorn 脚本引擎&#xff0c;可以执行js脚本以便对脚本进行前置处理。其中比较典型的应用就是通过执行js脚本对前端数据进行rsa加密&#xff0c;如登录密码加密。但在这里我就简单的应用javaScr…

No view found for id 0x7f0901c3 for fragment解决以及线上bug排查技巧

情景再现 开发这么久&#xff0c;不知道你们是否也经历过这样的情况&#xff0c;测试或者用户&#xff0c;反馈app闪退&#xff0c;结果你自己打开开发工具&#xff0c;去调试&#xff0c;一切正常&#xff0c;然后闪退还是存在&#xff0c;只是在开发环境中不能重现。这种情况…

11 - git stash 开发中临时加塞了紧急任务怎么处理

查看所有文章链接&#xff1a;&#xff08;更新中&#xff09;GIT常用场景- 目录 文章目录 开发中临时加塞了紧急任务怎么处理 开发中临时加塞了紧急任务怎么处理 当你此时工作区已经修改了 Readme 文件&#xff0c;然后突然需要解决其他问题&#xff08;紧急问题、新任务&…

ML-fairness-gym入门教学

1、ML-fairness-gym简介 ML-fairness-gym是一个探索机器学习系统长期影响的工具。可以用于评估机器学习系统的公平性和评估静态数据集上针对各种输入的误差度量的差异。开源网站&#xff1a;GitHub - google/ml-fairness-gym 2、安装ML-fairness-gym&#xff08;Windows&…

【贪心】CF1841 D

Codeforces 题意&#xff1a; 思路&#xff1a; 首先模拟一下样例 并没有发现什么 那么就去考虑特殊情况&#xff0c;看看有没有什么启发 考虑一个大区间包含所有小区间的情形&#xff0c;这种情况就是在这么多区间中找出两个区间 换句话说&#xff0c;这么多区间组成一个…

容器虚拟化基础之cgroups/LXC

"你真的&#xff0c;自由了~" 容器虚拟化基础之Cgroups: (1) 什么是cgroups cgroups是 linux 内核提供的一种机制&#xff0c; 这种机制可以根据需求把一系列系统任务及其子任务整合(或分隔)到按资源划分等级的不同组内&#xff0c;从而为系统资源管理提供一个统一…

嵌入式电火花线切割控制系统总体设计

2.1 电火花线切割机床的特点与结构 电火花线切割加工&#xff08; Wire Cut EDM &#xff09;是特种加工中电火花加工方式的一种&#xff0c;是 直接利用电能或热能进行加工的工艺方法。加工基本原理是利用在导丝架固定的轨 道上连续移动电极丝&#xff08;钼丝 / 铜丝&…

Spring框架【IOC详解】

目录 一、前言 1.1.Spring简介 1.2.使用Spring的优点 1.3.Spring组成 二、Spring之IOC详解 2.1.IOC理论推导 2.1.1.IOC分析实现 2.1.2.IOC本质 2.2.Maven项目导入Jar包 2.3.依赖注入 2.3.1.Set注入&#xff08;重点&#xff09; 2.3.2.构造注入 无参构造创建对象 …

STM32F4X-GPIO输入功能使用

STM32F4 GPIO输入模式配置 上一节讲GPIO的时候说到了将GPIO设置成输出模式&#xff0c;并通过将GPIO的电平拉高拉低控制LED灯的例程。GPIO除了用作输出功能之外&#xff0c;还可以用作输入功能。最常用的就是检测按键的输入电平。 硬件设计 本章的硬件是基于正点原子的探索者…