C语言实例_双向链表增删改查

news2025/4/7 15:06:38

一、双向链表介绍

双向链表(Doubly Linked List)是一种常见的数据结构,在单链表的基础上增加了向前遍历的功能。与单向链表不同,双向链表的每个节点除了包含指向下一个节点的指针外,还包含指向前一个节点的指针。

image-20230629101812768

image-20230629101952404

作用和原理:

(1)插入和删除操作:由于双向链表中每个节点都有指向前一个节点的指针,所以在双向链表中进行插入或删除操作时,相对于单向链表更加高效。可以通过修改前后节点的指针来完成插入和删除,而无需遍历链表。

(2)双向遍历:双向链表支持从头部到尾部以及从尾部到头部的双向遍历。这在某些场景下非常有用,例如需要反向查找、删除最后一个节点等。

(3)增加了灵活性:由于每个节点都具有指向前一个节点和后一个节点的指针,双向链表在某些特定场景下更灵活。例如,需要在链表中间插入或删除节点,或者需要修改前一个节点的信息。

双向链表的原理很简单。每个节点由数据域和两个指针组成,其中一个指针指向前一个节点,一个指针指向后一个节点。头节点指向链表的第一个节点,尾节点指向链表的最后一个节点。通过调整节点之间的指针,可以在双向链表中执行插入、删除和遍历等操作。

使用场景:

(1)编辑器中的撤销和重做功能:双向链表可以用于实现撤销和重做功能,每次编辑操作都将其结果存储为一个节点,并使用指针链接起来。通过双向链表,可以方便地在撤销和重做之间进行切换。

(2)浏览器的导航历史:浏览器的导航历史可以使用双向链表来保存已访问的页面,每个页面作为一个节点,并使用指针链接起来,以便进行前进和后退操作。

(3)实现LRU缓存替换算法:LRU缓存中,最近最少使用的数据被淘汰,可以使用双向链表来维护缓存中的数据,最近访问的数据位于链表的头部,最久未访问的数据位于链表的尾部。

(4)实现双向队列:双向链表可以用于实现双向队列(Dequeue),支持在队列的两端进行插入和删除操作。

双向链表提供了更多的灵活性和功能,特别是当需要在双向遍历、频繁的插入和删除操作等场景下使用。在许多常见的数据结构和算法中都有广泛的应用。

二、代码实现

以下是使用C语言实现的完整双向链表代码,包含了链表的创建、增加、删除、修改、排序和插入等功能。代码中封装了一套完整的子函数,以方便使用。

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

// 双向链表节点结构
typedef struct Node {
    int data;              // 数据域
    struct Node* prev;     // 指向前一个节点的指针
    struct Node* next;     // 指向后一个节点的指针
} Node;

// 创建新节点
Node* createNode(int data) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    if (newNode == NULL) {
        printf("Failed to allocate memory for new node\n");
        return NULL;
    }
    
    newNode->data = data;
    newNode->prev = NULL;
    newNode->next = NULL;
    
    return newNode;
}

// 在链表末尾添加节点
void append(Node** head, int data) {
    Node* newNode = createNode(data);
    if (newNode == NULL) {
        return;
    }
    
    if (*head == NULL) {
        *head = newNode;
    } else {
        Node* current = *head;
        
        while (current->next != NULL) {    // 找到链表末尾
            current = current->next;
        }
        
        current->next = newNode;
        newNode->prev = current;
    }
}

// 在链表头部添加节点
void prepend(Node** head, int data) {
    Node* newNode = createNode(data);
    if (newNode == NULL) {
        return;
    }
    
    if (*head == NULL) {
        *head = newNode;
    } else {
        newNode->next = *head;
        (*head)->prev = newNode;
        *head = newNode;
    }
}

// 在指定位置插入节点
void insert(Node** head, int data, int position) {
    if (position < 0) {
        printf("Invalid position\n");
        return;
    }
    
    if (position == 0) {
        prepend(head, data);
        return;
    }
    
    Node* newNode = createNode(data);
    if (newNode == NULL) {
        return;
    }
    
    Node* current = *head;
    int count = 0;
    
    while (count < (position - 1) && current != NULL) {    // 找到插入位置前一个节点
        current = current->next;
        count++;
    }
    
    if (current == NULL) {
        printf("Invalid position\n");
        free(newNode);
        return;
    }
    
    newNode->next = current->next;
    newNode->prev = current;
    
    if (current->next != NULL) {
        current->next->prev = newNode;
    }
    
    current->next = newNode;
}

// 删除指定位置的节点
void removeAt(Node** head, int position) {
    if (*head == NULL) {
        printf("List is empty\n");
        return;
    }
    
    if (position < 0) {
        printf("Invalid position\n");
        return;
    }
    
    Node* current = *head;
    int count = 0;
    
    if (position == 0) {
        *head = current->next;
        if (*head != NULL) {
            (*head)->prev = NULL;
        }
        
        free(current);
        return;
    }
    
    while (count < position && current != NULL) {    // 找到要删除的节点
        current = current->next;
        count++;
    }
    
    if (current == NULL) {
        printf("Invalid position\n");
        return;
    }
    
    current->prev->next = current->next;
    
    if (current->next != NULL) {
        current->next->prev = current->prev;
    }
    
    free(current);
}

// 修改指定位置的节点值
void modify(Node* head, int position, int data) {
    Node* current = head;
    int count = 0;
    
    while (count < position && current != NULL) {    // 找到要修改的节点
        current = current->next;
        count++;
    }
    
    if (current == NULL) {
        printf("Invalid position\n");
        return;
    }
    
    current->data = data;
}

// 对链表进行排序
void sort(Node** head) {
    if (*head == NULL) {
        printf("List is empty\n");
        return;
    }
    
    Node* current = *head;
    Node* temp = NULL;
    int swapped;
    
    do {
        swapped = 0;
        current = *head;
        
        while (current->next != NULL) {
            if (current->data > current->next->data) {
                int tmp = current->data;
                current->data = current->next->data;
                current->next->data = tmp;
                
                swapped = 1;
            }
            
            current = current->next;
        }
        
        temp = current;
    } while (swapped);
}

// 打印链表
void printList(Node* head) {
    if (head == NULL) {
        printf("List is empty\n");
        return;
    }
    
    Node* current = head;
    
    while (current != NULL) {
        printf("%d ", current->data);
        current = current->next;
    }
    
    printf("\n");
}

// 释放链表内存
void freeList(Node** head) {
    if (*head == NULL) {
        return;
    }
    
    Node* current = *head;
    Node* next = NULL;
    
    while (current != NULL) {
        next = current->next;
        free(current);
        current = next;
    }
    
    *head = NULL;
}

int main() {
    Node* head = NULL;
    
    append(&head, 5);
    append(&head, 3);
    prepend(&head, 9);
    insert(&head, 7, 1);
    removeAt(&head, 2);
    modify(head, 0, 2);
    sort(&head);
    
    printList(head);
    
    freeList(&head);
    
    return 0;
}

代码里实现了创建双向链表、在链表末尾添加节点、在链表头部添加节点、在指定位置插入节点、删除指定位置的节点、修改指定位置的节点值、对链表进行排序、打印链表及释放链表内存等功能。

三、思路讲解

代码里定义了一个双向链表节点结构,包含数据域(data)、指向前一个节点的指针(prev)和指向后一个节点的指针(next)。

typedef struct Node {
    int data;
    struct Node* prev;
    struct Node* next;
} Node;

(1)createNode函数用于创建新节点。分配内存以存储节点,并检查内存分配是否成功。设置节点的数据域为传入的数据,并将前一个节点和后一个节点的指针都设置为NULL。最后,返回新创建的节点的指针。

(2)append函数用于在链表末尾添加节点。首先调用createNode函数创建一个新节点。如果头节点为空,则将新节点设置为头节点。否则,遍历链表直到找到最后一个节点,将新节点连接到最后一个节点的下一个位置,并设置新节点的prev指针指向最后一个节点。

(3)prepend函数用于在链表头部添加节点。首先,调用createNode函数创建一个新节点。如果头节点为空,则将新节点设置为头节点。否则,将新节点的next指针指向当前的头节点,将当前头节点的prev指针指向新节点,然后将新节点设置为头节点。

(4)insert函数用于在指定位置插入节点。首先,检查插入位置是否合法。如果插入位置为0,则调用prepend函数在链表头部插入节点。否则,调用createNode函数创建一个新节点,然后遍历链表直到找到插入位置前一个节点,将新节点插入到这两个节点之间,即将新节点的next指针指向前一个节点的next指针所指向的节点,将新节点的prev指针指向前一个节点,然后更新新节点两侧节点的指针。

(5)removeAt函数用于删除指定位置的节点。首先,检查链表是否为空。如果链表为空,则输出相应的提示信息。如果要删除的位置为0,即删除头节点,需要特殊处理,即将头节点的下一个节点设置为新的头节点,并将新的头节点的prev指针设置为NULL。否则,遍历链表直到找到要删除的节点,将要删除节点的前一个节点的next指针指向要删除节点的下一个节点,然后更新两侧节点的指针。

(6)modify函数用于修改指定位置的节点值。首先,遍历链表直到找到要修改的节点,然后将该节点的数据域设置为传入的新数据。

(7)sort函数用于对链表进行排序。首先,检查链表是否为空。如果链表为空,则输出相应的提示信息。使用冒泡排序算法,重复遍历链表并比较相邻节点的值,如果前一个节点的值大于后一个节点的值,则交换它们的值。重复此过程,直到链表没有发生交换为止。

(8)printList函数用于打印链表中的所有节点的值。首先,检查链表是否为空。如果链表为空,则输出相应的提示信息。遍历链表的每个节点,并输出节点中存储的数据。

(9)freeList函数用于释放链表的内存。首先,检查链表是否为空。如果链表为空,则直接返回。遍历链表的每个节点,使用free函数释放节点的内存,并将节点指针设为NULL,最后将头节点指针设为NULL。

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

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

相关文章

NPM中管理团队

目录 1、关于开发团队 2、创建团队 3、将组织成员添加到团队 3.1 从CLI管理团队 4、从团队中删除组织成员 5、管理团队对组织包的访问 5.1 向团队添加包访问权限 Web网页上的团队添加包访问权限 使用CLI向团队添加包访问权限 5.2 从组中删除包访问权限 从Web网页上的…

【Vue】vue2预览显示quill富文本内容,vue-quill-editor回显页面,v-html回显富文本内容

文章目录 前言一、下载二、使用步骤1.引入样式2.html代码 总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; vue后台框架&#xff0c;若依系统里有一个富文本编辑器&#xff0c;效果如下 在package.json里面查看&#xff0c;发现插件名叫quill 插件的…

Systick滴答定时器

今天&#xff0c;对Systick滴答定时器进行资料的整理&#xff0c;这个定时器在程序中的作用就是提供延时函数。参考&#xff08;【STM32】Systick滴答定时器_一只大喵咪1201的博客-CSDN博客&#xff09; Systick滴答定时器的介绍 相关寄存器 寄存器CTRL 补充HCLK 寄存器LOAD…

Linux 网络编程 和 字节序的概念

网络编程概述 不同于之前学习的所有通讯方法&#xff0c;多基于Linux内核实现&#xff0c;只能在同一个系统中不同进程或线程间通讯&#xff0c;Linux的网络编程可以实现真正的多机通讯&#xff01; 两个不相关的终端要实现通讯&#xff0c;必须依赖网络&#xff0c;通过地址…

【C++】—— c++11新特性之 lambda

前言&#xff1a; 上期&#xff0c;我们学习了有关 C11 一些属于了解的新特性介绍。本期&#xff0c;我们要讲到的内容则属于 需要掌握 的知识点之一。 目录 &#xff08;一&#xff09;lambda 的引入 &#xff08;二&#xff09; lambda表达式 1、lambda表达式语法 2、捕获…

压力监测设备——监测压力的仪器仪表

压力监测设备的原理和差压变送器相同&#xff0c;所不同的是压力监测设备低压室压力是大气压或真空。压力监测设备是一种接受压力变量&#xff0c;经传感转换后&#xff0c;按一定比例将压力变化转换成标准信号的仪表。 压力监测设备的输出信号传送到中央控制室进行压力指示和…

【大模型】基于 LlaMA2 的高 star 的 GitHub 开源项目汇总

【大模型】基于 LlaMA2 的高 star 的 GitHub 开源项目汇总 Llama2 简介开源项目汇总NO1. FlagAlpha/Llama2-ChineseNO2. hiyouga/LLaMA-Efficient-TuningNO3. yangjianxin1/FireflyNO4. LinkSoul-AI/Chinese-Llama-2-7bNO5. wenge-research/YaYiNO6. michael-wzhu/Chinese-LlaM…

万字精讲——数据结构栈与队列必会OJ练习

W...Y的主页 &#x1f495; 代码库分享 &#x1f60a; 在之前的博客中&#xff0c;我们学习了栈与队列的基本内容&#xff0c;并且实现了栈与队列。今天我们进行刷题训练&#xff0c;走进栈与队列的世界中去感受一番&#xff01;&#xff01;&#xff01; 目录 括号匹配问题…

springboot集成redisson

springboot集成redisson有两种方式&#xff0c;分别是集成redisson-spring-boot-starter或redisson-spring-data。由于作者的项目和redisson-spring-boot-starter有冲突&#xff0c;所以选择集成redisson-spring-data&#xff0c;下面介绍集成集成步骤&#xff0c;已单机版redi…

对CSV格式的数据文件进行插值处理

使用Python程序&#xff0c;实现对一个较短的csv文件&#xff0c;进行差值处理&#xff0c;并绘制GUI界面&#xff1b; 这个程序是一个使用Python的Tkinter库构建的GUI应用程序&#xff0c;用于对CSV格式的数据文件进行插值处理。下面我会逐步解释程序的各个部分和功能&#x…

使用 docker 搭建 granfana+prometheus 监控平台监控测试服务器资源

互联网发展的今天&#xff0c;人们对互联网产品的用户体验要求也越来越高&#xff0c;企业为了能提供更优质的用户体验&#xff0c;就会绞尽脑汁想尽各种办法。而对于服务器的资源监控&#xff0c;搭建一个资源监控平台&#xff0c;就是一个很好的维护优质服务的保障平台。利用…

函数的参数传递和返回值-PHP8知识详解

本文学习的是《php8知识详解》中的《函数的参数传递和返回值》。主要包括&#xff1a;向函数传递参数值、向函数传递参数引用、函数的返回值。 1、向函数传递参数值 函数是一段封闭的程序&#xff0c;有时候&#xff0c;程序员需要向函数传递一些数据进行操作。可以接受传入参…

Python如何输出文本到屏幕

如何输出文本到屏幕 在Python中&#xff0c;要将文本输出到屏幕&#xff0c;可以使用print()函数。这个函数是Python中的内置函数&#xff0c;非常常用&#xff0c;让我们一起来学习如何使用它。 使用print()函数 print()函数用于将内容输出到终端&#xff08;屏幕&#xff…

从0开始配置eslint

没有在.eslintrc文件中配置parserOptions指定语言版本和模块类型 {"parserOptions": {"ecmaVersion": 7, //指定es版本为es2016"sourceType": "module", //使用import导入模块} }eslint还不能识别jsx语法 {"parserOptions"…

Android11.0 Launcher3单层app列表页排序功能实现

1.概述 在11.0的定制化开发中,对于Launcher3的功能定制也是好多的,而对于单层app列表页来说排序功能的开发,也是常有的功能这就需要了解加载app数据的流程,然后根据需要进行排序就可以了, 如图: 2. Launcher3 单层app列表页排序功能实现 packages\apps\Launcher3\src\…

禅道后台命令执行漏洞

漏洞简介 禅道是第一款国产的开源项目管理软件。它集产品管理、项目管理、质量管理、文档管理、 组织管理和事务管理于一体&#xff0c;是一款专业的研发项目管理软件&#xff0c;完整地覆盖了项目管理的核心流程。 禅道管理思想注重实效&#xff0c;功能完备丰富&#xff0c;…

Matlab彩色图像转索引图像

索引图像 索引图像是一种把像素值直接作为RGB调色板下标的图像。索引图像包括一个数据矩阵X&#xff0c;一个调色板矩阵map&#xff0c;也称为颜色映像矩阵。其中&#xff0c;数据矩阵X可以是8位无符号整型、16位无符号整型或双精度类型。调色板矩阵map是一个m3的数据阵列&…

操作符详解-C语言

目录 一、操作符分类 二、算术操作符 三、移位操作符 3.1. 左移操作数 3.2 右移操作符 四、位操作符 五、赋值操作符 六、单目操作符 七、关系操作符 八、逻辑操作符 九、条件操作符 十、逗号表达式 十一、下标引用函数调用和结构体成员 一、操作符分类 操作符分为算…

ArrayList的介绍

arrayList数组列表&#xff0c;特点是 ①有序&#xff1a;y元素存入的顺序和取出的顺序一致&#xff1b; ②可重复&#xff1a;可以存储重复的元素 ③可为null&#xff1a;可以存储多个null 优点&#xff1a;查询快 缺点&#xff1a;插入删除慢 size是元素的个数&#xff0c;容…

设计模式之详解

概念 在软件工程中&#xff0c;设计模式是指软件设计问题的推荐方案。 设计模式一般是描述如何组织代码和使用最佳实践来解决常见的设计问题。 设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。 好处 设计模式可以提高代码的可重用性和可读…