C语言数据结构专题--顺序表(1基础)

news2024/11/19 22:50:17

前言

我们在对C语言有一定的了解之后,我们就可以开始数据结构的学习了,数据结构多用指针、结构体、动态内存开辟等知识,若对这些知识还不太了解的朋友,就需要加深其理解了,那么废话不多说,我们正式开始本节的学习

什么是数据结构

数据结构是由"数据" 和 "结构" 两个词相组合得到的 

那么什么是数据呢?

例如:常见的数字1、2、3、4、5等,手机通讯录里面保存的联系人信息(号码、名字等)、浏览器网页里面的信息,这些都统称为数据

什么是结构呢?

当我们需要使用大量同一类型的数据时,我们手动的定义大量独立的变量对程序来说可读性很差,我们往往可以借助数组等这些数据结构将大量的数据组织在一起,所以结构也可以理解为组织数据的方式

举个通俗易懂的例子:我们如果想要在一大片草场上寻找一名叫 "多莉" 的羊难度会很大,但是我们要在羊圈中找到 "多莉" 就会很简单,羊圈就好像一个结构,而羊就好似数据

概念:数据结构是计算机存储、组织数据的⽅式。数据结构是指相互之间存在⼀种或多种特定关系 的数据元素的集合。数据结构反映数据的内部构成,即数据由那部分构成,以什么⽅式构成,以及数据元素之间呈现的结构

其中:在数据结构中最基础的数据结构就是数组

顺序表

顺序表是线性表的一种,其中:线性表是具有相同特征的数据结构的集合

线性表的特征:

1.物理结构不一定连续(数据间的地址不一定是连续的)

2.逻辑结构是连续的

顺序表在物理结构和逻辑结构层面上都是连续的

顺序表的底层就是数组,顺序表是在数组的基础上进行维护、封装

可能有人就会产生疑问:既然都有数组了,数组也可以实现数据的增加、删除、查询、修改功能,那么顺序表的存在又有什么意义呢?

当我们想修改、插入或者删除数组中的某个数据时,我们首先需要通过循环来查找数组之中已有元素的个数,再进行数据的修改、插入或者删除,我们每次进行操作的时候都需要进行循环的处理,这样就会很浪费时间和空间,此时顺序表的存在就有意义了

顺序表的底层虽然是数组,但是数组提供了很多现成的办法来处理数据,相比直接使用数组会更加的简洁高效

顺序表分类

1.静态顺序表

struct Seqlist
{
	int arr[100];
	int size;//记录顺序表当前有效数据的个数


};

静态顺序表的底层是一个定长的数组,在代码的编译阶段,我们就确认了数组的大小

2.动态顺序表

//动态顺序表
struct Seqlist
{
	int* arr;
	int size;//有效的数据个数
	int capacity;//记录空间大小
};

那么动态顺序表和静态顺序表二者相比较谁更好呢?

对于静态顺序表而言。若是数组大小给小了,则会导致空间不够用的问题,可能导致数据的丢失;若是数组大小给大了,则会导致空间的浪费

所以动态顺序表更好,因为它更加灵活

我们在实现顺序表时,我们通常把它分成两个文件

头文件:顺序表的结构,声明实现顺序表的方法(头文件实现的是类似于书的目录的功能)

源文件:实现顺序表的方法

同时我们在实现顺序表的时候还需要多创建一个文件,用于检测其功能是否正常

​
//动态顺序表
struct Seqlist
{
	int* arr;
	int size;//有效数据的个数
	int capacity;//空间大小
};

​

我们先在头文件中定义顺序表结构体,考虑到我们存入顺序表中的数据类型不一定为 int 类型,还有可能是 char 类型的数据,所以我们可以定义一下,用定义表示当前顺序表中存储数据的类型,具体操作如下:

typedef int SLDataType;

//动态顺序表
struct Seqlist
{
	SLDataType* arr;
	int size;//有效数据的个数
	int capacity;//空间大小
};

我们可以重命名一下结构体类型来简化代码:

//动态顺序表
typedef struct Seqlist
{
	SLDataType* arr;
	int size;//有效数据的个数
	int capacity;//空间大小
}SL;

下面我们就来实现一下顺序表的各种功能:

顺序表的初始化

在源文件中初始化前,我们需要在头文件中声明一下

void SLInit(SL ps);

此时我们就可以在源文件中初始化

#include"Seqlist.h"
void SLInit(SL s)
{
	s.arr = NULL;
	s.size = s.capacity = 0;

}

由于我们不知道初始化是否成功,我们就可以在测试文件中通过打印各个成员的值,通过在屏幕上面打印的值在观察出初始化是否成功

我们按照我们的常规理解来写出如下代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include"Seqlist.h"


void SLTest01()
{
	SL s1;
	SLInit(s1);

}

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

当我们在运行程序的时候,我们发现它报错了

这个报错非常的奇怪,编译器说我们使用了未初始化的局部变量 s1,而我们现在所做的操作不正是要初始化吗?这样不是自相矛盾吗?

我们仔细分析一下就可以发现问题:

我们传入的 sl 是形式参数,我们采取的是传值操作,由于 sl 本来就是没有初始化的变量,无法进行传值操作,要想解决这个问题,我们需要使用传址操作,现在我们就来改一下代码:

#include"Seqlist.h"
void SLInit(SL* ps)
{
	ps->arr = NULL;
	ps->size = ps->capacity = 0;

}
void SLInit(SL* ps);

此时我们的初始化代码就成功完成了

顺序表的销毁

因为数组的大小是动态开辟的,需要使用到动态内存函数,我们需要释放开辟的空间,那么我们就需要采取如下的操作:

顺序表的头部 插入 和 删除 + 顺序表的 尾部 插入 和 删除
顺序表的尾插

举个例子:若数组里面只有四个数据

0123

                              0                     1                  2                    3             4               5

此时 size = 4  capacity = 6,我们想要在它的尾部插入 x = 5

我们可以发现我们想要插入数据的地方的下标就是 size 的大小,同时再插入数据之前,我们需要先保证有空间允许去插入,如果空间不够,我们还需要去申请空间,那么我们该怎么申请空间呢?

首先,空间的申请需要使用到 malloc calloc realloc 函数,当申请的空间被使用完了我们还需要去增容,那么我们要申请多大的空间呢?一次增容又该增多大呢?

申请空间的规则:增容通常来说是成倍数的增加,一般是两倍增容,若空间是一个一个地去增加,那么插入数据也得一个一个的去插入,这样会非常的麻烦,若要频繁的增容,程序运行的效率就会大大降低;若一次进行大量的增容,就会造成空间的浪费。所以采取两倍两倍的增容是最合理的

4 --------> 8 --------> 16 --------> 32 --------> 64

通过以上几个注意事项,我们可以写出代码:

​
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
	if (ps == NULL)
	{
		return;
	}
	//插入数据前看空间够不够
	if (ps->capacity == ps->size)
	{
		//申请空间
		//判断capacity是否为0
		int newCapaciy = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, ps->newCapacity * sizeof(SLDataType));
		if (tmp == NULL)
		{
			perror("realloc fail!");
			exit(1);
			//直接退出程序,不再继续执行
		}
		//空间申请成功
		ps->arr = tmp;
		ps->capacity = newCapaciy;
	}
	
	ps->arr[ps->size] = x;
	++ps->size;
	//或者写作 ps->arr[ps->size++] = x;
	ps->size++;

}

​

注意:我们在增容的时候需要临时创建一个变量用于存储增容后的指针,若是直接将 其赋给 arr,倘若空间申请失败,原数据内容也会丢失,导致不可逆的错误

我们在 test.c 中测试一下这个代码

#include"Seqlist.h"


void SLTest01()
{
	SL s1;
	SLInit(&s1);
	//测试尾插代码
	SLPushBack(&s1, 1);
	SLPushBack(&s1, 2);
	SLPushBack(&s1, 3);
	SLPushBack(&s1, 4);
	SLPushBack(&s1, 5);

	//.....
	SLDestory(&s1);

}

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

这样我们的尾插操作就完成了

顺序表的头插

举个例子:

0123

                               0                 1                    2                     3              4(size)

在开始进行头插之前。我们同样的需要判断插入数据的空间够不够,不够的话我们需要增容。

头插需要在下标为0的地方插入数据,但是原数组下标为0的地方已经有了数据,这时我们应该怎么插入数据呢?

我们此时需要把原顺序表中的数据统一向后挪动一位

0123

若我们此时需要插入一个数据 x = 6

由于我们在进行头插和尾插之前都需要判断空间是否足够,此时我们就可以把判断空间的代码单独封装成一个函数:

void SLCheckCapacity(SL* ps)
{
	//插入数据前看空间够不够
	if (ps->capacity == ps->size)
	{
		//申请空间
		//判断capacity是否为0
		int newCapaciy = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapaciy * sizeof(SLDataType));
		if (tmp == NULL)
		{
			perror("realloc fail!");
			exit(1);
			//直接退出程序,不再继续执行
		}
		//空间申请成功
		ps->arr = tmp;
		ps->capacity = newCapaciy;
	}
}

在数据挪动的时候,我们可以发现:数组中的最后一个元素,经过挪动后,需要到达下标为 size 处

所以由此我们可以写出头插的代码:

//头插
void SLPushFront(SL* ps, SLDataType x)
{
	if (ps == NULL)
	{
		return;
	}
	SLCheckCapacity(ps);
	//先让顺序表中的数据向后挪动一位
	for (int i = ps->size; i > 0; i--)
	{
		ps->arr[i] = ps->arr[i - 1];//arr[1] = arr[0]
	}
		ps->arr[0] = x;
		ps->size++;
	
}

我们再在 test.c 文件里面测试一下:

#define _CRT_SECURE_NO_WARNINGS 1
#include"Seqlist.h"


void SLTest01()
{
	SL s1;
	SLInit(&s1);
	//测试尾插代码
	SLPushFront(&s1, 1);
	SLPushFront(&s1, 2);
	SLPushFront(&s1, 3);
	SLPushFront(&s1, 4);
	SLPushFront(&s1, 5);

	//.....
	SLDestory(&s1);

}

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

我们运行调试,可以发现头插功能成功实现

由于每次都运行调试比较麻烦,我们来定义一个函数用于打印顺序表中的元素:

//打印
void SLPrint(SL s)
{
	for (int i = 0; i < s.size; i++)
	{
		printf("%d ", s.arr[i]);
	}
	printf("\n");
}
顺序表的尾部删除
0123

我们首先需要判断顺序表是否为空,为空的话就不能执行删除操作

我们可以知道,每次删除完数据以后,数组里面的数据就会减少一个,所以 size--

此时我们就能够很轻松地写出尾部删除的代码:

//尾部删除
void SLPopBack(SL* ps)
{
	if (ps == NULL)
	{
		return;
	}

	if (ps->size == 0)
	{
		return;
	}

	--ps->size;

}
我们来测试一下
void SLTest01()
{
	SL s1;
	SLInit(&s1);
	测试尾插代码
	SLPushBack(&s1, 1);
	SLPushBack(&s1, 2);
	SLPushBack(&s1, 3);
	SLPushBack(&s1, 4);
	SLPrint(s1);
	SLPopBack(&s1);
	SLPrint(s1);


	//.....
	SLDestory(&s1);

}

int main()
{
	SLTest01();
	return 0;
}
顺序表的头部删除

我们在删除完数据以后,需要把顺序表里原有的顺序整体向前挪动一位

根据这个,我们可以写出代码如下:

//头部删除
void SLPopFront(SL* ps)
{
	if (ps == NULL)
	{
		return;
	}

	if (ps->size == 0)
	{
		return;
	}

	//数据整体前挪
	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

我们测试一下:

#define _CRT_SECURE_NO_WARNINGS 1
#include"Seqlist.h"


void SLTest01()
{
	SL s1;
	SLInit(&s1);
	测试尾插代码
	SLPushBack(&s1, 1);
	SLPushBack(&s1, 2);
	SLPushBack(&s1, 3);
	SLPushBack(&s1, 4);
	SLPrint(s1);
	SLPopFront(&s1);
	SLPrint(s1);


	//.....
	SLDestory(&s1);

}

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

代码运行成功

顺序表代码合并

头文件

#pragma once
#include <stdio.h>
#include <stdlib.h>


typedef int SLDataType;

//动态顺序表
typedef struct Seqlist
{
	SLDataType* arr;
	int size;//有效数据的个数
	int capacity;//空间大小
}SL;

//顺序表的初始化
void SLInit(SL* ps);
//顺序表的销毁
void SLDestory(SL* ps);
//顺序表的头部 插入 和 删除 + 顺序表的 尾部 插入 和 删除
void SLPushBack(SL* ps, SLDataType x);
void SLPushFront(SL* ps, SLDataType x);

void SLPopBack(SL* ps);
void SLPopFront(SL* ps);

//顺序表的打印
void SLPrint(SL s);

源文件

#define _CRT_SECURE_NO_WARNINGS 1
//静态顺序表
//struct Seqlist
//{
//	int arr[100];
//	int size;//记录顺序表当前有效数据的个数
//
//
//};

//动态顺序表
//struct Seqlist
//{
//	int* arr;
//	int size;//有效的数据个数
//	int capacity;//记录空间大小
//};

#include"Seqlist.h"
void SLInit(SL* ps)
{
	ps->arr = NULL;
	ps->size = ps->capacity = 0;

}

//顺序表的销毁
void SLDestory(SL* ps)
{
	if (ps->arr)
	{
		free(ps->arr);
	}

	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}


void SLCheckCapacity(SL* ps)
{
	//插入数据前看空间够不够
	if (ps->capacity == ps->size)
	{
		//申请空间
		//判断capacity是否为0
		int newCapaciy = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapaciy * sizeof(SLDataType));
		if (tmp == NULL)
		{
			perror("realloc fail!");
			exit(1);
			//直接退出程序,不再继续执行
		}
		//空间申请成功
		ps->arr = tmp;
		ps->capacity = newCapaciy;
	}
}



//尾插
void SLPushBack(SL* ps, SLDataType x)
{
	if (ps == NULL)
	{
		return;
	}
	SLCheckCapacity(ps);
	ps->arr[ps->size] = x;
	++ps->size;
	//或者写作 ps->arr[ps->size++] = x;

}

//头插
void SLPushFront(SL* ps, SLDataType x)
{
	if (ps == NULL)
	{
		return;
	}
	SLCheckCapacity(ps);
	//先让顺序表中的数据向后挪动一位
	for (int i = ps->size; i > 0; i--)
	{
		ps->arr[i] = ps->arr[i - 1];//arr[1] = arr[0]
	}
		ps->arr[0] = x;
		ps->size++;
	
}

//打印
void SLPrint(SL s)
{
	for (int i = 0; i < s.size; i++)
	{
		printf("%d ", s.arr[i]);
	}
	printf("\n");
}

//尾部删除
void SLPopBack(SL* ps)
{
	if (ps == NULL)
	{
		return;
	}

	if (ps->size == 0)
	{
		return;
	}

	--ps->size;

}
//头部删除
void SLPopFront(SL* ps)
{
	if (ps == NULL)
	{
		return;
	}

	if (ps->size == 0)
	{
		return;
	}

	//数据整体前挪
	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

测试文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"Seqlist.h"


void SLTest01()
{
	SL s1;
	SLInit(&s1);
	测试尾插代码
	SLPushBack(&s1, 1);
	SLPushBack(&s1, 2);
	SLPushBack(&s1, 3);
	SLPushBack(&s1, 4);
	SLPrint(s1);
	SLPopBack(&s1);
	SLPrint(s1);


	//.....
	SLDestory(&s1);

}

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

结尾

顺序表代码的实现整体难度不是很大,但是要求我们在编写代码的时候要考虑仔细,若是有疏忽就很容易出现错误。因为顺序表的内容很多,我就将顺序表封装成了两节,本节我们讲解的是顺序表的基本内容,下一节为大家带来顺序表更高级的内容实现:顺序表删除、插入指定位置的数据。谢谢您的浏览

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

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

相关文章

【C++学习】哈希的应用—位图与布隆过滤器

目录 1.位图1.1位图的概念1.2位图的实现3.位图的应用 2.布隆过滤器2.1 布隆过滤器提出2.2布隆过滤器概念2.3如何选择哈希函数个数和布隆过滤器长度2.4布隆过滤器的实现2.4.1布隆过滤器插入操作2.4.2布隆过滤器查找操作2.4.3 布隆过滤器删除 2.5 布隆过滤器优点2.6布隆过滤器缺陷…

小程序实现微信 【我的】界面

小程序实现仿微信 【我的】界面 一、简介 小程序实现仿微信 【我的】界面 采用 uni-app 实现&#xff0c;可以适用微信小程序、其他各种小程序以及 APP、Web等多个平台 具体实现步骤如下&#xff1a; 下载开发者工具 HbuilderX进入 【Dcloud 插件市场】 搜索 【小程序实现…

HTML基础知识详解(上)(如何想知道html的全部基础知识点,那么只看这一篇就足够了!)

前言&#xff1a;在学习前端基础时&#xff0c;必不可少的就是三大件&#xff08;html、css、javascript &#xff09;&#xff0c;而HTML&#xff08;超文本标记语言——HyperText Markup Language&#xff09;是构成 Web 世界的一砖一瓦&#xff0c;它定义了网页内容的含义和…

SwiftUI 中无法对添加模糊(blur)效果视图截图的初步解决

概览 在 万物皆可截图:SwiftUI 中任意视图(包括List和ScrollView)截图的通用实现 这篇博文里,我们讨论了在 SwiftUI 中对任意视图截图的一般方法。 不过,经码友反应这些方法对添加模糊(blur)效果的视图好像不太灵了。这里,就让我们看看一些可能的应变(Workaround)之…

Vue3:组件间通信-各种通信方式的用法总结

Vue3组件通信和Vue2的区别&#xff1a; 移出事件总线&#xff0c;使用mitt代替。vuex换成了pinia。把.sync优化到了v-model里面了。把$listeners所有的东西&#xff0c;合并到$attrs中了。$children被砍掉了。

用顺序表实现通讯录

前言 这次的通讯录是基于上一篇的动态顺序表的基础上实现的&#xff0c;如果对动态顺序表不熟悉&#xff0c;可以打开这个链接阅读http://t.csdnimg.cn/9zJ5g&#xff0c;这里我们会调用动态顺序表的函数。 如果想看静态顺序表实现通讯录&#xff0c;可以打开这个链接阅读http:…

【Canvas与艺术】绘制蓝色波纹铜质Best Product Guaranteed徽章

【关键点】 使用贝塞尔二次曲线生成环状波纹轮廓。 【成果图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>蓝色波纹铜质…

离线数仓(十)【ADS 层开发】

前言 剩下的 ADS 层主要就是写 SQL 了&#xff0c;就像我们之前练习的 HQL 题一样&#xff0c;不同的是这里的数据从哪张表读取&#xff08;DWD 还是 ADS 甚至个别表需要从 DIM 层读取&#xff09;需要我们自己来分析。 ADS 的建表语句和 MySQL 是对应的&#xff0c;我们到时候…

创意绘图小程序:绘画与实用功能的完美融合

创意绘图小程序&#xff1a;绘画与实用功能的完美融合 在数字化时代&#xff0c;创意绘图小程序以其便捷性、互动性和创新性&#xff0c;成为了人们表达自我、释放创意的新平台。本文将介绍一款集白板画、黑板画功能于一身&#xff0c;同时融合画笔调整、画布清空、橡皮擦清除…

专有钉钉微应用埋点以及本地调试埋点总结

最近在对接浙政钉&#xff0c;稳定性监控、通用采集 SDK、基础埋点、基础埋点&#xff0c;每次发布上去&#xff0c;工作人员那边反馈抓取不到信息 稳定性监控代码、通用采集 SDK index.html <!-- 流量稳定监控 S 关于埋点上线打开--><script src"https://wpk-…

在project模式下使用Implementation Runs窗口

要在“Implementation Runs”窗口中启动active implementation run&#xff0c;请执行以下任一操作&#xff1a; • 在Flow Navigator中选择“Run Implementation”。 • 在主菜单中选择“Flow > Run Implementation”。 • 从工具栏菜单中选择“Run Implementation”。 • …

达梦DMHS-Manager工具日常操作

目录 1、前言 2、同步服务管理 2.1、DMHS Agent节点管理 2.2、DMHS实例节点管理 2.3、DMHS模块节点管理 3、监控及告警 3.1、主机资源监控 3.2、同步链路监控 3.3、告警配置 4、系统管理 4.1、用户管理 4.2、角色管理 4.3、系统配置 4.4、审计信息 5、联机帮助 …

《手把手教你》系列技巧篇(七十一)-java+ selenium自动化测试-自定义类解决元素同步问题(详解教程)

1.简介 前面宏哥介绍了几种关于时间等待的方法&#xff0c;也提到了&#xff0c;在实际自动化测试脚本开发过程&#xff0c;百分之90的报错是和元素因为时间不同步而发生报错。本文介绍如何新建一个自定义的类库来解决这个元素同步问题。这样&#xff0c;我们在写脚本的时候&a…

spark高手必备

Spark 官网 https://spark.apache.org/ spark官方问题交流 Stack Overflow Newest apache-spark Questions - Stack Overflow 其它参考文档 Distributed Systems Architecture | brought to you by Alexey Grishchenko Shuffle原理 Spark Architecture: Shuffle | Distri…

C语言动态内存空间分配

1. 前言 在讲内存分配前&#xff0c;咱来聊一下为什么会有内存分配这个概念呢&#xff0c;大家都知道C语言当中是有着许多的数据类型&#xff0c;使用这些数据类型就会在内存上开辟其相对应的空间&#xff0c;那既然会开辟相应的空间&#xff0c;为什么还会有内存分配呢&#x…

函数式编程(一)

函数式编程总体介绍 函数式编程(functional programming)其实是个很古老的概念&#xff0c;诞生距今快60年啦&#xff01; 最古老的函数式编程语言Lisp 新出现的函数式编程语言&#xff1a;比如Erlang、Scala、clojure等 热门语言&#xff1a;Python、java、JavaScript、C等…

Scala第十九章节(Actor的相关概述、Actor发送和接收消息以及WordCount案例)

Scala第十九章节 章节目标 了解Actor的相关概述掌握Actor发送和接收消息掌握WordCount案例 1. Actor介绍 Scala中的Actor并发编程模型可以用来开发比Java线程效率更高的并发程序。我们学习Scala Actor的目的主要是为后续学习Akka做准备。 1.1 Java并发编程的问题 在Java并…

【Python】无法将“pip”项识别为 cmdlet、函数、脚本文件或可运行程序的名称解决方案

【Python】无法将“pip”项识别为 cmdlet、函数、脚本文件或可运行程序的名称解决方案 大家好 我是寸铁&#x1f44a; 总结了一篇【Python】无法将“pip”项识别为 cmdlet、函数、脚本文件或可运行程序的名称解决方案✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 今天寸铁…

java(7)之跳转语句

1、break跳转语句 说到break其实也不是跳转&#xff0c;它更像是一个终结语句&#xff0c;常用于在循环语句需要停止出现例如 while&#xff08;&#xff09;{ if&#xff08;&#xff09;{ break&#xff1b; }} 这样的形式或者 switch&#xff08;&#xff09;{ case…

LEAP模型的能源环境发展、碳排放建模预测及不确定性分析教程

原文链接&#xff1a;LEAP模型的能源环境发展、碳排放建模预测及不确定性分析教程https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247599754&idx4&sn243c9f8bff355235a7056c2cbb1331fa&chksmfa82076dcdf58e7b871c3369c95ead9ff1d90baa0431318b26b6abd27…