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

一、双向链表介绍

双向链表(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。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spark on Yarn集群模式搭建及测试

&#x1f947;&#x1f947;【大数据学习记录篇】-持续更新中~&#x1f947;&#x1f947; 点击传送&#xff1a;大数据学习专栏 持续更新中&#xff0c;感谢各位前辈朋友们支持学习~ 文章目录 1.Spark on Yarn集群模式介绍2.搭建环境准备3.搭建步骤 1.Spark on Yarn集群模式介…

重排链表(C语言)

题目&#xff1a; 示例&#xff1a; 思路&#xff1a; 这题我们将使用栈解决这个问题&#xff0c;利用栈先进后出的特点&#xff0c;从链表的中间位置进行入栈&#xff0c;寻找链表的中间位置参考&#xff1a;删除链表的中间节点&#xff0c;之后从头开始进行连接。 本题使用…

LLaMA中ROPE位置编码实现源码解析

1、Attention中q&#xff0c;经下式&#xff0c;生成新的q。m为句长length&#xff0c;d为embedding_dim/head θ i 1 1000 0 2 i d \theta_i\frac{1}{10000^\frac{2i}{d}} θi​10000d2i​1​ 2、LLaMA中RoPE源码 import torchdef precompute_freqs_cis(dim: int, end: i…

【Java架构-包管理工具】-Maven基础(一)

本文摘要 Maven作为Java后端使用频率非常高的一款依赖管理工具&#xff0c;在此咱们由浅入深&#xff0c;分三篇文章&#xff08;Maven基础、Maven进阶、私服搭建&#xff09;来深入学习Maven&#xff0c;此篇为开篇主要介绍Maven概念、模型、安装配置、基本命令 文章目录 本文…

Python数据挖掘与机器学习

近年来&#xff0c;Python编程语言受到越来越多科研人员的喜爱&#xff0c;在多个编程语言排行榜中持续夺冠。同时&#xff0c;伴随着深度学习的快速发展&#xff0c;人工智能技术在各个领域中的应用越来越广泛。机器学习是人工智能的基础&#xff0c;因此&#xff0c;掌握常用…

使用Nodejs创建简单的HTTP服务器,借助内网穿透工具实现公网访问的方法分享

文章目录 前言1.安装Node.js环境2.创建node.js服务3. 访问node.js 服务4.内网穿透4.1 安装配置cpolar内网穿透4.2 创建隧道映射本地端口 5.固定公网地址 前言 Node.js 是能够在服务器端运行 JavaScript 的开放源代码、跨平台运行环境。Node.js 由 OpenJS Foundation&#xff0…

GWO-LSTM交通流量预测(python代码)

使用 GWO 优化 LSTM 模型的参数&#xff0c;从而实现交通流量的预测方法 代码运行版本要求 1.项目文件夹 data是数据文件夹&#xff0c;data.py是数据归一化等数据预处理脚本 images文件夹装的是不同模型结构打印图 model文件夹 GWO-LSTM测试集效果 效果视频&#xff1a;GWO…

兼具传统和新锐基因的极氪,是怎么做用户运营的?|新能源车专题研究

主笔&#xff1a;浣芳黛 出品&#xff1a;增长黑盒研究组 近几个月来&#xff0c;新能源车势头强劲&#xff0c;众多车企纷纷传出连月增长和再创新高的捷报&#xff0c;在当下整体经济复苏缓慢的映衬下&#xff0c;显得格外耀眼。 于是&#xff0c;增长黑盒近期针对新能源车企展…

RISC-V(1)——RISC-V是什么,有什么用

目录 1. RISC-V是什么 2. RISC-V指令集 3. RISC-V特权架构 4. RiscV的寄存器描述 5. 指令 5.1 算数运算—add/sub/addi/mul/div/rem 5.2 逻辑运算—and/andi/or/ori/xor/xori 5.3 位移运算—sll/slli/srl/srli/sra/srai 5.4 数据传输—lb/lh/lw/lbu/lhu/lwu/sb/sh/sw …

Zebec Protocol:模块化 L3 链 Nautilus Chain,深度拓展流支付体系

过去三十年间&#xff0c;全球金融科技领域已经成熟并迅速增长&#xff0c;主要归功于不同的数字支付媒介的出现。然而&#xff0c;由于交易延迟、高额转账费用等问题愈发突出&#xff0c;更高效、更安全、更易访问的支付系统成为新的刚需。 此前&#xff0c;咨询巨头麦肯锡的一…

iOS 17 及 Xcode 15.0 Beta7 问题记录

1、iOS 17 真机调试问题 iOS 17之后&#xff0c;真机调试Beta版本必须使用Beta版本的Xcode来调试&#xff0c;用以前复制DeviceSupport 方式无法调试&#xff0c;新的Beta版本Xcode中&#xff0c;已经不包含 iOS 17目录。如下图&#xff1a; 解决方案&#xff1a; 1&#x…

机器学习深度学习——NLP实战(自然语言推断——微调BERT实现)

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——针对序列级和词元级应用微调BERT &#x1f4da;订阅专栏&#xff1a;机器学习&&深度学习 希望文…

【SpringSecurity】三、访问授权

文章目录 1、配置用户权限2、针对URL授权3、针对方法的授权 1、配置用户权限 继续上一章&#xff0c;给在内存中创建两个用户配置权限。配置权限有两种方式&#xff1a; 配置roles配置authorities //哪个写在后面哪个起作用 //角色变成权限后会加一个ROLE_前缀&#xff0c;比…

物种气候生态位动态量化与分布特征模拟

在全球气候快速变化的背景下&#xff0c;理解并预测生物种群如何应对气候变化&#xff0c;特别是它们的地理分布如何变化&#xff0c;已经变得至关重要。利用R语言进行物种气候生态位动态量化与分布特征模拟&#xff0c;不仅可以量化描述物种对环境的需求和适应性&#xff0c;预…

Webstorm 入门级玩转uni-app 项目-微信小程序+移动端项目方案

1. Webstorm uni-app语法插件 &#xff1a; Uniapp Support Uniapp Support - IntelliJ IDEs Plugin | Marketplace 第一个是不收费&#xff0c;第二个收费 我选择了第二个Uniapp Support &#xff0c;有试用30天&#xff0c;安装重启webstorm之后&#xff0c;可以提高生产率…

6.物联网操作系统信号量,二值信号量,计数信号量

一。信号量的概念与应用 信号量定义 FreeRTOS信号量介绍 FreeRTOS信号量工作原理 1.信号量的定义 多任务环境下使用&#xff0c;用来协调多个任务正确合理使用临界资源。 2.FreeRTOS信号量介绍 Semaphore包括Binary&#xff0c;Count&#xff0c;Mutex&#xff1b; Mutex包…

Python爬虫猿人学逆向系列——第六题

题目&#xff1a;采集全部5页的彩票数据&#xff0c;计算全部中奖的总金额&#xff08;包含一、二、三等奖&#xff09; 地址&#xff1a;https://match.yuanrenxue.cn/match/6 本题比较简单&#xff0c;只是容易踩坑。话不多说请看分析。 两个参数&#xff0c;一个m一个f&…

PID直观感受简述

0、仿真控制框图 1、增加p的作用&#xff08;增加响应&#xff09;P 2、增加I的作用&#xff08;消除稳差&#xff09;PI 3、增加D的作用&#xff08;抑制波动&#xff09;PID 加入对噪声很敏 4、综合比对

【Linux】一张图了解系统文件

首先先认识磁盘结构 系统文件分布图 文件查找 文件删除 文件的增删改查都是围绕inode来完成的&#xff0c;所以当我们要进行文件删除的时候&#xff0c;只需要通过inode来获取到它对应的block bitmap和inode bitmap数据块容器和保存文件属性的位置置为 0即可 &#xff0c;如果想…

Pytorch学习:常见数据集torchvision.datasets及数据集的使用DataLoader

文章目录 1. Datasets常见数据集1.1 CIFAR101.2 Fashion-MNIST1.3 ImageNet 2. DataLoader2.1 shuffle2.2 drop_last 1. Datasets常见数据集 Torchvision在 torchvision.datasets 模块中提供了许多内置的数据集&#xff0c;以及用于构建自己的数据集的实用程序类。 官方文档&a…