【数据结构】解密链表之旅(单链表篇)

前言

哈喽大家好,我是野生的编程萌新,首先感谢大家的观看。数据结构的学习者大多有这样的想法:数据结构很重要,一定要学好,但数据结构比较抽象,有些算法理解起来很困难,学的很累。我想让大家知道的是:数据结构非常有趣,很多算法是智慧的结晶,我希望大家在学习数据结构的过程是一种愉悦的心情感受。因此我开创了《数据结构》专栏,在这里我将把数据结构内容以有趣易懂的方式展现给大家。

 1.线性表链式存储结构定义

在上一篇博客中我们提到了线性表有两种存储方式,一种是顺序存储,一种是链式存储。线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。这就意味着,这些数据可以存在内存未被占用的任意位置。

 在之前的顺序结构中,每个数据元素只需要存储数据元素信息就可以了。现在链式结构中,除了要存储数据元素信息外,还要存储它的后继元素的存储地址。链式存储结构相比于顺序存储结构的优势在于插入和删除操作的高效性。由于链式存储结构中的元素通过指针连接,所以在插入和删除元素时,只需改变指针的指向,不需要移动其他元素,因此效率较高。而顺序存储结构需要移动元素位置,效率较低。线性表的链式存储结构是通过节点之间的指针来实现的,每个节点包含两个部分:数据域(存储数据元素信息的域)和指针域(存储直接后继位置的域)。n个节点链接成一个链表,即为线性表的链式存储结构(链表)。

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表的存储结构就与火车车厢相似,淡季时车次的车厢会有所减少,相对的在旺季时车次的车厢会有所增加。这需要将火车的车厢去掉或者加上,不会影响其他的车厢,每节车厢都是独立的。就像下面这张图:

每节车厢都是独立的,每节车厢都有自己的车门。假设每节车厢都是锁住的状态,他们都需要不同的钥匙来解锁且每次只能携带一把钥匙,该如何从车头走向车尾呢?最简单的方法就是:在每节车厢存放下一节车厢的钥匙。那么在链表这个“火车”中,每节“车厢”的情况是什么样子的呢?

 与顺序表不同的是,链表每节“车厢”都是独立申请下来的空间,我们称为“节点/结点”。对于线性表来说,总得有头有尾啊,链表也不能例外。我们把链表中的第一个节点存储的位置叫做头指针,那么整个链表的存取就必须是从头指针开始运行了。之后的每一个节点,其实就是上一个后继指针指向的位置。既然如此,那最后一个节点的指针指向哪里?什么!最后一个?当然就意味着后继不存在了,所以我们规定最后一个节点的指针为空。有时候,我们为了更加方便地对链表进行操作,会在单链表的第一个节点之前附设一个节点,我们成为头节点。下图为在上图基础上加一个头节点:

 头指针和头结点的异同点

  1. 头指针和头节点都是链表的概念,用于表示和操作链表的入口。
  2. 头指针是一个指针变量,存储的是第一个节点的地址;头节点是一个特殊节点,位于链表的第一个位置,不包含有用的数据。
  3. 头指针用于遍历链表中的所有节点;头节点用于简化对链表的操作。
  4. 头指针在链表中的位置是可变的,可以随着节点的插入或删除而改变;头节点在链表中位置固定,一般不会发生变化。

链表的种类非常多样,我们主要根据是否有头节点、单向或双向、是否循环将链表分为8类:

1.带头或者不带头

2.单向或者双向:

 3.是否循环:

虽然有这么多的链表结构,其实我们最常用的还是两种结构:不带头的单向链表和双向循环链表。我们这一篇就主要围绕单链表来介绍。 

2.单链表各个功能的实现

单链表是一种最简单的链表数据结构,它由一系列节点组成,每个节点包含两部分:数据域和指针域。数据域用于存储节点的数据,指针域用于指向下一个节点。单链表的特点是节点之间只有一个指针连接,每个节点只能访问下一个节点,不能访问前一个节点。链表的头节点是第一个节点,尾节点是最后一个节点,尾节点的指针域通常指向一个空地址(NULL)。用C语言来描述单链表的结构指针:

typedef int SLNDataType;
typedef struct SListNode 
{SLNDataType val;struct SListNode* next;
}SLNode;

在这里我们主要详细介绍单链表的插入删除等操作。在单链表中插入有尾插、头插、任意位置插入等操作,每次插入都需要申请空间,每次申请空间的操作都相同,我们干脆写一个函数来实现申请空间,这样能使我们的操作更加方便。

SLNode* CreateNode(SLNDataType* x)
{SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));if (newnode == NULL){perror("malloc fail");exit(-1);}newnode->val = x;newnode->next = NULL;return newnode;
}

2.1单链表的尾插和尾删

单链表的尾插操作步骤:

  1. 创建一个新的节点,设置其数据域为要插入的值,指针域为空。
  2. 检查链表是否为空。若为空,则将新节点作为链表的第一个节点。
  3. 若链表不为空,需要找到链表的最后一个节点。从链表的头节点开始遍历,直到遍历到最后一个节点(即指针域为空的节点)。
  4. 将最后一个节点的指针域指向新节点,将新节点插入到链表中。

我们需要使用单链表指针变量来创建头指针,所以我们在传参时要使用二级指针。我们来实现一下单链表的尾插操作:

void SLPushBack(SLNode** phead, SLNDataType x)
{assert(phead);SLNode* newnode=CreateNode(x);if (*phead == NULL){*phead = newnode;}else{SLNode* tail = *phead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}

单链表的尾删具体操作步骤为:

  1. 判断链表是否为空。如果链表为空,则无法进行尾删操作,直接返回。
  2. 如果链表只有一个结点,则将链表的头指针置为空,删除这个结点即可。
  3. 如果链表有多个结点,则需要遍历到倒数第二个结点,即指针指向要删除结点的前一个结点。
  4. 将前一个结点的 next 指针指向 NULL,断开要删除结点和链表的连接。
  5. 释放要删除结点的内存空间。

我们来实现一下单链表尾删操作:

void SLPopBack(SLNode** phead)
{assert(phead);assert(*phead);if ((*phead)->next == NULL){free(*phead);*phead = NULL;}else{SLNode* prev = NULL;SLNode* tail = *phead;while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);tail = NULL;prev->next = NULL;}
}

2.2单链表的头插和头删

单链表的头插具体操作步骤为:

  1. 首先,创建一个新的节点,并将要插入的元素值赋给新节点的数据域。
  2. 为了将新节点插入到链表中,需要将新节点的next指针指向链表的第一个节点,即原本的第一个节点。
  3. 然后,将链表的头节点指向新节点,即将新节点设为链表的第一个节点。

我们来实现一下这个操作:

void SLPushFront(SLNode** phead, SLNDataType x)
{assert(phead);SLNode* newnode = CreateNode(x);newnode->next = *phead;*phead = newnode;
}

单链表的头删具体操作步骤为:

  1. 检查链表是否为空。如果链表为空,无法进行头删操作,直接返回。
  2. 创建一个临时变量tmp,将其指向链表的第一个节点。
  3. 将链表的头节点指针指向第一个节点的下一个节点,即tmp->next。
  4. 释放temp指向的节点。

我们来实现一下这个操作:

void SLPopFront(SLNode** phead)
{assert(phead);assert(*phead);SLNode* tmp = (*phead)->next;free(*phead);*phead = tmp;
}

2.3单链表的任意位置插入和任意位置删除

单链表的任意位置插入和顺序表中的任意插入有所不同,在单链表中我们需要先编写一个链表数据元素的查找函数,然后输入一个节点值,接着返回相对应的节点,然后进行插入删除操作。链表数据元素查找函数实现如下:

SLNode* SLFind(SLNode* phead, SLNDataType x)
{SLNode* cur = phead;while (cur != NULL){if (cur->val == x){return cur;}cur = cur->next;}return NULL;
}

我们创建一个单链表节点指针变量接收查找函数的返回信息。单链表的任意位置插入操作的具体步骤为:

  1. 首先,判断要插入的位置是否合法,即判断插入的位置是否超出了链表的范围。如果超出了范围,则不进行插入操作。

  2. 然后我们还要判断一下插入位置是否是在头节点,如果插入的位置是头节点,那我们直接调用头插的函数就行,反之,创建一个新结点,将要插入的数据放入在其中。

  3. 遍历链表,找到插入位置的前一个节点,即要在其后面插入新节点。

  4. 将新节点的指针域指向插入位置的前一个节点原来指向的节点。

  5. 将插入位置的前一个节点的指针域指向新节点。

我们来实现一下这个操作:

void SLInsert(SLNode** phead, SLNode* pos, SLNDataType x)
{assert(phead);assert(pos);if (*phead == pos){SLPushFront(phead, x);}else{SLNode* prev = *phead;while (prev->next != pos){prev = prev->next;}SLNode* newnode = CreateNode(x);prev->next = newnode;newnode->next = pos;}
}

当然还是有人喜欢任意位置之后插入,我们也来实现一下:

void SLInsertAfter(SLNode* pos, SLNDataType x)
{assert(pos);SLNode* newnode = CreateNode(x);newnode->next = pos->next;pos->next = newnode;
}

讲完任意位置插入了,我们现在来整任意位置删除,任意位置删除的基本操作步骤为:

  1. 首先判断单链表是否为空,若为空则无法进行删除操作,直接返回。
  2. 如果要删除的位置是头节点,则将头节点指向下一个节点,并释放原来的头节点(头删操作)。
  3. 如果要删除的位置不是头节点,需找到要删除节点的前一个节点。遍历单链表,找到要删除节点的前一个节点。可以使用两个指针prev和pos,prev指向要删除节点的前一个节点,pos指向要删除节点。当pos指向要删除节点时,prev指向的即为要删除节点的前一个节点。
  4. 判断要删除节点是否存在,如果pos为空,则说明要删除的节点不存在,直接返回。
  5. 将p的next指针指向q的next指针,即跳过要删除的节点。
  6. 释放要删除节点q的内存空间。

我们来实现一下这个操作:

void SLErase(SLNode** phead, SLNode* pos)
{assert(phead);if (*phead == pos){SLPopFront(phead);}else{SLNode* prev = *phead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}
}

 到了这里应该知道我要干什么了吧,实现任意位置之后的删除:

void SLEraseAfter(SLNode* pos)
{assert(pos);assert(pos->next);SLNode* tmp = pos->next;pos->next = tmp->next;free(tmp);tmp = NULL;
}

2.4单链表的销毁

单链表的销毁指的是将整个链表都删除,回收其占用的内存空间。单链表的销毁的具体步骤为:

  1. 首先需要定义一个指针变量,用于遍历链表。假设此变量为cur,并将其初始化为链表的头节点。
  2. 创建一个临时指针变量,用于保存当前节点的下一个节点。假设此变量为next。
  3. 使用循环遍历链表,直到cur指针变量为空。
  4. 在循环中,将next指针变量指向cur指针变量的下一个节点。
  5. 释放cur指针变量指向的节点的内存空间。可以使用free()函数来实现内存的释放。
  6. 将cur指针变量指向next指针变量,即将cur指针变量移动到下一个节点上。
  7. 重复步骤3-6,直到遍历完整个链表,即cur指针变量指向空。

当cur指针变量为空时,说明链表的所有节点都已经被删除,此时整个链表就被销毁了。我们来实现一下这个操作:

void SLDestory(SLNode** phead)
{assert(phead);SLNode* cur = *phead;while (cur){SLNode* next = cur->next;free(cur);cur = next;}*phead = NULL;
}

在进行链表的销毁时要注意:我们需要确保链表中的每个节点都被释放,以避免内存泄漏。同时,需要注意释放节点内存空间前,需要先保存下一个节点的指针,否则在释放当前节点后,就无法访问到下一个节点了。

3.多文件实现单链表

SList.h文件:

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLNDataType;
typedef struct SListNode 
{SLNDataType val;struct SListNode* next;
}SLNode;
void SLprint(SLNode* phead);//链表打印
void SLPushBack(SLNode** phead, SLNDataType x);//链表的尾插
void SLPushFront(SLNode** phead, SLNDataType x);//链表的头插
void SLPopBack(SLNode** phead);//链表的尾删
void SLPopFront(SLNode** phead);//链表的头删
SLNode* SLFind(SLNode* phead, SLNDataType x);//链表的查找
void SLInsert(SLNode** phead, SLNode* pos, SLNDataType x);//链表的插入
void SLErase(SLNode** phead, SLNode* pos);//链表的删除
void SLInsertAfter(SLNode* pos, SLNDataType x);//后面插入
void SLEraseAfter(SLNode* pos);//后面删除
void SLDestory(SLNode** phead);//链表的销毁

SList.c文件:

#include"SList.h"
void SLprint(SLNode* phead)
{SLNode* cur = phead;while (cur!= NULL){printf("%d->", cur->val);cur = cur->next;}printf("NULL\n");
}
SLNode* CreateNode(SLNDataType* x)
{SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));if (newnode == NULL){perror("malloc fail");exit(-1);}newnode->val = x;newnode->next = NULL;return newnode;
}
void SLPushBack(SLNode** phead, SLNDataType x)
{assert(phead);SLNode* newnode=CreateNode(x);if (*phead == NULL){*phead = newnode;}else{SLNode* tail = *phead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}
void SLPushFront(SLNode** phead, SLNDataType x)
{assert(phead);SLNode* newnode = CreateNode(x);newnode->next = *phead;*phead = newnode;
}
void SLPopBack(SLNode** phead)
{assert(phead);assert(*phead);if ((*phead)->next == NULL){free(*phead);*phead = NULL;}else{SLNode* prev = NULL;SLNode* tail = *phead;while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);tail = NULL;prev->next = NULL;}
}
void SLPopFront(SLNode** phead)
{assert(phead);assert(*phead);SLNode* tmp = (*phead)->next;free(*phead);*phead = tmp;
}
SLNode* SLFind(SLNode* phead, SLNDataType x)
{SLNode* cur = phead;while (cur != NULL){if (cur->val == x){return cur;}cur = cur->next;}return NULL;
}
void SLInsert(SLNode** phead, SLNode* pos, SLNDataType x)
{assert(phead);assert(pos);if (*phead == pos){SLPushFront(phead, x);}else{SLNode* prev = *phead;while (prev->next != pos){prev = prev->next;}SLNode* newnode = CreateNode(x);prev->next = newnode;newnode->next = pos;}
}
void SLErase(SLNode** phead, SLNode* pos)
{assert(phead);if (*phead == pos){SLPopFront(phead);}else{SLNode* prev = *phead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}
}
void SLInsertAfter(SLNode* pos, SLNDataType x)
{assert(pos);SLNode* newnode = CreateNode(x);newnode->next = pos->next;pos->next = newnode;
}
void SLEraseAfter(SLNode* pos)
{assert(pos);assert(pos->next);SLNode* tmp = pos->next;pos->next = tmp->next;free(tmp);tmp = NULL;
}
void SLDestory(SLNode** phead)
{assert(phead);SLNode* cur = *phead;while (cur){SLNode* next = cur->next;free(cur);cur = next;}*phead = NULL;
}

test.c文件(在这里测试函数功能):

#include"SList.h"
void test()
{SLNode* plist = NULL;//测试尾插SLPushBack(&plist, 4);SLPushBack(&plist, 5);SLPushBack(&plist, 6);SLPushBack(&plist, 7);SLprint(plist);//测试头插SLPushFront(&plist, 3);SLPushFront(&plist, 2);SLPushFront(&plist, 1);SLPushFront(&plist, 0);SLprint(plist);//测试尾删SLPopBack(&plist);SLprint(plist);//测试头删SLPopFront(&plist);SLprint(plist);//测试任意插入SLNode* pos = SLFind(plist, 3);SLInsert(&plist, pos, 30);SLprint(plist);//测试任意删除pos = SLFind(plist, 30);SLErase(&plist, pos);SLprint(plist);
}
int main()
{test();return 0;
}

我们看看对各个函数测试结果:

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

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

相关文章

QLExpress入门及实战总结

文章目录 1.背景2.简介3.QLExpress实战3.1 基础例子3.2 低代码实战3.2.1 需求描述3.2.1 使用规则引擎3.3.2 运行结果 参考文档 1.背景 最近研究低代码实现后端业务逻辑相关功能&#xff0c;使用LiteFlow作为流程编排后端service服务, 但是LiteFlow官方未提供图形界面编排流程。…

大型语言模型自我进化综述

24年4月来自北大的论文“A Survey on Self-Evolution of Large Language Models”。 大语言模型&#xff08;LLM&#xff09;在各个领域和智体应用中取得了显着的进步。 然而&#xff0c;目前从人类或外部模型监督中学习的LLM成本高昂&#xff0c;并且随着任务复杂性和多样性的…

InLine Chat功能优化对标Github Copilot,CodeGeeX带来更高效、更直观的编程体验!

VSCode中的CodeGeeX 插件上线InLine Chat功能后&#xff0c;收到不少用户的反馈&#xff0c;大家对行内交互编程这一功能非常感兴趣。近期我们针对这个功能再次进行了深度优化&#xff0c;今天详细介绍已经在VSCode插件v2.8.0版本上线的 CodeGeeX InLine Chat功能&#xff0c;以…

Visual Studio 2022专业版安装步骤

Visual studio下载 首先进入下载官网,下载2022专业版 我勾选了以下几个和c#开发有关的&#xff0c;后面缺什么还可以再安装所有以少勾了问题也不大 然后改一下安装位置,点击安装 专业版秘钥激活 打开设置选择帮助,注册vs 专业版密钥: TD244-P4NB7-YQ6XK-Y8MMM-YWV2J

【MinGW】MinGW-w64的安装及配置教程

目录 &#x1f31e;1. MinGW简介 &#x1f31e;2. MinGW安装详情 &#x1f30a;2.1 资源包获取 &#x1f30a;2.2 安装详情 &#x1f31e;1. MinGW简介 MinGW (Minimalist GNU for Windows) 是一个在 Windows 平台上开发软件的开发工具集合。它提供一组用于编译 Windows 应…

Python-VBA函数之旅-tuple函数

目录 一、tuple函数的常见应用场景 二、tuple函数使用注意事项 三、如何用好tuple函数&#xff1f; 1、tuple函数&#xff1a; 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、推荐阅读&#xff1a; 个人主页&#xff1a; https://myelsa1024.blog.csdn.net/ 一、tu…

共赴科技盛会“2024南京智博会”11月在南京国际博览中心召开

2024年&#xff0c;南京这座历史悠久的文化名城迎来了一场科技与智慧交织的盛会——南京智博会|南京国际智慧城市、物联网、大数据。本次博览会以智慧城市、人工智能、消费电子、物联网、大数据为主题&#xff0c;汇聚了全球各地的智能科技精英&#xff0c;共同探讨智慧城市建设…

大学c语言基础很差,能不能学51单片机?会不会很困难?

开始前我分享下我的经历&#xff0c;我刚入行时遇到一个好公司和师父&#xff0c;给了我机会&#xff0c;一年时间从3k薪资涨到18k的&#xff0c; 我师父给了一些51单片机学习方法和资料&#xff0c;让我不断提升自己&#xff0c;感谢帮助过我的人&#xff0c; 如大家和我一样…

HTML静态网页成品作业(HTML+CSS+JS)——华为商城网页(1个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;使用Javacsript代码实现首页图片切换轮播效果&#xff0c;共有1个页面…

IT行业现状与未来趋势分析

IT行业现状与未来趋势显示出持续的活力和变革&#xff0c;以下是上大学网&#xff08;www.sdaxue.com&#xff09;关于IT行业现状与未来趋势分析&#xff0c;供大家参考。 当前现状&#xff1a; 市场需求持续增长&#xff1a;随着信息时代的深入发展&#xff0c;各行各业对信息…

k8s endpoint

Endpoint Service 并不是和 pod 直接相连的&#xff0c;Endpoint 介于两者之间。Endpoint 资源就是暴露一个服务的 IP 地址和端口的列表。 虽然在 spec 服务中定义了 pod 选择器&#xff0c;但在重定向传入连接时不会直接使用它。选择器用于构建 IP 和端口列表&#xff0c;然…

材料物理 笔记-8

原内容请参考哈尔滨工业大学何飞教授&#xff1a;https://www.bilibili.com/video/BV18b4y1Y7wd/?p12&spm_id_frompageDriver&vd_source61654d4a6e8d7941436149dd99026962 或《材料物理性能及其在材料研究中的应用》&#xff08;哈尔滨工业大学出版社&#xff09; ——…

OpenCV中的模块:点云配准

点云配准是点云相关的经典应用之一。配准的目的是估计两个点云之间位姿关系从而完成两者对应点之间的对齐/对应,因而在英文中又叫“align”、“correspondence”。笔者曾经是基于OpenCV进行三维重建的,并且从事过基于深度学习的6DoF位置估计等工作。在这些工作中,除了重建点…

org.hsqldb.jdbcDriver 类,导致 ClassNotFoundException 异常如何解决?

确保JDBC驱动包存在&#xff1a;检查系统是否已经安装了HSQLDB JDBC驱动。如果没有安装或驱动没有正确放置在类路径中&#xff0c;需要下载并添加它。你可以从 HSQLDB官网 下载JDBC驱动包。 添加JDBC驱动到类路径&#xff1a;将下载的HSQLDB JDBC驱动&#xff08;通常是一个JA…

uniapp实现下拉刷新效果-uniapp原生接口

onPullDownRefresh | uni-app官网 1、需要在 pages.json 里&#xff0c;找到的当前页面的pages节点&#xff0c;并在 style 选项中开启 enablePullDownRefresh 2、生命周期中添加onPullDownRefresh&#xff0c;下拉时获取数据 3、处理完数据后&#xff0c;停止下拉效果stopPul…

腐烂的橘子BFS

题目&#xff1a; 腐烂的橘子 在给定的 m x n 网格 grid 中&#xff0c;每个单元格可以有以下三个值之一&#xff1a; 值 0 代表空单元格&#xff1b; 值 1 代表新鲜橘子&#xff1b; 值 2 代表腐烂的橘子。 每分钟&#xff0c;腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子…

如何把学浪上的视频保存到电脑

在这个信息爆炸的时代&#xff0c;知识的获取从未如此便捷&#xff0c;而学浪平台正是这股知识浪潮中的一艘航船。但是&#xff0c;当网络信号如同海上的风浪般变幻莫测&#xff0c;你是否曾渴望拥有一片宁静的港湾&#xff0c;让那些宝贵的学习资源得以永久停泊&#xff1f;今…

【C++】再识构造函数:初始化列表新方式

欢迎来到CILMY23的博客 &#x1f3c6;本篇主题为&#xff1a; 再识构造函数&#xff1a;初始化列表新方式 &#x1f3c6;个人主页&#xff1a;CILMY23-CSDN博客 &#x1f3c6;系列专栏&#xff1a;Python | C | C语言 | 数据结构与算法 | 贪心算法 | Linux &#x1f3c6;感…

ubuntu18.04的安装Anaconda步骤

参考&#xff1a;http://t.csdnimg.cn/7KX4p 这个链接写的很全&#xff0c;我主要记以下自己的步骤 1https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/ 这个链接下载的Anaconda3-2023.03-0-Linux-x86_64.sh 然后进入下载的目录&#xff0c; bash Anaconda3-2023.0…

SpringBoot集成Seata分布式事务OpenFeign远程调用

Docker Desktop 安装Seata Server seata 本质上是一个服务&#xff0c;用docker安装更方便&#xff0c;配置默认&#xff1a;file docker run -d --name seata-server -p 8091:8091 -p 7091:7091 seataio/seata-server:2.0.0与SpringBoot集成 表结构 项目目录 dynamic和dyna…