数据结构与算法—单链表

news2024/11/18 18:43:20

目录

一、链表

1、链表的概念及结构

2、分类

二、实现单向链表

1、声明链表结构体 

2、输出

3、头插&尾插

4、头删尾删

5、查找

6、指定位置插入

7、删除指定节点

8、删除指定节点的后一个节点

9、单链表的销毁

完整版

LList.h

LList.c

text.c


一、链表

 1、链表的概念及结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。

这张图生动形象地呈现了链表的结构。 

如同高铁一般,从头到尾一个连着一个。

2、分类

主要有两种类型的链表:单向链表和双向链表。在 单向链表中,每个节点包含一个数据元素和一个指向下一个节点的引用。而在 双向链表中,每个节点有两个引用,一个指向前一个节点,另一个指向后一个节点。

 

  • 从上图可看出,链式结构在逻辑上是连续的,但是在物理上不一定连续。
  • 现实中的结点一般都是从堆上申请出来的。
  • 从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续

本次讲解基础的单向链表。

二、实现单向链表

 我们创建三个文件:

  • 头文件LList.h用于调用库函数、声明结构体和函数。
  • 源文件LList.c存储函数。
  • 源文件text.c进行测试。

每个源文件都必须包含LList.h。

1、声明链表结构体 

#include <stdio.h>

typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;
  • 将链表的数据类型用SLTDatatype这个别名代TT替int,以后程序中使用到元素数据类型时都替换成SLTDatatype,方便日后修改顺序表数据类型。
  • 将结构体struct SListNode定义别名为SLTNode。
  • 结构体成员data为链表节点数据,数据类型是 SLTDataType。
  • next表示一个指向同类型结构体的指针,它指向单向链表下一个结点。

2、输出

void SLTPrint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur != NULL) {
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}
  •  接收传入参数为结构体的地址
  • 结构体指针cur指向头结点phead
  • 循环遍历链表,当cur不指向尾节点,则打印输出当前节点数据,cur指向下一个节点。
  • cur指向尾节点打印NULL。

3、头插&尾插

头插 

void SLPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuyLTNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}
  •  插入数据会修改头结点,所以传入头结点指针的地址,使用二级指针接收。
  • assert判断传入头节点指针的地址是否合法,为空则报错。(需要包含头文件<assert.h>)
  • *pphead 不需要断言,如果传入的链表为空,也可以进行插入数据。
  •  为新节点newnode开辟空间并将x储存其中,因为后续经常用到开辟空间,所以将这部分操作放入函数中。
  • 新节点newnode的next指针指向头结点*pphead
  • 头结点更新为newnode。

接下来讲解为新节点开辟空间的函数 BuyLTNode

新节点开辟空间  

SLTNode* BuyLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL) {
		perror("malloc fall");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
  • 为新节点开辟空间,返回值为新节点的地址,所以函数类型为 SLTNode* 结构体指针类型。
  • malloc函数为newnode开辟结构体大小个字节。
  • 判断是否开辟成功,失败则打印错误信息,结束函数运行。
  • 将新节点的数据data赋值为传入参数 x。
  • next赋值为空。

尾插

void SLPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead); 
	SLTNode* newnode = BuyLTNode(x);
	if (*pphead == NULL){
		*pphead = newnode;
    }
	else {
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
			tail = tail->next;
		
		tail->next = newnode;
	}		
}
  •  assert判断传入头节点指针的地址是否合法,为空则报错。
  • 为新节点newnode开辟空间并将x储存其中。
  • 插入时分两种情况:空链表 非空链表
  • 如果链表为空则直接将*pphead 指向新节点 newnode,使其成为新的链表的头节点。
  • 如果链表不为空,则创建变量tail指向头结点,循环遍历链表使tail指向尾节点,将新节点地址赋值给tail的next,成功将新节点添加到链表尾部。

4、头删尾删

头删

void SLPopFront(SLTNode** pphead)
{
	assert(pphead); 
	assert(*pphead);
	SLTNode* del = *pphead;
	*pphead = (*pphead)->next;
	free(del);
}
  • 第一个assert判断传入头节点指针的地址是否合法,为空则报错。
  • 第二个assert判断链表头节点是否为空,为空无法删除,则报错。
  • 定义变量del指向头节点,以便稍后释放该节点的内存。
  • 头节点指向头节点的next,也就是指向后一个节点。
  • 使用free函数释放del指向的已删除头节点空间。(需要包含头文件<stdlib.h>)

尾删 

void SLPopBack(SLTNode** pphead)
{
    assert(pphead);
    assert(*pphead);

    if ((*pphead)->next == NULL) {
        free(*pphead);
        *pphead = NULL;
    }
    else {
        SLTNode* tail = *pphead;
        while (tail->next->next) {
            tail = tail->next;
        }
        free(tail->next);
        tail->next = NULL;
    }
}
  • 第一个assert判断传入头节点指针的地址是否合法,为空则报错。
  • 第二个assert判断链表头节点是否为空,为空则报错。
  • 链表只有一个节点时,直接释放头节点空间,然后置空。
  • 链表有多个节点使,通过循环使变量 tail->next 找到尾节点,然后释放tail后一个节点的空间,也就是尾节点的空间,同时将其置空。

5、查找

SLTNode* STFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;
	while (cur) {
		if (cur->data == x)
			return cur;
		
		cur = cur->next;
	}
	return NULL;
}
  •  函数在单链表中查找包含特定数据值 x 的节点。
  • 变量cur通过循环找到数据data等于x的节点。
  • 找到则返回指向当前节点的指针 cur,否则返回值为空。

6、指定位置插入

指定位置之前


void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);
	if (*pphead == pos){
		SLPushFront(pphead, x);
	}
	else {
		SLTNode* prev = *pphead;
		while (prev->next != pos){
			prev = prev->next;
		}

		SLTNode* newnode = BuyLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}
  • 第一个assert判断传入头节点指针的地址是否合法,为空则报错。
  • 第二个assert判断传入指向链表中某个节点的指针pos是否合法,不存在则报错。
  • 如果在头节点位置之前插入,则调用头插解决。
  • 如果不是头节点位置,则创建一个指向链表头节点的指针 prev,然后使用循环找到要插入位置 pos 前面的节点。
  • 创建一个新的节点 newnode 并将数据值 x 存储在其中。

  • 修改 prev 节点的 next 指针,使其指向新节点 newnode,从而将新节点插入到 pos 前面。

 指定位置之后

void SLInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuyLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}
  •  assert判断传入指向链表中某个节点的指针pos是否合法,不存在则报错。
  • 创建一个新的节点 newnode 并将数据值 x 存储在其中。
  • newnode的next指针指向pos的后一项。
  • pos的next指向新节点newnode。

7、删除指定节点

void SLErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(*pphead);
	if (pos = *pphead){
		SLPopFront(pphead);
	}
	else {
		SLTNode* prev = *pphead;
		while (prev->next != pos) {
			prev = prev -> next;
		}
		prev->next = pos->next;
		free(pos);
	}
}
  • 第一个assert判断传入头节点指针的地址是否合法,为空则报错。
  • 第二个assert判断链表头节点是否为空,为空则报错。
  • pos节点为头节点,则调用头删解决。
  • pos不为头节点,则创建变量prev指向头节点,通过循环找到pos节点的前一个节点。
  • 将prev的next指向要删除的pos节点的下一个节点。
  • 释放pos空间

8、删除指定节点的后一个节点

void SLEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);
	SLTNode* next = pos->next;
	pos->next = next->next;
	free(next);
}
  •  创建一个指向要删除的节点 pos 后面节点的指针 next,以便稍后释放该节点的内存。
  • 修改 pos 节点的 next 指针,将其指向 next 的下一个节点,从而绕过要删除的节点,使链表不再包含它。

  • 最后,使用 free 函数释放 next 指向的节点的内存,完成删除操作。

9、单链表的销毁

void SListDestroy(SLTNode* pphead)
{
	SLTNode* cur = pphead;
	SLTNode* tmp = NULL;
	while (cur != NULL) {
		tmp = cur;
		cur = cur->next;
		free(tmp);
	}
}
  • 定义了两个指针,cur 和 tmp,用于遍历链表并释放内存。开始时,cur 被初始化为链表的头节点指针 pphead
  • 这是一个循环,它会一直执行,直到 cur 变为 NULL,也就是遍历到链表的末尾。

  • 在循环中,首先将 cur 赋值给 tmp,以便稍后释放 cur 指向的节点的内存。

  • 然后,将 cur 移动到下一个节点,即 cur = cur->next;

  • 最后,使用 free 函数释放 tmp 指向的节点的内存,即释放链表中的一个节点,接着进行循环依次释放节点直到链表最后。

完整版

LList.h

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

//打印链表
void SLTPrint(SLTNode* phead);

//头插尾插
void SLPushFront(SLTNode** pphead, SLTDataType x);
void SLPushBack(SLTNode** pphead, SLTDataType x);

//头删尾删
void SLPopFront(SLTNode** pphead);
void SLPopBack(SLTNode** pphead);

// 单链表查找
SLTNode * STFind(SLTNode * phead, SLTDataType x);

// 在pos之前插入
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
void SLInsertAfter(SLTNode* pos, SLTDataType x);

// 删除pos位置的值
void SLErase(SLTNode** pphead, SLTNode* pos);

// 删除pos位置后面的值
void SLEraseAfter(SLTNode* pos);

// 单链表的销毁
void SListDestroy(SLTNode* plist);

LList.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "LList.h"

void SLTPrint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur != NULL) {
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

SLTNode* BuyLTNode(SLTDataType x)//为新元素开辟空间
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL) {
		perror("malloc fall");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

void SLPushFront(SLTNode** pphead, SLTDataType x)//头插
{
	assert(pphead);  // 链表为空,pphead也不为空,因为他是头指针plist的地址
	//assert(*pphead); // 不能断言,链表为空,也需要能插入
	SLTNode* newnode = BuyLTNode(x);//newnode是局部变量
	newnode->next = *pphead;//头插后首节点next指向原有的首节点
	*pphead = newnode;//将链表的头指针 *pphead 指向新插入的节点
}

void SLPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead); // 链表为空,pphead也不为空,因为他是头指针plist的地址
	SLTNode* newnode = BuyLTNode(x);
	//两种情况
	//空链表  非空链表
	if (*pphead == NULL)//链表为空改变结构体指针
		*pphead = newnode;

	else {//不为空,则改变结构体的节点
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
			tail = tail->next;
		
		tail->next = newnode;
	}		
}

void SLPopFront(SLTNode** pphead)
{
	assert(pphead); // 链表为空,pphead也不为空,因为他是头指针plist的地址
	assert(*pphead); // 链表为空,不能头删。(当然你还可以用温柔的检查)
	SLTNode* del = *pphead;//指针del用于释放节点空间
	*pphead = (*pphead)->next;
	free(del);
}

void SLPopBack(SLTNode** pphead)
{
	assert(pphead); // 链表为空,pphead也不为空,因为他是头指针plist的地址
	assert(*pphead); // 链表为空,不能头删。(当然你还可以用温柔的检查)

	//只有一个节点
	if ((*pphead)->next == NULL) {
		free(*pphead);
		*pphead = NULL;//修改头节点为空
	}
	else {
		//第一种增加前项变量
		//SLTNode* prev = NULL;
		//SLTNode* tail = *pphead;
		//while (tail->next) {
		//	prev = tail;
		//	tail = tail->next;
		//}
		//free(tail);
		//prev->next = NULL;

		//第二种不新增变量
		//改变结构体的节点
		SLTNode* tail = *pphead;
		while (tail->next->next) {
			tail = tail->next;
		}
		free(tail->next);//将指向的最后一个节点释放
		tail->next = NULL;
	}
}


SLTNode* STFind(SLTNode* phead, SLTDataType x)//找到返回链表地址
{
	SLTNode* cur = phead;
	while (cur) {
		if (cur->data == x)
			return cur;
		
		cur = cur->next;
	}
	return NULL;
}

// 在pos之前插入
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);
	if (*pphead == pos){//在头节点前插入等于头插
		SLPushFront(pphead, x);
	}
	else {
		SLTNode* prev = *pphead;//用于找到pos前的位置

		while (prev->next != pos){
			prev = prev->next;
		}

		SLTNode* newnode = BuyLTNode(x);
		prev->next = newnode;//pos前一个位置next指向新开辟节点
		newnode->next = pos;//新节点next指向pos
	}
}

// 在pos之后插入
void SLInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuyLTNode(x);
	//下面两行不能调换顺序,否则无法链接新节点后项节点
	newnode->next = pos->next;
	pos->next = newnode;
}

void SLErase(SLTNode** pphead, SLTNode* pos)// 删除pos位置的值
{
	assert(pphead);
	assert(*pphead);//链表为空则不能删除
	if (pos = *pphead){
		SLPopFront(pphead);
	}
	else {
		SLTNode* prev = *pphead;
		while (prev->next != pos) {//找到pos前一个节点
			prev = prev -> next;
		}
		prev->next = pos->next;//将pos前一个节点的next指向pos后一个节点
		free(pos);//释放pos空间
	}
}

void SLEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);//后项为空则不能删除
	SLTNode* next = pos->next;
	pos->next = next->next;
	free(next);
}

void SListDestroy(SLTNode* pphead)
{
	SLTNode* cur = pphead;
	SLTNode* tmp = NULL;
	while (cur != NULL) {
		tmp = cur;
		cur = cur->next;
		free(tmp);
	}
}

text.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "LList.h"

void test1()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 5);
	SLPushFront(&plist, 4); 
	SLPushFront(&plist, 3);
	SLPushBack(&plist, 6);

	//SLPopFront(&plist);

	SLTNode* pos = STFind(plist, 3);
	SLInsert(&plist, pos, 99);

	//pos = STFind(plist, 2);
	//if (pos)
	//{
	//	SLInsertAfter(pos, 20);
	//}
	
	//SLPopBack(&plist);
	//SLPopBack(&plist);
	//SLPopBack(&plist);
	//SLPopBack(&plist);
	//SLPopBack(&plist);

	SLTPrint(plist);
}

int main()
{
	test1();
	return 0;
}

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

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

相关文章

Go错误处理方式真的不好吗?

平时经常上一些网络平台阅读一些技术讨论的话题&#xff0c;对Go语言方面也有些浅浅的关注&#xff0c;正如标题所问&#xff0c;Go语言错误处理可以说算是网络上开发中对Go语言吐槽最多的点之一&#xff0c;那么&#xff0c;Go错误处理真的很不堪吗&#xff1f; 对此我认为&a…

CANoe制作网关实现CAN(FD)报文故障注入(报文长度/timeout/信号错误/E2E)1

CANoe制作网关实现CAN报文故障注入&#xff08;报文长度/timeout/信号错误/E2E&#xff09; 文章目录 CANoe制作网关实现CAN报文故障注入&#xff08;报文长度/timeout/信号错误/E2E&#xff09;1.基本介绍和实现功能 1.基本介绍和实现功能 下面是一个完整的CAN/CANFD总线&…

【Wifi】Wifi架构介绍

Wifi架构介绍 本文基于Android介绍其Wifi架构。Wifi是许多操作系统提供的重要功能之一&#xff0c;特别是越来越多的车载系统wifi是其必备功能。为啥wifi是必备功能&#xff1f; 一方面是传统的上网&#xff08;现在有些车载使用DCM模块管理网络&#xff09;&#xff0c;另一方…

项目管理软件中注释功能的作用是什么?

在项目管理软件中&#xff0c;注释功能允许您对任务、文件夹和项目进行详细的标注。这一功能不仅便于团队成员之间的沟通与协作&#xff0c;还能提高项目管理的效率。通过在项目中添加评论&#xff0c;您可以及时了解项目的最新动态&#xff0c;提出疑问并寻求解决方案。此外&a…

【大模型应用开发教程】01_大模型简介

C1 大模型简介 一. 什么是LLM&#xff08;大语言模型&#xff09;&#xff1f;1. 发展历程2. 大语言模型的概念LLM的应用和影响 二、大模型的能力和特点1. 大模型的能力1.1 涌现能力&#xff08;emergent abilities&#xff09;1.2 作为基座模型支持多元应用的能力1.3 支持对话…

AN基础工具——填色工具

【AN基础工具——填色工具】 基本使用方法填色补充给色块周围画上线 变色动画渐变变色的蜥蜴 本篇内容&#xff1a;填色动画制作 重点内容&#xff1a;填色工具 工 具&#xff1a;Adobe Animate 2022 基本使用方法 填色补充 之前说图形要封闭才能填色&#xff0c;实际情况是有…

ESP8266 Node Mcu开发板连接WIFI并上报数据到MQTT服务器——物联网应用开发

一、前言 本文主要介绍关于ESP8266 Node Mcu开发板如何连接WIFI并将本地采集的数据上传到MQTT服务器中。 大家调试可以使用MQTTBox 二、WIFI连接 首先&#xff0c;导入WIFI连接所需的头文件&#xff0c;引入所需库。 #include <ESP8266WiFi.h> 声明字符串常量&#xff0…

3.1 模板测试与深度测试(Stencil Test Z Test)

一、模板测试&#xff08;Stencil Test&#xff09; 模板测试可以实现的一些效果图 1.是什么 ①从渲染管线出发&#xff1a;模板测试是在逐片源操作阶段&#xff0c;透明测试之后&#xff0c;深度测试之前的位置。 ②从书面概念上理解 说到模板测试&#xff0c;就要先说道模…

Java设计模式-结构性设计模式(享元设计模式)

简介 属于结构型模式&#xff0c;主要⽤于减少创建对象的数量&#xff0c;以减少内存占⽤和提⾼性能&#xff0c; 它提供了减少对象数量从⽽改善应⽤所需的对象结构的⽅式享元模式尝试重⽤现有的同类对象&#xff0c;如果未找到匹配的对象&#xff0c;则创建新对象应用场景 JAV…

C语言天花板——指针(进阶1)

接上次的指针初阶&#xff08;http://t.csdnimg.cn/oox5s&#xff09;&#xff0c;这次我们继续的探寻指针的奥秘&#xff0c;发车咯&#xff01;&#xff01;&#xff01;&#x1f697;&#x1f697;&#x1f697; 一、字符指针 可以看到我们将指针p给打印出来&#xff0c;就是…

LDA(Fisher)线性判别分析

LDA&#xff08;Fisher&#xff09;线性判别分析 对于二分类问题若存在一个 y i W x i y_iWx_i yi​Wxi​将样本 X \pmb X X投影到一维空间上 为了使两个样本能够较好的分开&#xff0c;应该是的每一个同类的样本的方差&#xff08;离散程度&#xff09;尽可能的小&#xff0…

Java实现hack汇编器

Hack汇编语言是一种特定于计算机体系结构的汇编语言&#xff0c;使用Hack架构的机器码指令来编写程序。Hack是一种基于Von Neumann结构的计算机体系结构&#xff0c;由Harvard大学的Nand to Tetris项目开发出来&#xff0c;用于实现计算机硬件和软件。 Hack汇编语言主要用于在…

FPGA面试题(5)

一.FPGA可以综合实现为RAM/ROM/CAM的三种资源及注意事项 三种资源&#xff1a;BLOCK RAM&#xff0c;触发器&#xff08;FF&#xff09;&#xff0c;查找表&#xff08;LUT&#xff09; 注意事项&#xff1a; 1.生成RAM&#xff0c;首选BLOCK RAM。因为BLOCK RAM是已经存在的“…

Jmeter压测http接口和java代码放在Jmeter执行

Jmeter无缝支持java语言&#xff0c;使其在市场上有很高的占有率&#xff0c;一些公司还专门对JMenter进行二次开发&#xff0c;使其成为公司级压测平台。 本次介绍JMenter的一些入门级使用&#xff0c;方便大家继续深入探索。 1、启动Jmeter 2、压测简单http接口 添加线程组…

Ant Design Vue设置表格滚动 宽度自适应 不换行

Ant Design Vue设置表格滚动 宽度自适应 不换行 添加以下属性即可解决这个问题&#xff1a; <a-table :columns"columns" :data-source"list":pagination"false"bordered:scroll"{ x: max-content }" >

Lazysysadmin靶机

信息收集 主机发现 nmap -sn 192.168.88.0/24 //-sn&#xff1a;制作主机发现&#xff0c;不做端口扫描&#xff1b;扫描结果包含本机IP 端口扫描 nmap --min-rate 10000 -p- 192.168.88.136 扫描端口详细信息 端口扫描发现&#xff0c;该主机的22、80、139、445、3306、…

进阶JAVA篇- DateTimeFormatter 类与 Period 类、Duration类的常用API(八)

目录 1.0 DateTimeFormatter 类的说明 1.1 如何创建格式化器的对象呢&#xff1f; 1.2 DateTimeFormatter 类中的 format&#xff08;LocalDateTime ldt&#xff09; 实例方法 2.0 Period 类的说明 2.1 Period 类中的 between(localDate1,localDate2) 静态方法来创建对象。 3.…

vue过渡动画效果

官网:https://cn.vuejs.org/v2/api/#transition 要与v-show,v-if 动态组件结合 给需要过渡的元素外层加<transition> ,并用name命名 , show:true, --------------------- <button click"show!show">button</button> <transition namefade>&…

基本地址变换机构

基本地址变换机构&#xff1a;用于实现逻辑地址到物理地址转换的一组硬件机构。 关于页号页表的定义&#xff0c;放个本人的传送门 1.页表寄存器 基本地址变换机构可以借助进程的页表将逻辑地址转换为物理地址。 1.作用 通常会在系统中设置一个页表寄存器&#xff08;PTR&…

KubeVela交付

有什么用我也不想说了&#xff0c;这个是k8s CI/CD,进阶玩家玩的了&#xff0c;比你们喜欢Arg CD更科学&#xff0c;更现代 在 Kubernetes 中安装 KubeVela helm repo add kubevela https://charts.kubevela.net/core helm repo update helm install --create-namespace -n v…