当前位置: 首页 > news >正文

数据结构应用实例(四)——最小生成树

Content:

      • 一、问题描述
      • 二、算法思想
      • 三、代码实现
      • 四、两种算法的比较
      • 五、小结

一、问题描述

  利用 prim 算法和 kruskal 算法实现最小生成树问题;

二、算法思想

  首先判断图是否连通,只有在连通的情况下才进行最小树的生成;

三、代码实现

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define maxx 999999
#pragma warning(disable:4996)typedef struct arc//弧
{	int index;//指向节点编号float weight;//边的权值struct arc *next;
}AR;typedef struct edge
{int i,j;//边的起点和终点编号float edge;//边的权值
}EG;typedef struct MyGraph
{int type;//0表示无向图,1表示有向图int arcnum,vexnum;//边的个数、顶点个数char **vexname;//存放顶点名称的二维数组	AR *N;//表头数组float **A;//邻接矩阵,这里使用float型EG *E;//存放边的三元组
}GH;int findvex(char *s,GH *G);//找到图G中名称为s的节点编号,并将其返回
void creatGraph(GH *G);//创建图G
void showGraph(GH *G);//以邻接表的形式显示图Gint isconnect(GH *G);//判断图G是否连通,是则返回1,否则返回0
int BFSvisit(int start,GH *G);//对图G从start号点开始广度优先遍历,返回访问过的节点个数void prim(GH *G,int start);//利用prim算法,求图G中以start为起点的最小生成树
void showTree(AR *N,char **vexname,int n);//显示最小生成树,N表示表头数组,vexname中存放顶点名称,n表示节点数量void kruskal(GH *G);//利用kruskal算法,求图G中的最小生成树
void heapsort(EG *E, int n);//对含有n条边的三元组E进行堆排序
void adjust(int index, EG *E, int n);//对利用顺序表E表示的含有n个节点的完全二叉树的index号节点进行筛选
void changeflag(int m,int y,int *flag, AR *N,int n);//将图G中m号节点所在的连通分支上的点的flag统一赋值为y,N为表头数组,n表示原图中节点个数int main(void)
{GH *G;int begin;//起点编号//创建图G=(GH *)malloc(sizeof(GH));creatGraph(G);printf("图的邻接表形式:\n");showGraph(G);//if (!isconnect(G))//{//	printf("该图不连通,不具有最小生成树.\n");
//		exit(-1);
//	}printf("该图连通,寻找最小生成树:\n");printf("\n\n====================prim算法========================\n");printf("请输入起点编号(-1结束):");scanf("%d", &begin);while (begin != -1){prim(G, begin);printf("请输入起点编号(-1结束):");scanf("%d", &begin);}printf("\n\n================kruskal算法========================\n");kruskal(G);free(G);return 0;
}int findvex(char *s,GH *G)//找到图G中名称为s的节点编号,并将其返回
{for(int i=0;i<G->vexnum;i++){if(strcmp(s,G->vexname[i])==0)return i;}printf("该点不存在\n");exit(-1);
}void creatGraph(GH *G)//创建图G
{int i,j,k,n,m;float edge;char filename[]="graph.txt";char str[10],str1[10];FILE *fp;AR *p;fp=fopen(filename,"r");if(!fp){printf("文件打开失败!\n");exit(-1);}//读取图的类型和节点数量fscanf(fp,"%d%d",&G->type,&n);	G->vexnum=n;//分配空间G->vexname=(char **)malloc(n*sizeof(char *));G->N=(AR *)malloc(n*sizeof(AR));G->A=(float **)malloc(n*sizeof(float *));//初始化A和Nfor (i = 0; i < n; i++){G->N[i].next = NULL;G->A[i] = (float *)malloc(n*sizeof(float));for (j = 0; j < n; j++)G->A[i][j]=maxx;}//读取顶点名称for(i=0;i<n;i++){fscanf(fp,"%s",str);//先分配空间G->vexname[i]=(char *)malloc(strlen(str)*sizeof(char));strcpy(G->vexname[i],str);}    //读取边fscanf(fp,"%d",&m);G->arcnum=m;//边的个数G->E=(EG *)malloc((m+1)*sizeof(EG));//0号位置不存放数据for(k=1;k<=m;k++){fscanf(fp,"%s%s%f",str,str1,&edge);i=findvex(str,G);j=findvex(str1,G);//邻接表中添加节点p=(AR *)malloc(sizeof(AR));p->index=j;p->weight=edge;p->next=G->N[i].next;G->N[i].next=p;//邻接矩阵G->A[i][j]=edge;//三元组G->E[k].i=i;G->E[k].j=j;G->E[k].edge=edge;if(G->type==0){//邻接表中添加节点p=(AR *)malloc(sizeof(AR));p->index=i;p->weight=edge;p->next=G->N[j].next;G->N[j].next=p;//邻接矩阵G->A[j][i]=edge;}}fclose(fp);
}void showGraph(GH *G)//以邻接表的形式显示图G
{int i;AR *p; //用于遍历for (i = 0; i < G->vexnum; i++){printf("%s",G->vexname[i]);p=G->N[i].next;while (p){if (G->type == 1)printf("-->");elseprintf("--");printf("%s(%.2f)",G->vexname[p->index],p->weight);p=p->next;}printf("\n");}printf("\n");
}int isconnect(GH *G)//判断图G是否连通,是则返回1,否则返回0
{int k;k=BFSvisit(0,G);//k:广度优先遍历访问到的节点个数if(k==G->vexnum)return 1;elsereturn 0;
}int BFSvisit(int start,GH *G)//对图G从start号点开始广度优先遍历,返回访问过的节点个数
{int i,n;int *visit;int *q;int front,rear;AR *p;//空间分配n=G->vexnum;visit=(int *)malloc(n*sizeof(int));q=(int *)malloc(n*sizeof(int));//初始化for(i=0;i<n;i++)visit[i]=0;	visit[start]=1;q[0]=start;front=0;rear=1;while(front!=rear){p=G->N[q[front]].next;//对出队点的直接后继进行遍历//printf("%s\n",G->vexname[q[front]]);	front++;while (p){if (visit[p->index] == 0)//如果未被访问,添加访问标记,入队{visit[p->index]=1;q[rear]=p->index;rear++;}p=p->next;}		}free(visit);free(q);return rear;//返回广度优先遍历访问到的节点个数
}void prim(GH *G, int start)//利用prim算法,求图G中以start为起点的最小生成树
{int i,n;int *pre;float *weight;	AR *N,*p;float min;int next;n=G->vexnum;pre=(int *)malloc(n*sizeof(int));//前驱数组weight=(float *)malloc(n*sizeof(float));//距生成树的最短距离,值为0.0表示属于生成树N=(AR *)malloc(n*sizeof(AR));//表头数组,用于存放最小生成树	//1.初始化for (i = 0; i < n; i++){if (G->A[start][i] < maxx)pre[i] = start;			elsepre[i] = -1;weight[i] = G->A[start][i];N[i].next=NULL;}weight[start]=0;//起点放到生成树中//2.寻找距离已生成树最近的顶点min=maxx;for (i = 0; i < n; i++){if (weight[i] != 0 && weight[i] < min)//从与原图中与最小生成树连通的节点中选择{min=weight[i];next=i;}}while (min < maxx){weight[next]=0;//3.放入最小生成树中//相应地添加节点p=(AR *)malloc(sizeof(AR));p->index=next;p->weight=G->A[pre[next]][next];p->next=N[pre[next]].next;N[pre[next]].next=p;if (G->type == 0){p=(AR *)malloc(sizeof(AR));p->index=pre[next];p->weight=G->A[pre[next]][next];p->next=N[next].next;N[next].next=p;}//4.更新weight和prep=G->N[next].next;while (p){if (weight[p->index] != 0 && weight[p->index] > p->weight){weight[p->index]=p->weight;pre[p->index]=next;}p=p->next;}//继续寻找min=maxx;for (i = 0; i < n; i++){if (weight[i] != 0 && weight[i] < min){min=weight[i];next=i;}}}//结果显示showTree(N,G->vexname,n);free(pre);free(weight);free(N);
}void showTree(AR *N, char **vexname, int n)//显示最小生成树,N表示表头数组,vexname中存放顶点名称,n表示节点数量
{int i;AR *p;printf("最小生成树的邻接表形式如下:\n");for (i = 0; i < n; i++){	p=N[i].next;if (p){printf("%s", vexname[i]);while (p){printf("--%s(%.2f)", vexname[p->index], p->weight);p = p->next;}printf("\n");}}printf("\n");
}void kruskal(GH *G)//利用kruskal算法,求图G中的最小生成树
{int i;int m,n;int p,q;int *flag;	AR *N,*t;m=G->arcnum;n=G->vexnum;flag=(int *)malloc(n*sizeof(int *));//位于同一连通分量的节点的flag值相同N=(AR *)malloc(n*sizeof(AR));//表头数组,用于存放最小生成树//1.初始化for (i = 0; i < n; i++){flag[i] = i;//各自为营N[i].next = NULL;}//2.对存放边的三元组G->E进行堆排序heapsort(G->E,m);//3.挑取边for (i = 1; i <= m; i++){   //读取两端节点编号p=G->E[i].i;q=G->E[i].j;if(flag[p]!=flag[q])//4.若两端位于不同的连通分支,表示该边可选{changeflag(q,flag[p],flag,N,n);//将q号节点所在的连通分支上的点的flag改为flag[p]//相应地添加节点t=(AR *)malloc(sizeof(AR));t->index=q;t->weight=G->A[p][q];t->next=N[p].next;N[p].next=t;//无向图需要双边添加节点if (G->type == 0){t = (AR *)malloc(sizeof(AR));t->index=p;t->weight=G->A[p][q];t->next=N[q].next;N[q].next=t;}}}//结果显示showTree(N,G->vexname,n);free(flag);free(N);
}void heapsort(EG *E, int n)//对含有n条边的三元组E进行堆排序
{int i;//首先调整所有的父节点for (i = n / 2; i >= 1; i--)adjust(i,E,n);//然后进行n-1趟堆排序,找出n-1个最大值for (i = 1; i <= n - 1; i++){   //首先进行首尾交换E[0]=E[1];E[1]=E[n-i+1];E[n-i+1]=E[0];//然后进行调整adjust(1,E,n-i);}
}void adjust(int index, EG *E, int n)//对利用顺序表E表示的含有n个节点的完全二叉树的index号节点进行筛选
{int i,j;i=index;j=2*i;E[0]=E[i];//哨兵while (j <= n){if (j + 1 <= n)//保证j始终指向较大的孩子{if(E[j+1].edge>E[j].edge)j=j+1;}if (E[j].edge > E[0].edge)//如果大于哨兵{E[i]=E[j];//将值赋给父节点i=j;//更新循环变量j=2*i;}else//E[j].edge <= E[0].edgebreak;//跳出循环}//i没有孩子或者E[j].edge <= E[0].edgeE[i]=E[0];
}void changeflag(int m,int y,int *flag, AR *N,int n)//将图G中m号节点所在的连通分支上的点的flag统一赋值为y,N为表头数组,n表示原图中节点个数
{   //利用BFS,访问m号节点所在的连通分支,更改其flag值int *q;int front,rear;AR *p;q=(int *)malloc(n*sizeof(int));//初始化flag[m]=y;q[0]=m;front=0;rear=1;while (front != rear){p=N[q[front]].next;front++;while (p){if (flag[p->index] != y){flag[p->index]=y;q[rear]=p->index;rear++;}p=p->next;}}free(q);
}

四、两种算法的比较

  两种算法从不同的角度提出了最小生成树的构造方法:
  Prim 算法从选节点的角度出发,每次都选距离生成树最近的节点,从而保证了最后的结果为最小生成树,时间复杂度与顶点个数 N 有关;每次选择最近顶点,需要进行一次遍历,比较 N 次,之后将其添加进最小生成树,更新 weight 和 pre,一次遍历,N 次操作,总共需要选择 N-1 次,所以 prim 算法的时间复杂度为 O ( N 2 ) O(N^2) O(N2)
  Kruskal 算法从另一个角度出发,选取边,每次都选取权值最小的、添加之后不构成圈的边进行添加,同样保证了结果为最小生成树,时间复杂度与边的个数 e 有关;为方便后续处理,首先对边按权值大小进行排序,时间复杂度为 O ( e l o g 2 e ) O(elog_2e) O(elog2e),然后对每条边进行判断并决定是否添加,e 次操作,所以总的时间复杂度为 O ( e l o g 2 e ) O(elog_2e) O(elog2e)
  综合所述,Prim 算法适合顶点相对较少而边相对稠密的网的最小生成树的求解,而 Kruskal 算法适合于求边比较稀疏的网的最小生成树;

五、小结

1、 首先进行一次 BFS,返回遍历到的节点数目,据此判断图是否连通,非连通图不具有最小生成树;
2、 对于 prim 算法,将最小生成树中的节点的 weight[i] 置为0,表示其在最小生成树中;
3、 最小生成树中每增添一个节点,更新其直接后继的 weight 和 pre;
4、 kruskal 算法,为图的结构体添加成员 E ——存放边的结构体数组,成员为两节点编号和边的权值,这样在创建图时,可以直接创建 E,同时,图的数据文件中添加边的条数;
5、 在执行 kruskal 算法时,可对 E 按照边的权值进行堆排序,避免了从邻接矩阵中再次读取边的操作,降低了算法的时间复杂度;
6、 通过 BFS 来实现某一连通分支上所有点的 flag 值的更改;

相关文章:

数据结构应用实例(四)——最小生成树

Content&#xff1a; 一、问题描述二、算法思想三、代码实现四、两种算法的比较五、小结 一、问题描述 利用 prim 算法和 kruskal 算法实现最小生成树问题&#xff1b; 二、算法思想 首先判断图是否连通&#xff0c;只有在连通的情况下才进行最小树的生成&#xff1b; 三、代…...

为OneAPI配置MySQL数据库及设置开机启动

OneAPI启动时&#xff0c;如果发现没有数据库他会在项目根目录自动创建SqlLit&#xff0c;为提高OneAPI的性能及管理&#xff0c;这里给出一个使用MySQL数据库的案例&#xff0c;同时本文介绍如何在源码部署的情况下&#xff0c;设置OneAPI的开机自动启动。 OneAPI的源代码安装…...

完整的k8s搭建服务器流程

一、准备 1、禁用selinux #临时禁用 setenforce 0 #永久禁用 sed -i s/enforcing/disabled/ /etc/selinux/config #检查selinux是否已禁用 sestatus 2、禁用交换分区 #命令行临时禁用 swapoff -a #永久禁用 vim /etc/fstab 注释掉有swap字样的那行&#xff0c;重启 3、允许…...

【Petri网导论学习笔记】Petri网导论入门学习(一)

Petri 网导论 如需学习转载请注明原作者并附本帖链接&#xff01;&#xff01;&#xff01; 如需学习转载请注明原作者并附本帖链接&#xff01;&#xff01;&#xff01; 如需学习转载请注明原作者并附本帖链接&#xff01;&#xff01;&#xff01; 发现网上关于Petri网的学习…...

Zabbix监控自动化

监控在运维工作中所占的比例为 30%左右&#xff0c;监控做得好&#xff0c;会省很多事&#xff0c;让工作能有序地进行。理想的监控应该是自动化的&#xff0c;只需要配置规则&#xff0c;即可自动完成所有的事情&#xff0c;比如主机的自动添加和注册、模板的自动添加、分组的…...

pytorch pyro 贝叶斯神经网络 bnn beyesean neure network svi ​定制SVI目标和培训循环,变更推理

定制SVI目标和培训循环 Pyro支持各种基于优化的贝叶斯推理方法&#xff0c;包括Trace_ELBO作为SVI(随机变分推理)的基本实现。参见文件&#xff08;documents的简写&#xff09;有关各种SVI实现和SVI教程的更多信息I, 二&#xff0c;以及罗马数字3了解SVI的背景。 在本教程中…...

Openeuler22 部署 RackTables0.22.0

目录 0、前言 一、部署lamp环境&#xff0c;lamp环境测试 1、部署Apache&#xff0c;apache环境测试 2、部署php、mysql&#xff0c;php环境测试 二、放文件 三、配置mysql 四、安装racktables 第一步、点击proceed继续 第二步、点击proceed 第三步、根据提示进行操作…...

从传统到智能:高标准农田灌区信息化助力农业现代化

从传统农业的粗放式管理&#xff0c;到如今智能化、精准化的现代农业转型&#xff0c;高标准农田灌区信息化建设无疑是推动这一历史进程的关键力量。它不仅标志着农业生产方式的根本性变革&#xff0c;还深刻影响着农业资源的高效利用与可持续发展策略&#xff0c;为实现农业现…...

堆排序-建堆,增删替换

我们 之前写过根据 堆排序的优先级队列&#xff0c;但是如果我们想要建立一个堆怎么办呢&#xff1f; 如何实现上浮 下潜 具体看这篇文章 堆排序-优先级队列-CSDN博客 建堆 我们有两种方法建立一个堆 1.我们基于add方法建立一个堆&#xff0c;一次次的add&#xff0c;然后对…...

使用AI写WebSocket知识是一种怎么样的体验?

一、WebSocket基础知识 1. WebSocket概念 1.1 为什么会出现WebSocket 一般的Http请求我们只有主动去请求接口&#xff0c;才能获取到服务器的数据。例如前后端分离的开发场景&#xff0c;自嘲为切图仔的前端大佬找你要一个配置信息的接口&#xff0c;我们后端开发三下两下开…...

若依系统(Security)增加微信小程序登录(自定义登录)

若依系统(分离版后端)自带的账号验证是基于 UsernamePasswordAuthenticationToken authenticationToken new UsernamePasswordAuthenticationToken(username, password); 验证&#xff0c;然后在系统中controller或service类中 SecurityUtils 工具类中直接可获取用户或用户…...

道可云人工智能元宇宙每日资讯|2024互联网岳麓峰会在长沙召开

道可云元宇宙每日简报&#xff08;2024年9月10日&#xff09;讯&#xff0c;今日元宇宙新鲜事有&#xff1a; 2024互联网岳麓峰会在长沙召开 9月9日&#xff0c;2024互联网岳麓峰会在长沙召开&#xff0c;湖南省副省长曹志强在峰会表示&#xff0c;今年上半年湖南省人工智能产…...

MySQL JDBC URL各参数详解

jdbc:mysql://localhost:3306/test?userroot&password123456&useUnicodetrue&characterEncodinggbk &autoReconnecttrue&failOverReadOnlyfalse&serverTimezoneUTC&drivercom.mysql.cj.jdbc.Driver 参数名称参数说明缺省值user指定用于连接数据库…...

celery control.shutdown

Celery 提供了 control 模块&#xff0c;允许你发送控制命令给正在运行的 worker。其中 shutdown 命令可以用来关闭一个或多个 worker。下面是如何使用 control.shutdown 来关闭 worker 的详细说明。 使用 control.shutdown 1. 导入必要的模块 首先&#xff0c;你需要导入 C…...

数据库设计与软件工程阶段的对应关系

数据库设计的很多阶段确实可以与软件工程的各阶段对应起来&#xff0c;这体现了数据库设计作为软件工程中一个核心组成部分的紧密关联性。 1. 需求分析阶段 数据库设计&#xff1a;需求分析是数据库设计的初始阶段&#xff0c;主要任务是收集和分析用户的需求&#xff0c;包括…...

基于ASP+ACCESS的教师信息管理系统

摘要 随着我国社会主义市场经济的发展和改革开放的不断深入&#xff0c;计算机的应用已遍及国民经济的各个领域&#xff0c;计算机来到我们的工作和生活中&#xff0c;改变着我们和周围的一切。在以前&#xff0c;学校用手工处理教师档案以及工资发放等繁多的工作和数据时&…...

【智能体】浅谈大模型之AI Agent

随着ChatGPT推出插件和函数调用功能&#xff0c;构建以LLM&#xff08;大语言模型&#xff09;为核心控制器的AI Agent愈发成为一个拥有无限可能的概念。 AI Agent是一种超越简单文本生成的人工智能系统。它使用大型语言模型&#xff08;LLM&#xff09;作为其核心计算引擎&am…...

大疆 嵌入式 笔记 面试题目汇总大全[嵌入式找工作必看] 比较有难度适合进阶收藏学习

24届大疆车载嵌入式系统安全面试经验 投递岗位&#xff1a;&#xff08;大疆车载&#xff09;嵌入式系统安全 投递时间&#xff1a;大疆大概在7月-8月月初开秋招岗位。8月月中开始笔试&#xff0c;8月月末开始面试。&#xff08;理论上9月&#xff0c;10月开奖。&#xff09;…...

线程池以及详解使用@Async注解异步处理方法

目录 一.什么是线程池&#xff1a; 二.使用线程池的好处&#xff1a; 三.线程池的使用场景&#xff1a; 四.使用线程池来提高Springboot项目的并发处理能力&#xff1a; 1.在application.yml配置文件中配置&#xff1a; 2.定义配置类来接受配置文件内的属性值&#xff1a…...

css鼠标移动过去变成手的图标

在css中定义 cursor:pointer;直接在html中指定 <div class"mt-2 mt-md-2 mt-lg-1 fs-md-1 fs-lg-2 " style"cursor:pointer;"></div>...

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…...

Cesium1.95中高性能加载1500个点

一、基本方式&#xff1a; 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...

UDP(Echoserver)

网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法&#xff1a;netstat [选项] 功能&#xff1a;查看网络状态 常用选项&#xff1a; n 拒绝显示别名&#…...

STM32标准库-DMA直接存储器存取

文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA&#xff08;Direct Memory Access&#xff09;直接存储器存取 DMA可以提供外设…...

跨链模式:多链互操作架构与性能扩展方案

跨链模式&#xff1a;多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈&#xff1a;模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展&#xff08;H2Cross架构&#xff09;&#xff1a; 适配层&#xf…...

SpringCloudGateway 自定义局部过滤器

场景&#xff1a; 将所有请求转化为同一路径请求&#xff08;方便穿网配置&#xff09;在请求头内标识原来路径&#xff0c;然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...

用机器学习破解新能源领域的“弃风”难题

音乐发烧友深有体会&#xff0c;玩音乐的本质就是玩电网。火电声音偏暖&#xff0c;水电偏冷&#xff0c;风电偏空旷。至于太阳能发的电&#xff0c;则略显朦胧和单薄。 不知你是否有感觉&#xff0c;近两年家里的音响声音越来越冷&#xff0c;听起来越来越单薄&#xff1f; —…...

MFC 抛体运动模拟:常见问题解决与界面美化

在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

pycharm 设置环境出错

pycharm 设置环境出错 pycharm 新建项目&#xff0c;设置虚拟环境&#xff0c;出错 pycharm 出错 Cannot open Local Failed to start [powershell.exe, -NoExit, -ExecutionPolicy, Bypass, -File, C:\Program Files\JetBrains\PyCharm 2024.1.3\plugins\terminal\shell-int…...

区块链技术概述

区块链技术是一种去中心化、分布式账本技术&#xff0c;通过密码学、共识机制和智能合约等核心组件&#xff0c;实现数据不可篡改、透明可追溯的系统。 一、核心技术 1. 去中心化 特点&#xff1a;数据存储在网络中的多个节点&#xff08;计算机&#xff09;&#xff0c;而非…...