【数据结构】链表(2):双向链表和双向循环链表

news2025/1/8 14:47:40

双向链表(Doubly Linked List)

定义:
每个节点包含三个部分:

  1. 数据域。
  2. 前驱指针域(指向前一个节点)。
  3. 后继指针域(指向下一个节点)。

支持从任意节点向前或向后遍历。

#define datatype int
typedef struct link{
  datatype data;
  struct link* next;  //该指针保存的后继结点的地址
  struct link* prev;  //该指针保存的前驱结点的地址 
}link_t;

双向链表的特点:

  • 双向遍历更灵活,插入和删除操作更高效。
  • 占用更多内存(每个节点需存储两个指针)。
1> 初始化link_init
void link_init(link_t **p)
{
   //申请堆 
    *p = (link_t *)malloc(sizeof(link_t));
    if(NULL == p)
    {
        perror("malloc");
        return;
    }
    (*p)->prev = NULL;
    (*p)->next = NULL;
}
2> 创建结点create_node
static link_t *create_node(datatype d)
{
    //1>向堆空间申请
    link_t *p = (link_t *)malloc(sizeof(link_t));
    if(NULL == p)
    {
        perror("malloc");
        return NULL;
    }
    //2>赋值  
    p->data = d;
    p->next = NULL;   
    p->prev = NULL;
    return p;
}
3> 插入函数insert_behind
//将一个结点(a)插到另一个结点(b)的后面
static void insert_behind(link_t *a,link_t *b)
{
   //遵循先连后断 
   a->next = b->next; 
   a->prev = b;
   if(b->next != NULL)
       b->next->prev = a;
   b->next = a;
}

在这里插入图片描述

4> 头插函数link_insert_head
void link_insert_head(link_t *p,datatype data)
{
    //创建结点
    link_t * node = create_node(data);
    if(NULL == node)
    {
        perror("create_node");
        return;
    }
    //将node插到p后面
    insert_behind(node,p);
}
5> 尾插函数link_insert_tail
void link_insert_tail(link_t *p,datatype data)
{
    //创建结点
    link_t * node = create_node(data);
    if(NULL == node)
    {
        perror("create_node");
        return;
    }
    //遍历找到尾结点
    while(p->next != NULL)
        p = p->next;
    //将node插到尾结点p后面
    insert_behind(node,p);
}
6> 正序遍历display
void display(link_t *p)
{
    printf("正序遍历结果为:\n");
    while(p->next != NULL)
    {
       p = p->next;       
       printf("%d ",p->data);
    }
    printf("\n");
}
7> 逆序遍历dispaly_reverse
void dispaly_reverse(link_t *p)
{
   printf("逆序遍历结果为:\n");
    while(p->next != NULL)
    {
       p = p->next;       
    }
    //往前遍历
    while(p->prev != NULL)
    {
      printf("%d ",p->data);
      p = p->prev;
    }
    printf("\n");  
}
8> 删除函数link_del
void link_del(link_t *p,datatype data)
{
   link_t *node =NULL;
   //遍历 
   while(p->next != NULL)
   {
     //数据对比
     if(p->next->data == data)
     {
        //使要删除的结点的前后结点建立联系
        node = p->next; 
        p->next = node->next;
        if(p->next != NULL)
            p->next->prev = p;
        //释放结点
        node->next = NULL;
        node->prev = NULL;
        free(node);
        continue;
     }  
     p = p->next;  
   } 
}
9> 替换函数link_replace
void link_replace(link_t *p,datatype old,datatype new)
{
   link_t *new_node = NULL;
   link_t *old_node = NULL;
   //遍历 
   while(p->next != NULL) 
   {
       if(p->next->data == old)
       {
          new_node = create_node(new);
          if(NULL == new_node)
          {
              perror("create_node");
              return;
          }
          //替换 
          old_node = p->next;
          new_node->prev = p;
          new_node->next = old_node->next;
          if(old_node->next != NULL)
             old_node->next->prev = new_node;
          p->next = new_node;
          //释放
          old_node->next = NULL;
          old_node->prev = NULL;
          free(old_node);
          continue;
       }
       p = p->next;  //没找到对于数据才会移动
   }    
}

在这里插入图片描述

双向循环链表(Doubly Circular Linked List)

定义:

  1. 双向链表的变体,首尾相连,形成循环。
  2. 每个节点的前驱指针指向前一个节点,后继指针指向下一个节点。

双向循环链表的特点:

  • 支持双向循环遍历。
  • 节点链接更紧密,占用更多内存。

代码包含链表的创建、节点插入、节点删除、以及正向和反向遍历、打印等功能:

1> 初始化link_init
void link_init(link_t **p)
{
   //申请堆 
    *p = (link_t *)malloc(sizeof(link_t));
    if(NULL == p)
    {
        perror("malloc");
        return;
    }
    /*修改处*/ //自己指向自己
    (*p)->prev = (*p);
    (*p)->next = (*p);
}
2> 创建结点create_node
static link_t *create_node(datatype d)
{
    //1>向堆空间申请
    link_t *p = (link_t *)malloc(sizeof(link_t));
    if(NULL == p)
    {
        perror("malloc");
        return NULL;
    }
    //2>赋值  /*修改处*/ 
    p->data = d;
    p->next = p;   
    p->prev = p;
    return p;
}
3> 插入函数insert_behind
//将一个结点(a)插到另一个结点(b)的后面
static void insert_behind(link_t *a,link_t *b)
{
   //遵循先连后断  /*修改处*/
   a->next = b->next; 
   a->prev = b;
   b->next->prev = a;
   b->next = a;
}
4> 头插函数link_insert_head
void link_insert_head(link_t *p,datatype data)
{
    //创建结点
    link_t * node = create_node(data);
    if(NULL == node)
    {
        perror("create_node");
        return;
    }
    //将node插到p后面
    insert_behind(node,p);
}
5> 尾插函数link_insert_tail
void link_insert_tail(link_t *p,datatype data)
{
    //创建结点
    link_t * node = create_node(data);
    if(NULL == node)
    {
        perror("create_node");
        return;
    }
    //将node插到头结点前面 /*修改处*/
    insert_forward(node,p);
}
5.5> 插入到头前函数insert_forward
static void insert_forward(link_t *node,link_t *p)
{
	//先连后断  //尾结点地址用头节点去表示
	node->next = p;
	node->prev = p->prev;
	p->prev->next = node;
	p->prev = node;
}
6> 正序遍历display
void display(link_t *p)
{
    /*修改处*/
    link_t *head = p;
    printf("正序遍历结果为:\n");
    while(p->next != head) /*修改处*/
    {
       p = p->next;       
       printf("%d ",p->data);
    }
    printf("\n");
}
7> 逆序遍历dispaly_reverse
void dispaly_reverse(link_t *p)
{
    /*修改处*/
   link_t *head = p;
   printf("逆序遍历结果为:\n");
    //往前遍历 /*修改处*/
    while(p->prev != head)
    {
      p = p->prev;
      printf("%d ",p->data);
    }
    printf("\n");  
}
8> 删除函数link_del
void link_del(link_t *p,datatype data)
{
    /*修改处*/
   link_t *head =p;
   link_t *node =NULL;
   //遍历 
   while(p->next != head)
   {
     //数据对比
     if(p->next->data == data)
     {
        //使要删除的结点的前后结点建立联系 /*修改处*/
        node = p->next; 
        p->next = node->next;
        p->next->prev = p;
        //释放结点 /*修改处*/
        node->next = node;
        node->prev = node;
        free(node);
        continue;
     }  
     p = p->next;  
   } 
}
9> 替换函数link_replace
void link_replace(link_t *p,datatype old,datatype new)
{
   /*修改处*/
   link_t *head = p;
   link_t *new_node = NULL;
   link_t *old_node = NULL;
   //遍历 
   while(p->next != head)  /*修改处*/
   {
       if(p->next->data == old)
       {
          new_node = create_node(new);
          if(NULL == new_node)
          {
              perror("create_node");
              return;
          }
          //替换  /*修改处*/
          old_node = p->next;
          new_node->prev = p;
          new_node->next = old_node->next;
          old_node->next->prev = new_node;
          p->next = new_node;
          //释放  /*修改处*/
          old_node->next = old_node;
          old_node->prev = old_node;
          free(old_node);
          continue;
       }
       p = p->next;  //没找到对于数据才会移动
   }    
}
一段完整的代码片
#include <stdio.h>
#include <stdlib.h>

// 定义节点结构
typedef struct Node {
    int data;               // 数据域
    struct Node* next;      // 指向下一个节点
    struct Node* prev;      // 指向前一个节点
} Node;

// 创建一个新节点
Node* createNode(int data) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
    newNode->next = NULL;
    newNode->prev = NULL;
    return newNode;
}

// 插入节点到链表末尾
void append(Node** head, int data) {
    Node* newNode = createNode(data);

    // 如果链表为空
    if (*head == NULL) {
        *head = newNode;
        newNode->next = newNode; // 自己指向自己(形成循环)
        newNode->prev = newNode;
        return;
    }

    // 查找到链表的最后一个节点
    Node* tail = (*head)->prev;

    // 更新新节点的指针
    newNode->next = *head;      // 新节点的 next 指向头节点
    newNode->prev = tail;       // 新节点的 prev 指向尾节点

    // 更新头节点和尾节点的指针
    tail->next = newNode;       // 尾节点的 next 指向新节点
    (*head)->prev = newNode;    // 头节点的 prev 指向新节点
}

// 删除链表中的指定节点
void deleteNode(Node** head, int key) {
    if (*head == NULL) return;  // 链表为空,直接返回

    Node* current = *head;      // 从头节点开始查找
    Node* temp = NULL;

    // 遍历链表找到值为 key 的节点
    do {
        if (current->data == key) {
            temp = current;
            break;
        }
        current = current->next;
    } while (current != *head); // 回到头节点时终止循环

    if (temp == NULL) {
        printf("节点 %d 未找到。\n", key);
        return;
    }

    // 如果链表中只有一个节点
    if (temp->next == temp && temp->prev == temp) {
        *head = NULL; // 删除唯一节点后链表为空
        free(temp);
        return;
    }

    // 更新前驱和后继节点的指针
    temp->prev->next = temp->next;
    temp->next->prev = temp->prev;

    // 如果删除的是头节点,更新头指针
    if (temp == *head) {
        *head = temp->next;
    }

    free(temp); // 释放删除的节点
}

// 正向遍历链表
void printListForward(Node* head) {
    if (head == NULL) {
        printf("链表为空。\n");
        return;
    }

    Node* temp = head;
    printf("正向遍历链表:");
    do {
        printf("%d -> ", temp->data);
        temp = temp->next;
    } while (temp != head); // 回到头节点时终止
    printf("(head)\n");
}

// 反向遍历链表
void printListBackward(Node* head) {
    if (head == NULL) {
        printf("链表为空。\n");
        return;
    }

    Node* tail = head->prev; // 从尾节点开始
    Node* temp = tail;
    printf("反向遍历链表:");
    do {
        printf("%d -> ", temp->data);
        temp = temp->prev;
    } while (temp != tail); // 回到尾节点时终止
    printf("(tail)\n");
}

// 主函数
int main() {
    Node* head = NULL; // 初始化空链表

    // 添加节点
    append(&head, 10);
    append(&head, 20);
    append(&head, 30);
    append(&head, 40);

    // 打印链表
    printListForward(head);
    printListBackward(head);

    // 删除节点
    printf("删除节点 20:\n");
    deleteNode(&head, 20);
    printListForward(head);
    printListBackward(head);

    // 删除节点 10(头节点)
    printf("删除节点 10:\n");
    deleteNode(&head, 10);
    printListForward(head);
    printListBackward(head);

    return 0;
}
完整代码片 分析:
  1. 结构定义
    • 每个节点结构包含:
      • data:存储节点数据。
      • next:指向下一个节点。
      • prev:指向前一个节点。
  2. append 函数
    • 用于在链表末尾插入新节点。
    • 维护双向循环链表的特性:更新新节点、头节点和尾节点的指针。
  3. deleteNode 函数
    • 在链表中查找值等于key的节点并删除。
    • 特别处理了链表为空、只有一个节点、删除头节点等特殊情况。
  4. 遍历函数
    • printListForward:正向遍历,从头节点出发,依次打印每个节点。
    • printListBackward:反向遍历,从尾节点出发,依次打印每个节点。
  5. 主函数测试
    • 插入多个节点。
    • 正向和反向打印链表。
    • 删除指定节点,并再次验证。

双向循环链表运行结果示例:

正向遍历链表:10 -> 20 -> 30 -> 40 -> (head)
反向遍历链表:40 -> 30 -> 20 -> 10 -> (tail)

删除节点 20:
正向遍历链表:10 -> 30 -> 40 -> (head)
反向遍历链表:40 -> 30 -> 10 -> (tail)

删除节点 10:
正向遍历链表:30 -> 40 -> (head)
反向遍历链表:40 -> 30 -> (tail)

双向循环链表提供了灵活的正向和反向遍历功能,同时保持循环特性,适合需要频繁插入、删除并支持循环操作的场景。代码实现中考虑了各种边界条件(如链表为空、只有一个节点等),确保程序安全性。

综上。希望该内容能对你有帮助,感谢!

以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。

我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!

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

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

相关文章

指针 const 的组合

1、首先来了解一下常量 const int num 5&#xff1b; 那么num的值是5&#xff0c; num的值不可修改 2、来了解一下指针 int value 5; int* p &value; 我喜欢吧指针和类型放一起&#xff0c;来强调p是一个指针类型&#xff0c; 而赋值的时候就得赋值一个int类型的地址…

Tableau数据可视化与仪表盘搭建-数据可视化原理

目录 内容 做个小实验 数据如何变成图表 1 2 维度和度量定义 3 度量映射图形&#xff0c;维度负责区分 1 可映射的数据类型 2 可视化字典 3 使用Tableau将数据变成图表(Tableau可视化原理) 1 2 拖拽 3 具体操作 4 总结 内容 点击左下角的工作表 tableau可以自动…

【WRF数据准备】气象驱动数据-ERA5是否需要单层位势数据?

目录 气象驱动数据-ERA5是否需要单层位势(Geopotential)数据?位势(Geopotential)输入的重要性Vtable的管理参考气象驱动数据-ERA5是否需要单层位势(Geopotential)数据? 本博客参考WRF论坛中讨论内容-How to use ERA5 Data From Copernicus Database,总结位势(Geopot…

用ResNet50+Qwen2-VL-2B-Instruct+LoRA模仿Diffusion-VLA的论文思路,在3090显卡上训练和测试成功

想一步步的实现Diffusion VLA论文的思路&#xff0c;不过论文的图像的输入用DINOv2进行特征提取的&#xff0c;我先把这个部分换成ResNet50。 老铁们&#xff0c;直接上代码&#xff1a; from PIL import Image import torch import torchvision.models as models from torch…

常见中间件漏洞(tomcat,weblogic,jboss,apache)

先准备好今天要使用的木马文件 使用哥斯拉生成木马 压缩成zip文件 改名为war后缀 一&#xff1a;Tomcat 1.1CVE-2017-12615 环境搭建 cd vulhub-master/tomcat/CVE-2017-12615 docker-compose up -d 1.首页抓包&#xff0c;修改为 PUT 方式提交 发送shell.jsp 和木马内容 …

嵌入式科普(26)为什么heap通常8字节对齐

目录 一、概述 二、newlibc heap 2.1 stm32cubeide .ld heap 2.2 e2studio .ld heap 三、glibc源码 3.1 Ubuntu c heap 四、总结 一、概述 结论&#xff1a;在嵌入式c语言中&#xff0c;heap通常8字节对齐 本文主要分析这个问题的分析过程 二、newlibc heap newlibc…

大数据-240 离线数仓 - 广告业务 测试 ADS层数据加载 DataX数据导出到 MySQL

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; Java篇开始了&#xff01; 目前开始更新 MyBatis&#xff0c;一起深入浅出&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff0…

CTF杂项——[NSSRound#4 SWPU]Pixel_Signin

得到一个像素点 提取像素点 脚本 from PIL import Image result open(1.txt,w) img Image.open("Pixel_Signin.png") img img.convert(RGB) for i in range(31):for j in range(31):r,g,b img.getpixel((j,i))print(r,g,b,end ,fileresult) 运行之后得到 把它…

Harmony开发【笔记1】报错解决(字段名写错了。。)

在利用axios从网络接收请求时&#xff0c;发现返回obj的code为“-1”&#xff0c;非常不解&#xff0c;利用console.log测试&#xff0c;更加不解&#xff0c;可知抛出错误是 “ E 其他错误: userName required”。但是我在测试时&#xff0c;它并没有体现为空&#xff0c;…

springCloudGateWay使用总结

1、什么是网关 功能: ①身份认证、权限验证 ②服务器路由、负载均衡 ③请求限流 2、gateway搭建 2.1、创建一个空项目 2.2、引入依赖 2.3、加配置 3、断言工厂 4、过滤工厂 5、全局过滤器 6、跨域问题

韩国机场WebGIS可视化集合Google遥感影像分析

目录 前言 一、相关基础数据介绍 1、韩国的机场信息 2、空间数据准备 二、Leaflet叠加Google地图 1、叠加google地图 2、空间点的标记及展示 3、韩国机场空间分布 三、相关成果展示 1、务安国际机场 2、有同类问题的机场 四、总结 前言 12月29日8时57分左右务安国际机…

电子电气架构 --- 设计车载充电机的关键考虑因素

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 简单,单纯,喜欢独处,独来独往,不易合同频过着接地气的生活,除了生存温饱问题之外,没有什么过多的欲望,表面看起来很高冷,内心热情,如果你身…

python进阶06:MySQL

课后大总结 Day1 一、数据库命令总结 1.连接数据库 连接数据库进入mysql安装目录打开bin文件夹&#xff0c;输入cmd(此命令后无分号)mysql.exe -u root -ppassword命令后输入密码:root 设置密码set passwordpassword("root123"); 查看所有数据库show databases; …

php反序列化原生态 ctfshow练习 字符串逃逸

web262 拿着题审计一下 <?php error_reporting(0); class message{public $from;public $msg;public $to;public $tokenuser;public function __construct($f,$m,$t){$this->from $f;$this->msg $m;$this->to $t;} }$f $_GET[f]; $m $_GET[m]; $t $_GET[t…

探秘前沿科技:RFID 与 NFC,开启智能识别新篇

RFID&#xff08;射频识别&#xff09;与NFC&#xff08;近场通信&#xff09;作为两种基于射频技术的无线通信方式&#xff0c;在现代社会中发挥着越来越重要的作用。尽管它们都具备非接触式识别和通信的能力&#xff0c;但在工作原理、应用场景、技术细节等方面存在着显著的差…

【04】优雅草央千澈详解关于APP签名以及分发-上架完整流程-第四篇安卓APP上架之vivo商店-小米商店,oppo商店,应用宝

【04】优雅草央千澈详解关于APP签名以及分发-上架完整流程-第四篇安卓APP上架之vivo商店-小米商店&#xff0c;oppo商店&#xff0c;应用宝 背景介绍 接第三篇上架华为&#xff0c;由于华为商店较为细致&#xff0c;本篇幅介绍其他4类商店相对简要一点&#xff0c;剩下其他更…

OpenCV计算机视觉 06 图像轮廓检测(轮廓的查找、绘制、特征、近似及轮廓的最小外接圆外接矩形)

目录 图像轮廓检测 轮廓的查找 轮廓的绘制 轮廓的特征 面积 周长 根据面积显示特定轮廓 轮廓的近似 给定轮廓的最小外接圆、外接矩形 外接圆 外接矩形 图像轮廓检测 轮廓的查找 API函数 image, contours, hierarchy cv2.findContours(img, mode, method) 代入参…

ROS2 跨机话题通信问题(同一个校园网账号)

文章目录 写在前面的话校园网模式&#xff08;失败&#xff09;手机热点模式&#xff08;成功&#xff09; 我的实验细节实验验证1、ssh 用户名IP地址 终端控制2、互相 ping 通 IP3、ros2 run turtlesim turtlesim_node/turtle_teleop_key4、ros2 multicast send/receive5、从机…

web3与AI结合-Sahara AI 项目介绍

背景介绍 Sahara AI 于 2023 年创立&#xff0c;是一个 "区块链AI" 领域的项目。其项目愿景是&#xff0c;利用区块链和隐私技术将现有的 AI 商业模式去中心化&#xff0c;打造公平、透明、低门槛的 “协作 AI 经济” 体系&#xff0c;旨在重构新的利益分配机制以及…

【C++】你了解异常的用法吗?

文章目录 Ⅰ. C语言传统的处理错误的方式Ⅱ. C异常概念Ⅲ. 异常的使用1、异常的抛出和匹配原则2、在函数调用链中异常栈展开匹配原则3、异常的重新抛出4、异常安全5、异常规范 Ⅳ. 自定义异常体系Ⅴ. C标准库的异常体系Ⅵ. 异常的优缺点1、异常的优点2、异常的缺点3、总结 Ⅰ. …