双向链表示例

news2025/4/9 21:34:01
#include <stdio.h>
#include <stdlib.h>

// 定义双向链表节点结构体
typedef struct list {
    int data;           // 数据部分
    struct list *next;  // 指向下一个节点的指针
    struct list *prev;  // 指向前一个节点的指针
} list_t;

// 初始化链表,将链表的头节点的 next 和 prev 指向自己
void list_init(list_t *list) {
    printf("init list addr = %x\n", list);
    list->next = list;
    list->prev = list;
}

// 创建一个新的节点并返回
list_t* create_node(int data) {
    list_t* new_node = (list_t*)malloc(sizeof(list_t));
    if (new_node == NULL) {
        printf("Memory allocation failed!\n");
        return NULL;
    }
    new_node->data = data;
    new_node->next = new_node;
    new_node->prev = new_node;
    return new_node;
}

// 插入节点到链表末尾
void list_insert_tail(list_t *list, int data) {
    list_t* new_node = create_node(data);
    if (new_node == NULL) return;

    list_t* tail = list->prev;  // list 是头节点,list->prev 是尾节点

    // 更新尾节点的 next 和头节点的 prev
    tail->next = new_node;
    list->prev = new_node;

    // 将新节点的 next 和 prev 指向相应的节点
    new_node->prev = tail;
    new_node->next = list;
}

// 插入节点到链表头部
void list_insert_head(list_t *list, int data) {
    list_t* new_node = create_node(data);
    if (new_node == NULL) return;

    list_t* head = list->next;  // list->next 是第一个节点

    // 更新头节点的 next 和第一个节点的 prev
    list->next = new_node;
    head->prev = new_node;

    // 将新节点的 next 和 prev 指向相应的节点
    new_node->prev = list;
    new_node->next = head;
}

// 删除指定节点
void list_delete(list_t *list, list_t *node) {
    if (node == NULL || list == node) return;

    list_t* prev_node = node->prev;
    list_t* next_node = node->next;

    // 更新前后节点的指针
    prev_node->next = next_node;
    next_node->prev = prev_node;

    free(node);  // 释放节点内存
}

// 遍历并打印链表
void list_traverse(list_t *list) {
    list_t* current = list->next;  // 跳过头节点
    if (current == list) {
        printf("List is empty.\n");
        return;
    }
    
    printf("List contents: ");
    do {
        printf("%d ", current->data);
        current = current->next;
    } while (current != list);  // 循环到回到头节点为止
    printf("\n");
}

// 销毁链表,释放所有节点
void list_destroy(list_t *list) {
    list_t* current = list->next;
    while (current != list) {
        list_t* next_node = current->next;
        free(current);
        current = next_node;
    }
}

// 测试程序
int main() {
    list_t head;
    list_init(&head);

    // 向链表尾部插入元素
    list_insert_tail(&head, 10);
    list_insert_tail(&head, 20);
    list_insert_tail(&head, 30);
    list_traverse(&head);  // 输出: 10 20 30

    // 向链表头部插入元素
    list_insert_head(&head, 5);
    list_traverse(&head);  // 输出: 5 10 20 30

    // 删除指定节点
    list_t* node_to_delete = head.next;  // 删除第一个节点
    list_delete(&head, node_to_delete);
    list_traverse(&head);  // 输出: 10 20 30

    // 销毁链表
    list_destroy(&head);
    
    return 0;
}

1. 初始化链表

初始化链表后,头节点(head)的 nextprev 指向自己,这表示链表为空,只有一个虚拟的头节点。

head  --->  |  NULL  | <-->  |  NULL  | 
            ^--------^        ^--------^
  • 头节点的 next 指向头节点本身。

  • 头节点的 prev 也指向头节点本身。

  • 这里并没有实际存储数据的节点,链表的操作是基于这个空链表的。

2. 插入节点 10

插入节点 10 后,链表变为:

head  --->  |   10   | <-->  |  NULL  | 
            ^--------^        ^--------^
                        |
                        v
                      head
  • 头节点的 next 指向节点 10。

  • 节点 10 的 next 指向头节点。

  • 节点 10 的 prev 指向头节点。

3. 插入节点 20

插入节点 20 后,链表变为:

head  --->  |   10   | <-->  |   20   | <-->  |  NULL  | 
            ^--------^        ^--------^        ^--------^
                        |                         |
                        v                         v
                      head                    head
  • 头节点的 next 指向节点 10。

  • 节点 10 的 next 指向节点 20,节点 10 的 prev 指向头节点。

  • 节点 20 的 next 指向头节点,节点 20 的 prev 指向节点 10。

4. 插入节点 30

插入节点 30 后,链表变为:

head  --->  |   10   | <-->  |   20   | <-->  |   30   | <-->  |  NULL  | 
            ^--------^        ^--------^        ^--------^        ^--------^
                        |                         |                     |
                        v                         v                     v
                      head                    head                  head
  • 头节点的 next 指向节点 10。

  • 节点 10 的 next 指向节点 20,节点 10 的 prev 指向头节点。

  • 节点 20 的 next 指向节点 30,节点 20 的 prev 指向节点 10。

  • 节点 30 的 next 指向头节点,节点 30 的 prev 指向节点 20。

5. 删除节点 10

删除节点 10 后,链表变为:

head  --->  |   20   | <-->  |   30   | <-->  |  NULL  | 
            ^--------^        ^--------^
                        |                     
                        v                         
                      head                   
  • 头节点的 next 指向节点 20。

  • 节点 20 的 next 指向节点 30,节点 20 的 prev 指向头节点。

  • 节点 30 的 next 指向头节点,节点 30 的 prev 指向节点 20。

6. 结果总结

每个节点的结构如下:

[Prev] <--> [Data] <--> [Next]
  • 头节点(headnext 指向链表的第一个节点,prev 指向最后一个节点。

  • 每个节点 有两个指针:prev 指向前一个节点,next 指向下一个节点。这样就形成了一个环形链表。

  • 环形链表的特点:最后一个节点的 next 指向头节点,头节点的 prev 指向最后一个节点。

Step 1: Initializing the list (empty list, head points to itself):
+-------+       +--------+
| head  | ----> |   head | 
+-------+       +--------+
   |               |
   v               v
  (points to itself)
Step 2: Insert node 10:
+-------+       +--------+        +-------+
| head  | ----> | node 10| <----> | head  |
+-------+       +--------+        +-------+
Step 3: Insert node 20:
+-------+       +--------+        +--------+        +-------+
| head  | ----> | node 10| <----> | node 20| <----> | head  |
+-------+       +--------+        +--------+        +-------+
Step 4: Insert node 30:
+-------+       +--------+        +--------+        +--------+        +-------+
| head  | ----> | node 10| <----> | node 20| <----> | node 30| <----> | head  |
+-------+       +--------+        +--------+        +--------+        +-------+
Step 5: Delete node 10:
+-------+       +--------+        +--------+        +-------+
| head  | ----> | node 20| <----> | node 30| <----> | head  |
+-------+       +--------+        +--------+        +-------+

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

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

相关文章

Socket编程TCP

Socket编程TCP 1、V1——EchoServer单进程版2、V2——EchoServer多进程版3、V3——EchoServer多线程版4、V4——EchoServer线程池版5、V5——多线程远程命令执行6、验证TCP——Windows作为client访问Linux7、connect的断线重连 1、V1——EchoServer单进程版 在TcpServer.hpp中实…

文件映射mmap与管道文件

在用户态申请内存&#xff0c;内存内容和磁盘内容建立一一映射 读写内存等价于读写磁盘 支持随机访问 简单来说&#xff0c;把磁盘里的数据与内存的用户态建立一一映射关系&#xff0c;让读写内存等价于读写磁盘&#xff0c;支持随机访问。 管道文件&#xff1a;进程间通信机…

代码随想录回溯算法03

93.复原IP地址 本期本来是很有难度的&#xff0c;不过 大家做完 分割回文串 之后&#xff0c;本题就容易很多了 题目链接/文章讲解&#xff1a;代码随想录 视频讲解&#xff1a;回溯算法如何分割字符串并判断是合法IP&#xff1f;| LeetCode&#xff1a;93.复原IP地址_哔哩哔…

批量改CAD图层颜色——CAD c#二次开发

一个文件夹下大量图纸&#xff08;几百甚至几千个文件&#xff09;需要改图层颜色时&#xff0c;可采用插件实现&#xff0c;效果如下&#xff1a; 转换前&#xff1a; 转换后&#xff1a; 使用方式如下&#xff1a;netload加载此dll插件&#xff0c;输入xx运行。 附部分代码如…

【内网安全】DHCP 饿死攻击和防护

正常情况&#xff1a;PC2可以正常获取到DHCP SERVER分别的IP地址查看DHCP SERCER 的ip pool地址池可以看到分配了一个地址、Total 253个 Used 1个 使用kali工具进行模拟攻击 进行DHCP DISCOVER攻击 此时查看DHCP SERVER d大量的抓包&#xff1a;大量的DHCP Discover包 此时模…

10种电阻综合对比——《器件手册--电阻》

二、电阻 前言 10种电阻对比数据表 电阻类型 原理 特点 应用 贴片电阻 贴片电阻是表面贴装元件&#xff0c;通过将电阻体直接贴在电路板上实现电路连接 体积小、重量轻&#xff0c;适合高密度电路板&#xff1b;精度高、稳定性好&#xff0c;便于自动化生产 广泛应用于…

剑指Offer(数据结构与算法面试题精讲)C++版——day6

剑指Offer&#xff08;数据结构与算法面试题精讲&#xff09;C版——day6 题目一&#xff1a;不含重复字符的最长子字符串题目二&#xff1a;包含所有字符的最短字符串题目三&#xff1a;有效的回文 题目一&#xff1a;不含重复字符的最长子字符串 这里还是可以使用前面&#x…

freertos韦东山---事件组以及实验

事件组的原理是什么&#xff0c;有哪些优点&#xff0c;为啥要创造出这个概念 在实时操作系统&#xff08;如 FreeRTOS&#xff09;中&#xff0c;事件组是一种用于任务间同步和通信的机制&#xff0c;它的原理、优点及存在意义如下&#xff1a; 事件组原理 数据结构&#xf…

架构师面试(二十六):系统拆分

问题 今天我们聊电商系统实际业务场景的问题&#xff0c;考查对业务系统问题的分析能力、解决问题的能力和对系统长期发展的整体规划能力。 一电商平台在早期阶段业务发展迅速&#xff0c;DAU在 10W&#xff1b;整个电商系统按水平分层架构进行设计&#xff0c;包括【入口网关…

Java中的同步和异步

一、前言 在Java中&#xff0c;同步&#xff08;Synchronous&#xff09;和异步&#xff08;Asynchronous&#xff09;是两种不同的任务处理模式。核心区别在任务执行的顺序控制和线程阻塞行为。 二、同步&#xff08;Synchronous&#xff09; 定义&#xff1a;任务按顺序执行…

在 Ubuntu24.04 LTS 上 Docker Compose 部署基于 Dify 重构二开的开源项目 Dify-Plus

一、安装环境信息说明 硬件资源&#xff08;GB 和 GiB 的主要区别在于它们的换算基数不同&#xff0c;GB 使用十进制&#xff0c;GiB 使用二进制&#xff0c;导致相同数值下 GiB 表示的容量略大于 GB&#xff1b;换算关系&#xff1a;1 GiB ≈ 1.07374 GB &#xff1b;1 GB ≈ …

NO.64十六届蓝桥杯备战|基础算法-简单贪心|货仓选址|最大子段和|纪念品分组|排座椅|矩阵消除(C++)

贪⼼算法是两极分化很严重的算法。简单的问题会让你觉得理所应当&#xff0c;难⼀点的问题会让你怀疑⼈⽣ 什么是贪⼼算法&#xff1f; 贪⼼算法&#xff0c;或者说是贪⼼策略&#xff1a;企图⽤局部最优找出全局最优。 把解决问题的过程分成若⼲步&#xff1b;解决每⼀步时…

瑞萨RA4M2使用心得-KEIL5的第一次编译

目录 前言 环境&#xff1a; 开发板&#xff1a;RA-Eco-RA4M2-100PIN-V1.0 IDE&#xff1a;keil5.35 一、软件的下载 编辑瑞萨的芯片&#xff0c;除了keil5 外还需要一个软件&#xff1a;RASC 路径&#xff1a;Releases renesas/fsp (github.com) 向下找到&#xff1a; …

数据分析-Excel-学习笔记

Day1 复现报表聚合函数&#xff1a;日期联动快速定位区域SUMIF函数SUMIFS函数环比、同比计算IFERROR函数混合引用单元格格式总结汇报 拿到一个Excel表格&#xff0c;首先要看这个表格个构成&#xff08;包含了哪些数据&#xff09;&#xff0c;几行几列&#xff0c;每一列的名称…

整车CAN网络和CANoe

车载网络中主要包含有Can网络,Lin网络,FlexRay,Most,以太网。 500kbps:500波特率,表示的数据传输的速度。表示的是最大的网速传输速度。也就是每秒 500kb BodyCan车身Can InfoCan娱乐信息Can 车身CAN主要连接的是ESB电动安全带 ADB自适应远光灯等 PTCan动力Can 底盘Can

ChatGPT 的新图像生成器非常擅长伪造收据

本月&#xff0c;ChatGPT 推出了一种新的图像生成器&#xff0c;作为其 4o 模型的一部分&#xff0c;该模型在生成图像内的文本方面做得更好。 人们已经在利用它来生成假的餐厅收据&#xff0c;这可能会为欺诈者使用的已经很广泛的 AI 深度伪造工具包添加另一种工具。 多产的…

JS页面尺寸事件

元素位置 在这里插入图片描述 父元素带有定位时输出相对于父亲元素的距离值

网络协议之基础介绍

写在前面 本文看下网络协议相关基础内容。 1&#xff1a;为什么要有网络协议 为了实现世界各地的不同主机的互联互通。 2&#xff1a;协议的三要素 协议存在的目的就是立规矩&#xff0c;无规矩不成方圆嘛&#xff01;但是这个规矩也不是想怎么立就怎么立的&#xff0c;也…

初识数据结构——Java集合框架解析:List与ArrayList的完美结合

&#x1f4da; Java集合框架解析&#xff1a;List与ArrayList的完美结合 &#x1f31f; 前言&#xff1a;为什么我们需要List和ArrayList&#xff1f; 在日常开发中&#xff0c;我们经常需要处理一组数据。想象一下&#xff0c;如果你要管理一个班级的学生名单&#xff0c;或…

uniapp微信小程序引入vant组件库

1、首先要有uniapp项目&#xff0c;根据vant官方文档使用yarn或npm安装依赖&#xff1a; 1、 yarn init 或 npm init2、 # 通过 npm 安装npm i vant/weapp -S --production# 通过 yarn 安装yarn add vant/weapp --production# 安装 0.x 版本npm i vant-weapp -S --production …