链表(C 语言)

目录

  • 一、链表的概念
    • 1. 链表的结构
    • 2. 链表的分类
    • 3. 链表的优势
  • 二、链表的实现
    • 1. 无头单项非循环链表的实现
      • 1.1 代码说明
    • 2. 带头双向循环链表的实现
      • 2.1 代码说明
  • 三、链表和顺序表的区别
  • 四、链表总结

一、链表的概念

链表是一种顺序表,它由一个一个的节点组成,该节点中存储着该节点的数据 val 和下一个结点的位置 next。最后一个节点的 next 指向NULL。

1. 链表的结构

链表的逻辑结构是连续的,但是物理结构通常是不连续的。因为在逻辑上,好像每个节点都是链接在一起的。
在这里插入图片描述
但是实际上,只是 next 存储了下一个结点的地址。
在这里插入图片描述

2. 链表的分类

链表通过是否带哨兵位头节点、是否循环还有单向和双向三个标准分为 8 种。

哨兵位头节点:不用来存储数据,仅占位,相当于一个站岗的士兵。有了它就不用判断是不是第一次插入数据。
在这里插入图片描述

循环:尾节点的 next 指向头节点,形成一个环。
在这里插入图片描述

双向:当前节点既有下一个节点的地址,也有上一个节点的地址。
在这里插入图片描述

3. 链表的优势

相比于顺序表,链表具有以下优势:
(1)不需要扩容,不会造成空间的浪费
因为链表插入数据时就申请一个节点,容量刚好。
(2)插入删除数据时不需要移动数据,只需要把新的节点链接进去或者把旧的节点解开释放即可。

二、链表的实现

不管是什么类型的链表,它的基本结构都是一个节点,该节点至少需要数据的值和下一个节点的地址两个成员。

不管是顺序表也好,链表也罢,无非就是增删查改。实现的功能都是一样的,只是使用的方法不同。

下面先实现一个最基本的无头单项非循环链表,然后再实现一个带头双向循环链表。当你完成第一个链表的实现再去看第二个链表时,你会觉得非常简单。等你这两个链表都能实现的时候,你会发现链表也就这样。

1. 无头单项非循环链表的实现

老样子,分成三个文件:头文件、方法实现文件和测试文件。

头文件:ListNode.h

// 头文件
#include <stdio.h>// 无头单向非循环链表// 类型声明
typedef int DataType;// 节点结构声明
typedef struct ListNode
{DataType val;  // 数据struct ListNode* next;  // 下一个结点的位置
}ListNode;// 方法// 获得节点
ListNode* BuyNode(DataType data);// 头插
void ListPushFront(ListNode** pphead, DataType data);// 头删
void ListPopFront(ListNode** pphead);// 尾插
void ListPushBack(ListNode** pphead, DataType data);// 尾删
void ListPopBack(ListNode** pphead);// 查找
ListNode* ListFind(ListNode** pphead, DataType data);// 插入
void ListInsert(ListNode* pos, DataType data);// 删除
void ListErase(ListNode* pos);// 打印链表
void ListPrint(const ListNode** pphead);// 销毁链表
void ListDestory(ListNode** pphead);

方法实现文件:ListNode.c

// 头文件
#include "ListNode.h"
#include <assert.h>
#include <stdlib.h>// 方法// 获得节点
ListNode* BuyNode(DataType data)
{// 申请节点ListNode* new_node = (ListNode*)malloc(sizeof(ListNode));if (NULL == new_node){perror("BuyNode::malloc: ");exit(-1);}// 赋值new_node->val = data;new_node->next = NULL;// 返回return new_node;
}// 头插
void ListPushFront(ListNode** pphead, DataType data)
{assert(pphead);// 获得新节点ListNode* new_node = BuyNode(data);// 链接new_node->next = *pphead;*pphead = new_node;
}// 头删
void ListPopFront(ListNode** pphead)
{assert(pphead);// 空表if (NULL == *pphead){printf("Empty List!\n");exit(-1);}// 删除ListNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}// 尾插
void ListPushBack(ListNode** pphead, DataType data)
{assert(pphead);// 获得新节点ListNode* new_node = BuyNode(data);// 头插判断if (NULL == *pphead){*pphead = new_node;}else  {// 找尾ListNode* tail = *pphead;while (tail->next){tail = tail->next;}// 链接tail->next = new_node;}
}// 尾删
void ListPopBack(ListNode** pphead)
{assert(pphead);// 空表if (NULL == *pphead){printf("Empty List!\n");exit(-1);}// 头删判断if (NULL == (*pphead)->next){free(*pphead);*pphead = NULL;}else{// 找尾前节点ListNode* pre_tail = *pphead;while (pre_tail->next->next){pre_tail = pre_tail->next;}// 释放尾free(pre_tail->next);// 新尾后置空pre_tail->next = NULL;}
}// 查找
ListNode* ListFind(ListNode** pphead, DataType data)
{assert(pphead);ListNode* cur = *pphead;while (cur){// 找到了if (cur->val == data)return cur;// 下一个cur = cur->next;}// 没找到return NULL;
}// 插入
void ListInsert(ListNode* pos, DataType data)
{assert(pos);// 申请新节点ListNode* new_node = BuyNode(data);// 链接new_node->next = pos->next;pos->next = new_node;
}// 删除
void ListErase(ListNode* pos)
{assert(pos);// 记录删除位ListNode* del = pos->next;// 链接pos->next = pos->next->next;// 删除free(del);
}// 打印链表
void ListPrint(const ListNode** pphead)
{assert(pphead);// 打印const ListNode* cur = *pphead;while (cur){printf("%d ", cur->val);// 下一个节点cur = cur->next;}printf("\n");
}// 销毁链表
void ListDestory(ListNode** pphead)
{assert(pphead);// 销毁ListNode* cur = *pphead;while (cur){// 存储下一个节点ListNode* next = cur->next;// 删除当前节点free(cur);// 下一个节点cur = next;}// 置空*pphead = NULL;
}

测试文件:test.c

// 头文件
#include "ListNode.h"// 头插头删测试
void test1()
{ListNode* phead = NULL;// 头插int i = 0;for (i = 0; i < 5; ++i){ListPushFront(&phead, i);}ListPrint(&phead);// 头删for (i = 0; i < 5; ++i){ListPopFront(&phead);printf("头删 %d 次: ", i+1);ListPrint(&phead);}// 销毁链表ListDestory(&phead);
}// 尾插尾删测试
void test2()
{ListNode* phead = NULL;// 尾插int i = 0;for (i = 0; i < 5; ++i){ListPushBack(&phead, i);}ListPrint(&phead);// 尾删for (i = 0; i < 5; ++i){ListPopBack(&phead);printf("尾删 %d 次: ", i+1);ListPrint(&phead);}// 销毁ListDestory(&phead);
}// 插入删除查找测试
void test3()
{ListNode* phead = NULL;// 尾插int i;for (i = 1; i <= 5; ++i){ListPushBack(&phead, i);}ListPrint(&phead);// 在每个数据后面插入 9for (i = 1; i <= 5; ++i){// 找位置ListNode* pos = ListFind(&phead, i);// 在后面插入 9ListInsert(pos, 9);}ListPrint(&phead);// 删除 9for (i = 1; i <= 5; ++i){// 找位置ListNode* pos = ListFind(&phead, i);// 在后面插入 9ListErase(pos);}ListPrint(&phead);// 销毁ListDestory(&phead);
}int main()
{// 头插头删测试printf("头插头删测试:\n");test1();// 尾插尾删测试printf("\n\n尾插尾删测试:\n");test2();// 插入删除查找测试printf("\n\n插入删除查找测试:\n");test3();return 0;
}

程序测试结果:
在这里插入图片描述

1.1 代码说明

1. 为什么使用二级指针?
首先,只要是插入操作,当插入第一个节点的时候需要改变头节点的指向,那么只有传入头节点的指针才能改变它的值。删除也是一个道理,当删除最后一个节点时,需要把头节点置空,也需要二级指针。所以这里已经有六个函数需要传递二级指针了,那么干脆把所有函数都设计成传递二级指针,这样既方便又统一。

2. 为什么需要对 pphead 断言?
这里要知道 pphead 是什么,它是头节点指针的地址。这个头节点指针是一定存在的,所以它的地址一定不是 NULL。

3. 为什么 ListInsert() 和 ListErase() 都是在给定位置之后插入删除?
如果在给定位置之前进行插入删除,需要遍历链表,找到前一个位置,这样就消耗了时间。而在给定位置之后插入删除,只需要链接上或者断开链接释放即可。

4. 尾插尾删需要进行判断
尾插需要判断是否是第一次插入,因为第一次插入需要改变头节点的位置。若不是第一次插入就需要寻找尾节点,然后再进行插入。
尾删需要判断是否是最后一个节点,因为删除完最后一个节点需要把头节点置空。若不是最后一个节点,需要找到尾节点的前一个节点,然后进行删除。

2. 带头双向循环链表的实现

有了前面无头单项不循环链表的基础,现在来实现这个带头双向循环链表那就是 so easy!这个新链表的节点在原来的基础上增加了一个 pre 指针,指向前一个节点。由于哨兵位头节点初始状态 pre 和 next 都指向自己,所以该链表的头节点需要使用一个函数来初始化并返回。令人最兴奋的是,该表的尾插和尾删都不要判断,而且所有函数都可以只传一级指针。

头文件:ListNode.h

// 头文件
#include <stdio.h>// 带头双向循环链表// 类型声明
typedef int DataType;// 链表节点声明
typedef struct ListNode
{DataType val;  // 数据struct ListNode* pre;  // 前一个节点的地址struct ListNode* next;  // 下一个节点的地址
}ListNode;// 方法// 获得哨兵位头节点
ListNode* ListCreate();// 获得新节点
ListNode* BuyNode(DataType data);// 头插
void ListPushFront(ListNode* phead, DataType data);// 头删
void ListPopFront(ListNode* phead);// 尾插
void ListPushBack(ListNode* phead, DataType data);// 尾删
void ListPopBack(ListNode* phead);// 插入
void ListInsert(ListNode* pos, DataType data);// 删除
void ListErase(ListNode* pos);// 打印
void ListPrint(const ListNode* phead);// 查找
ListNode* ListFind(const ListNode* phead, DataType data);// 销毁
void ListDestory(ListNode* phead);

方法实现文件:ListNode.c

// 头文件
#include "ListNode.h"
#include <stdlib.h>
#include <assert.h>// 方法// 获得哨兵位头节点
ListNode* ListCreate()
{// 申请新节点ListNode* phead = BuyNode(0);// 初始化phead->next = phead->pre = phead;phead->val = 0;// 返回return phead;
}// 获得新节点
ListNode* BuyNode(DataType data)
{// 申请新节点ListNode* new_node = (ListNode*)malloc(sizeof(ListNode));if (NULL == new_node){perror("ListCreate::malloc: ");exit(-1);}// 初始化new_node->next = new_node->pre = NULL;new_node->val = data;// 返回return new_node;
}// 头插
void ListPushFront(ListNode* phead, DataType data)
{assert(phead);// 获得新节点ListNode* new_node = BuyNode(data);// 前后链接ListNode* next = phead->next;new_node->next = next;next->pre = new_node;new_node->pre = phead;phead->next = new_node;
}// 头删
void ListPopFront(ListNode* phead)
{assert(phead);// 空表if (phead->next == phead){printf("Empty List!\n");exit(-1);}// 存储下一个节点ListNode* next = phead->next->next;// 删除头节点free(phead->next);next->pre = phead;phead->next = next;
}// 尾插
void ListPushBack(ListNode* phead, DataType data)
{assert(phead);// 申请新节点ListNode* new_node = BuyNode(data);// 链接ListNode* tail = phead->pre;tail->next = new_node;new_node->pre = tail;new_node->next = phead;phead->pre = new_node;
}// 尾删
void ListPopBack(ListNode* phead)
{assert(phead);// 空表if (phead->next == phead){printf("Empty List!\n");exit(-1);}// 存储尾前节点ListNode* new_tail = phead->pre->pre;free(phead->pre);new_tail->next = phead;phead->pre = new_tail;
}// 插入
void ListInsert(ListNode* pos, DataType data)
{// 申请新节点ListNode* new_node = BuyNode(data);// 链接ListNode* pre = pos->pre;pre->next = new_node;new_node->pre = pre;new_node->next = pos;pos->pre = new_node;
}// 删除
void ListErase(ListNode* pos)
{// 链接前后ListNode* pre = pos->pre;pre->next = pos->next;pos->next->pre = pre;// 释放当前结点free(pos);
}// 打印
void ListPrint(const ListNode* phead)
{assert(phead);ListNode* cur = phead->next;while (cur != phead){printf("%d ", cur->val);// 下一个节点cur = cur->next;}printf("\n");
}// 查找
ListNode* ListFind(const ListNode* phead, DataType data)
{assert(phead);ListNode* cur = phead->next;while (cur != phead){// 找到了if (cur->val == data)return cur;// 下一个节点cur = cur->next;}// 没找到return NULL;
}// 销毁
void ListDestory(ListNode* phead)
{assert(phead);ListNode* cur = phead->next;while (cur != phead){// 存储下一个节点ListNode* next = cur->next;// 删除当前节点free(cur);// 下一个节点cur = next;}// 释放哨兵位头节点free(phead);
}

测试文件:test.c

// 头文件
#include "ListNode.h"// 头插头删测试
void test1()
{// 创建链表ListNode* phead = ListCreate();// 头插int i = 0;for (i = 0; i < 5; ++i){ListPushFront(phead, i);}ListPrint(phead);// 头删for (i = 0; i < 5; ++i){ListPopFront(phead);printf("头删 %d 次: ", i + 1);ListPrint(phead);}// 销毁ListDestory(phead);phead = NULL;
}// 尾插尾删测试
void test2()
{ListNode* phead = ListCreate();// 尾插int i;for (i = 0; i < 5; ++i){ListPushBack(phead, i);}ListPrint(phead);// 尾删for (i = 0; i < 5; ++i){ListPopBack(phead);printf("尾删 %d 次: ", i+1);ListPrint(phead);}// 销毁ListDestory(phead);phead = NULL;
}// 插入删除查找测试
void test3()
{// 创建链表ListNode* phead = ListCreate();// 尾插int i;for (i = 0; i < 5; ++i){ListPushBack(phead, i);}ListPrint(phead);// 插入for (i = 0; i < 5; ++i){ListNode* pos = ListFind(phead, i);ListInsert(pos, 9);}ListPrint(phead);// 删除for (i = 0; i < 5; ++i){ListNode* pos = ListFind(phead, i);ListErase(pos);}ListPrint(phead);// 销毁ListDestory(phead);phead = NULL;
}int main()
{ 头插头删测试//test1(); 尾插尾删测试//test2();// 插入删除查找测试test3();return 0;
}

程序测试结果:
在这里插入图片描述

2.1 代码说明

1. 所有函数都只传一级指针
因为有哨兵位头节点的存在,不管是增删查改哪个功能都只需要一级指针。只是销毁链表需要在外头置空。

2. 尾插和尾删不需要检查和找尾
即使表中没有数据节点,但是还存在哨兵位头节点。且它的前一个就是尾节点,不需要寻找。

3. ListInsert() 和 ListErase() 函数可以在当前位置之前进行操作
由于该链表是双向的,不需要遍历寻找前一个节点。所以可以删除当前位置的节点和在当前节点的前面插入节点。

三、链表和顺序表的区别

在这里插入图片描述
上面的图片是偷课件的,下面作者用自己的理解来说明一下:
(1)顺序表是使用数组实现的,在内存中的存储是连续的。而链表是通过申请一个个节点链接在一起的,物理上是不连续的(极低概率的就不要来抬杠了)。
(2)顺序表支持随机访问,时间复杂度为O(1);而链表不支持随机访问,需要从头节点开始寻找,时间复杂度为O(N)。
(3)顺序表在插入元素的时候可能会触发增容,可能会造成空间浪费;链表没有增容这个说法,每次插入都申请一个新的节点,需要多少就申请多少,不存在浪费。
(4)顺序表在插入和删除元素时,时间复杂度最高为O(N),最低为O(1),分别对应头插头删和尾插尾删,平均时间复杂度为O(N)。而链表其实也和顺序表差不多,头插头删为O(1),尾插尾删为O(N),平均时间复杂度也是O(N)。但是由于顺序表存在增容,所以链表还是优于顺序表。
(5)顺序表的缓存利用率远高于链表。

关于缓存利用率这个概念,大家就只能自己下去了解一下了,我只能偷张图来帮助大家。
在这里插入图片描述

四、链表总结

其实在写完带头双向循环链表的时候,大家心中肯定在想,这个 3 层 buff 的表这么牛逼,为什么还要使用无头单项非循环链表。这是因为出面试题的时候使用的链表基本上是后者,如果给你前者,那么题目就会变得很简单。再者,无头单项非循环链表是一些复杂数据结构的子结构,一般不单独使用。

而带头双向循环链表可以根据使用场景来使用。

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

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

相关文章

QinQ的基础实验

拓扑 命令 LSW1 [LSW1]vlan batch 2 3 4 Info: This operation may take a few seconds. Please wait for a moment...done. [LSW1]interface g0/0/1 [LSW1-GigabitEthernet0/0/1]port link-type hybrid [LSW1-GigabitEthernet0/0/1]port hybrid untagged vlan 2 3 [LSW…

python-读写Excel:openpyxl-(4)下拉选项设置

使用openpyxl库的DataValidation对象方法可添加下拉选择列表。 DataValidation参数说明&#xff1a; type&#xff1a; 数据类型("whole", "decimal", "list", "date", "time", "textLength", "custom"…

Elasticsearch中时间字段格式用法详解

Elasticsearch中时间字段格式用法详解 攻城狮Jozz关注IP属地: 北京 2024.03.18 16:27:51字数 758阅读 2,571 Elasticsearch&#xff08;简称ES&#xff09;是一个基于Lucene构建的开源、分布式、RESTful搜索引擎。它提供了全文搜索、结构化搜索以及分析等功能&#xff0c;广泛…

【JavaEE初阶 — 多线程】Thread的常见构造方法&属性

目录 Thread类的属性 1.Thread 的常见构造方法 2.Thread 的几个常见属性 2.1 前台线程与后台线程 2.2 setDaemon() 2.3 isAlive() Thread类的属性 Thread 类是JVM 用来管理线程的一个类&#xff0c;换句话说&#xff0c;每个线程都有一个唯一的Thread 对象与之关联&am…

论文阅读笔记:DRCT: Saving Image Super-Resolution away from Information Bottleneck

论文阅读笔记&#xff1a;DRCT: Saving Image Super-Resolution away from Information Bottleneck 1 背景1.1 问题1.2 本文提出的方法 2 创新点3 方法4 模块4.1 问题描述4.2 深度特征提取模块4.3 同任务渐进式训练策略 5 效果5.1 和SOTA方法对比 论文&#xff1a;https://arxi…

数据结构 —— 红黑树

目录 1. 初识红黑树 1.1 红黑树的概念 1.2 红⿊树的规则 1.3 红黑树如何确保最长路径不超过最短路径的2倍 1.4 红黑树的效率:O(logN) 2. 红黑树的实现 2.1 红黑树的基础结构框架 2.2 红黑树的插⼊ 2.2.1 情况1&#xff1a;变色 2.2.2 情况2&#xff1a;单旋变色 2.2…

健身房数字化转型:SpringBoot管理系统

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式&#xff0c;是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示&#xff1a; 图4-1系统工作原理…

字符串相乘(全网最快0ms方法)

一&#xff1a;题目 二&#xff1a;思路 解释&#xff1a;每次相乘的结果不进位直接放进同一个数组里&#xff0c;相同位置则新放进的结果即可&#xff0c;最后得到左图的数组&#xff0c;再对其进行进位&#xff0c;得到正确的结果 Q1&#xff1a;数组的大小取多少&#xff1…

虚拟展厅和实景复刻有什么区别?应用场景和优势有哪些?

虚拟展厅和实景复刻在展览展示领域均扮演着重要角色&#xff0c;但二者之间存在显著的差异。以下是对这两者的详细比较&#xff1a; 一、定义与构建方式 虚拟展厅 虚拟展厅是利用数字技术和三维建模技术创建的虚拟展览环境&#xff0c;使参观者可以通过计算机、智能手机等设…

如何删除react项目的默认图标,使在浏览器中不显示默认图标favicon.ico

要删除 React 项目的默认图标&#xff0c;使在浏览器中不显示默认图标favicon.ico&#xff0c;其实有两种方法&#xff1a; 方法一 方法要点&#xff1a;删除掉 public 目录下的 favicon.ico 文件&#xff0c;再用浏览器访问时&#xff0c;如果加载不到图标文件&#xff0c;就…

计算机网络——路由器构成

算路由表是分布式去算——你算你的&#xff0c;我算我的 输出队列非先来先传 调度发生在哪里 缓存队列一般是应对——来数据方向的速度过快问题

项目活动进度计算题

六个时间参数①最早开始时间ESmax{紧前工作最早完成时间EF}&#xff08;紧前取大&#xff09; 最早完成时间EFES工期&#xff0c;从左→右计算&#xff0c;累加取大 ②最迟完成时间LFmin{紧后工作最迟开始时间LS}&#xff08;紧后取小&#xff09; 最迟开始时间LSLF-工期&am…

练习LabVIEW第四十题

学习目标&#xff1a; 用labvIEW做一个循环闪烁指示灯&#xff0c;要能够在前面板调节周期和占空比。 开始编写&#xff1a; 前面板 一个布尔指示灯一维数组&#xff0c;两个数值输入控件&#xff1b; 程序框图 添加一个while循环&#xff0c;循环内添加初始化数组&…

工商业储能是什么,工商业储能有什么作用?

随着全球能源结构的转型和“双碳”目标的推进&#xff0c;工商业储能系统作为新型电力系统的重要组成部分&#xff0c;正逐渐成为能源管理和电力市场的关键力量。工商业储能系统通过削峰填谷、需量管理、电力现货交易等多种方式&#xff0c;不仅能够有效降低企业的用电成本&…

DApp开发定制:合约设计与源码搭建支持快速上线

随着区块链技术的飞速发展&#xff0c;去中心化应用&#xff08;DApp&#xff09;已经成为区块链生态中不可或缺的一部分。DApp不仅改变了传统互联网应用的运作方式&#xff0c;还通过去中心化的理念和智能合约的支持&#xff0c;赋能了用户和开发者。无论是金融、游戏、社交、…

ssm+vue683基于VUE.js的在线教育系统设计与实现

博主介绍&#xff1a;专注于Java&#xff08;springboot ssm 等开发框架&#xff09; vue .net php phython node.js uniapp 微信小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不…

多商户电商平台开发指南:基于直播带货系统源码的搭建方案详解

本篇文章&#xff0c;小编将详细解析如何利用直播带货系统源码&#xff0c;快速搭建一套多商户电商平台的解决方案。 一、直播带货系统在多商户电商平台中的应用价值 在多商户电商平台中&#xff0c;直播带货系统可以帮助商家&#xff1a; 1.增加用户互动 2.提升转化率 3.…

登录功能设计(php+mysql)

一 登录功能 1. 创建一个登录页面&#xff08;login.php&#xff09;&#xff0c;包含一个表单&#xff0c;用户输入用户名和密码。 2. 在表单的提交事件中&#xff0c;使用PHP代码处理用户输入的用户名和密码。 3. 首先&#xff0c;连接MySQL数据库。然后&a…

P3-1.【结构化程序设计】第一节——知识要点:算法、顺序结构程序设计、if语句的语法结构及各种用法

讲解视频&#xff1a; P3-1.【结构化程序设计】第一节——知识要点&#xff1a;算法、顺序结构程序设计、if语句的语法结构及各种用法 知识要点&#xff1a;算法、顺序结构程序设计、if语句的语法结构及各种用法 一、算法、顺序结构程序设计任务分析 知识要点&#xff1a;算法…

18、论文阅读:AOD-Net:一体化除雾网络

AOD-Net: All-in-One Dehazing Network 前言介绍相关工作物理模型传统方法深度学习方法 建模与扩展变换后的公式网络设计与高级特征任务相结合 除雾评价数据集和实现 前言 该论文提出了一种基于卷积神经网络&#xff08;CNN&#xff09;的图像去雾模型&#xff0c;称为 All-in…