树,堆,二叉树的认识

news/2024/11/29 2:32:06/

1.树概念及结构

1.1树的概念

 

 注意:树形结构中,子树之间不能有交集,否则就不是树形结构

 1.2 树的相关概念

 1.3 树的表示

树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,既然保存值域,也要保存结点和结点之间的关系,实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。

我们这里就简单的了解其中最常用的孩子兄弟表示法。

 

1.4 树在实际中的运用(表示文件系统的目录树结构)

2.二叉树概念及结构

2.1概念

一棵二叉树(度最大为2)是结点的一个有限集合,该集合:

  • 1. 或者为空
  • 2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成

从上图可以看出:

  • 1. 二叉树不存在度大于2的结点
  • 2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

注意:对于任意的二叉树都是由以下几种情况复合而成的: 

 2.2现实中的二叉树:

 2.3 特殊的二叉树:

1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是 2^{k}-1,则它就是满二叉树。
2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

 

 2.4二叉树的性质

 

2.5 二叉树的存储结构 

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。
1. 顺序存储
顺序结构存储就是使用数组来存储一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,关于堆我们后面的章节会专门讲解。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

  

2. 链式存储
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链,后面课程学到高阶数据结构如红黑树等会用到三叉链。 

typedef int BTDataType;
// 二叉链
struct BinaryTreeNode
{struct BinTreeNode* _pLeft;   // 指向当前节点左孩子 struct BinTreeNode* _pRight; // 指向当前节点右孩子BTDataType _data; // 当前节点值域
}// 三叉链
struct BinaryTreeNode
{struct BinTreeNode* _pParent; // 指向当前节点的双亲 struct BinTreeNode* _pLeft;   // 指向当前节点左孩子 struct BinTreeNode* _pRight; // 指向当前节点右孩子BTDataType _data; // 当前节点值域 
};

3.二叉树的顺序结构及实现

3.1 二叉树的顺序结构 

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

 

3.2 堆的概念及结构 (完全二叉树)

大堆 :树中一个树及子树中,任何一个父亲都大于等于孩子
小堆 :树中一个树及子树中,任何一个父亲都小于等于孩子 

堆的性质:

  •  堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。

 

  

3.3 堆的实现

3.3.1 堆向下调整

现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。

向下调整算法有一个前提:左右子树必须是一个堆,才能调整。

//堆的向下调整(小堆)
void AdjustDown(int* a, int n, int parent)
{int child = parent * 2 + 1;while (child < n){// 选出左右孩子中小的那一个if (child + 1 < n && a[child + 1] < a[child]){++child;}// 如果小的孩子小于父亲,则交换,并继续向下调整if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}

3.3.2 堆向上调整

//堆的向上调整(小堆)
void AdjustUp(int* a, int child)
{assert(a);int parent = (child - 1) / 2;while (child > 0){if (a[child] < a[parent]){Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}

3.3.2堆的创建

下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆。

3.3.3 建堆时间复杂度

3.3.4 堆的插入

先插入一个10到数组的尾上,再进行向上调整算法,直到满足堆。

void HeapPush(HP* hp, HPDataType x)
{assert(hp);HeapCheckCapacity(hp);hp->a[hp->size] = x;hp->size++;AdjustUp(hp->a, hp->size - 1);
}

 3.3.5 堆的删除

删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。

 3.3.6 堆的代码实现

#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>typedef int HPDataType;typedef struct Heap
{HPDataType* a;int size;int capacity;
}HP;// 堆的初始化
void HeapInit(HP* hp);
// 堆的销毁
void HeapDestroy(HP* hp);
// 堆的插入
void HeapPush(HP* hp, HPDataType x);
// 堆的删除
void HeapPop(HP* hp);
// 取堆顶的数据
HPDataType HeapTop(HP* hp);
// 堆的数据个数
int HeapSize(HP* hp);
// 堆的判空
bool HeapEmpty(HP* hp);
// 堆的打印
void HeapPrint(HP* hp);
// 堆的扩容
void HeapCheckCapacity(HP* hp);
#define _CRT_SECURE_NO_WARNINGS 1#include"Heap.h"void Swap(HPDataType* px, HPDataType* py)
{HPDataType tmp = *px;*px = *py;*py = tmp;
}void HeapInit(HP* hp)
{assert(hp);hp->a = NULL;hp->size = hp->capacity = 0;
}void HeapDestroy(HP* hp)
{assert(hp);free(hp->a);hp->capacity = hp->size = 0;
}void HeapCheckCapacity(HP* hp)
{assert(hp);if (hp->size == hp->capacity){size_t newCapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;HPDataType* tmp = realloc(hp->a, sizeof(HPDataType) * newCapacity);if (tmp == NULL){printf("realloc fail\n");exit(-1);}hp->a = tmp;hp->capacity = newCapacity;}
}void HeapPush(HP* hp, HPDataType x)
{assert(hp);HeapCheckCapacity(hp);hp->a[hp->size] = x;hp->size++;AdjustUp(hp->a, hp->size - 1);
}bool HeapEmpty(HP* hp)
{assert(hp);return hp->size == 0;
}int HeapSize(HP* hp)
{assert(hp);return hp->size;
}HPDataType HeapTop(HP* hp)
{assert(hp);assert(!HeapEmpty(hp));return hp->a[0];
}void AdjustDown(int* a, int n, int parent)
{int child = parent * 2 + 1;while (child < n){// 选出左右孩子中小的那一个if (child + 1 < n && a[child + 1] < a[child]){++child;}// 如果小的孩子小于父亲,则交换,并继续向下调整if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}void HeapPop(HP* hp)
{assert(hp);assert(!HeapEmpty(hp));Swap(&hp->a[0], &hp->a[hp->size - 1]);hp->size--;AdjustDown(hp->a, hp->size, 0);
}
void HeapPrint(HP* hp)
{for (int i = 0; i < hp->size; ++i){printf("%d ", hp->a[i]);}printf("\n");
}
#include "Heap.h"int main()
{int a[] = { 70, 56, 30, 25, 15, 10, 75 };HP hp;HeapInit(&hp);for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i){HeapPush(&hp, a[i]);}HeapPrint(&hp);return 0;
}

3.4 堆的应用

3.4.1 堆排序 

堆排序即利用堆的思想来进行排序,总共分为两个步骤:

1. 建堆

  • 升序:建大堆
  • 降序:建小堆

 2. 利用堆删除思想来进行排序

建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。

 

 堆排序代码

Heap.h

#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>typedef int HPDataType;typedef struct Heap
{HPDataType* a;int size;int capacity;
}HP;// 堆的初始化
void HeapInit(HP* hp);
// 堆的销毁
void HeapDestroy(HP* hp);
// 堆的插入
void HeapPush(HP* hp, HPDataType x);
// 堆的删除
void HeapPop(HP* hp);
// 取堆顶的数据
HPDataType HeapTop(HP* hp);
// 堆的数据个数
int HeapSize(HP* hp);
// 堆的判空
bool HeapEmpty(HP* hp);
// 堆的打印
void HeapPrint(HP* hp);
// 堆的扩容
void HeapCheckCapacity(HP* hp);// 堆的向上调整
void AdjustUp(int* a, int child);
// 堆的向下调整
void AdjustDown(int* a, int n, int parent);
// 交换函数
void Swap(HPDataType* px, HPDataType* py);

Heap.c

#define _CRT_SECURE_NO_WARNINGS 1#include"Heap.h"void Swap(HPDataType* px, HPDataType* py)
{HPDataType tmp = *px;*px = *py;*py = tmp;
}void HeapInit(HP* hp)
{assert(hp);hp->a = NULL;hp->size = hp->capacity = 0;
}void HeapDestroy(HP* hp)
{assert(hp);free(hp->a);hp->capacity = hp->size = 0;
}void HeapCheckCapacity(HP* hp)
{assert(hp);if (hp->size == hp->capacity){size_t newCapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;HPDataType* tmp = realloc(hp->a, sizeof(HPDataType) * newCapacity);if (tmp == NULL){printf("realloc fail\n");exit(-1);}hp->a = tmp;hp->capacity = newCapacity;}
}void AdjustUp(int* a, int child)
{assert(a);int parent = (child - 1) / 2;while (child > 0){if (a[child] < a[parent]){Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}void HeapPush(HP* hp, HPDataType x)
{assert(hp);HeapCheckCapacity(hp);hp->a[hp->size] = x;hp->size++;AdjustUp(hp->a, hp->size - 1);
}bool HeapEmpty(HP* hp)
{assert(hp);return hp->size == 0;
}int HeapSize(HP* hp)
{assert(hp);return hp->size;
}HPDataType HeapTop(HP* hp)
{assert(hp);assert(!HeapEmpty(hp));return hp->a[0];
}void AdjustDown(int* a, int n, int parent)
{int child = parent * 2 + 1;while (child < n){// 选出左右孩子中小的那一个if (child + 1 < n && a[child + 1] < a[child]){++child;}// 如果小的孩子小于父亲,则交换,并继续向下调整if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}void HeapPop(HP* hp)
{assert(hp);assert(!HeapEmpty(hp));Swap(&hp->a[0], &hp->a[hp->size - 1]);hp->size--;AdjustDown(hp->a, hp->size, 0);
}
void HeapPrint(HP* hp)
{for (int i = 0; i < hp->size; ++i){printf("%d ", hp->a[i]);}printf("\n");
}

test.c

#include "Heap.h"// 升序  空间复杂度是多少? O(N) 要求优化到O(1) -> 不能用Heap
//void HeapSort(int* a, int n)
//{
//	HP hp;
//	HeapInit(&hp);
//	// 建议一个N个小堆
//	for (int i = 0; i < n; ++i)
//	{
//		HeapPush(&hp, a[i]);
//	}
//
//	// Pop N 次
//	for (int i = 0; i < n; ++i)
//	{
//		a[i] = HeapTop(&hp);
//		HeapPop(&hp);
//	}
//
//	HeapDestroy(&hp);
//}// 升序
void HeapSort(int* a, int n)
{// 把a构建成小堆// 方法1:/*for (int i = 1; i < n; ++i){AdjustUp(a, i);}*/// 方法2:// O(N)for (int i = (n - 1 - 1) / 2; i >= 0; --i){//n-1是最后一个节点,(n-1-1)/2是最后一个节点的父节点AdjustDown(a, n, i);//小堆}
//// 依次选数,调堆// O(N*logN)for (int end = n - 1; end > 0; --end){Swap(&a[end], &a[0]);// 再调堆,选出次小的数AdjustDown(a, end, 0);}
}int main()
{//TestTopk();int a[] = { 70, 56, 30, 25, 15, 10, 75, 33, 50, 69 };for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i){printf("%d ", a[i]);}printf("\n");HeapSort(a, sizeof(a) / sizeof(a[0]));for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i){printf("%d ", a[i]);}printf("\n");return 0;
}

 3.4.2 TOP-K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能 数据都不能一下子全部加载到内存中)。

最佳的方式就是用堆来解决,基本思路如下:
1. 用数据集合中前K个元素来建堆

  • 前k个最大的元素,则建小堆
  • 前k个最小的元素,则建大堆 

2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素

将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

首先思考下建立大堆还是小堆?

建小堆,因为如果建大堆,最大的数可能挡在头的位置,其他9个次大的数就进不来。

思路:

1、用前K个数建立一个K个数的小堆。
2、剩下的N-K个数,依次跟堆顶的数据进行比较,如果比堆顶数据大,就替换堆顶的数据,再向下调整
3、最后堆里面K个数就是最大的K个数

#include "Heap.h"// 在N个数找出最大的前K个  or  在N个数找出最小的前K个
void PrintTopK(int* a, int n, int k)
{
// 1. 建堆--用a中前k个元素建堆
// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换HP hp;HeapInit(&hp);// 创建一个K个数的小堆for (int i = 0; i < k; ++i){HeapPush(&hp, a[i]);}// 剩下的N-K个数跟堆顶的数据比较,比他大,就替换他进堆for (int i = k; i < n; ++i){if (a[i] > HeapTop(&hp)){//HeapPop(&hp);//HeapPush(&hp, a[i]);hp.a[0] = a[i];AdjustDown(hp.a, hp.size, 0);}}HeapPrint(&hp);HeapDestroy(&hp);
}void TestTopk()
{int n = 1000000;int* a = (int*)malloc(sizeof(int)*n);srand(time(0));for (size_t i = 0; i < n; ++i){a[i] = rand() % 1000000;}// 再去设置10个比100w大的数a[5] = 1000000 + 1;a[1231] = 1000000 + 2;a[5355] = 1000000 + 3;a[51] = 1000000 + 4;a[15] = 1000000 + 5;a[2335] = 1000000 + 6;a[9999] = 1000000 + 7;a[76] = 1000000 + 8;a[423] = 1000000 + 9;a[3144] = 1000000 + 10;PrintTopK(a, n, 10);
}void TestHeap()
{int a[] = { 70, 56, 30, 25, 15, 10, 75 };HP hp;HeapInit(&hp);for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i){HeapPush(&hp, a[i]);}HeapPrint(&hp);HeapPop(&hp);HeapPrint(&hp);HeapPop(&hp);HeapPrint(&hp);HeapPop(&hp);HeapPrint(&hp);HeapPop(&hp);HeapPrint(&hp);HeapDestroy(&hp);
}// 升序  空间复杂度是多少? 要求优化到O(1) -> 不能用Heap
//void HeapSort(int* a, int n)
//{
//	HP hp;
//	HeapInit(&hp);
//	// 建议一个N个小堆
//	for (int i = 0; i < n; ++i)
//	{
//		HeapPush(&hp, a[i]);
//	}
//
//	// Pop N 次
//	for (int i = 0; i < n; ++i)
//	{
//		a[i] = HeapTop(&hp);
//		HeapPop(&hp);
//	}
//
//	HeapDestroy(&hp);
//}int main()
{//TestTopk();int a[] = { 70, 56, 30, 25, 15, 10, 75 };for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i){printf("%d ", a[i]);}printf("\n");HeapSort(a, sizeof(a) / sizeof(a[0]));for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i){printf("%d ", a[i]);}printf("\n");return 0;
}

4.二叉树链式结构及其实现

4.1 前置说明

在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。

由于现在大家对二叉树结构掌握还不够深入,为了降低大家学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。

typedef char BTDataType;
typedef struct BinaryTreeNode
{struct BinaryTreeNode* left;struct BinaryTreeNode* right;BTDataType data;
}BTNode;BTNode* CreatBinaryTree()
{BTNode* nodeA = BuyNode('A');BTNode* nodeB = BuyNode('B');BTNode* nodeC = BuyNode('C');BTNode* nodeD = BuyNode('D');BTNode* nodeE = BuyNode('E');BTNode* nodeF = BuyNode('F');nodeA->left = nodeB;nodeA->right = nodeC;nodeB->left = nodeD;nodeC->left = nodeE;nodeC->right = nodeF;return nodeA;
}

 注意:上述代码并不是创建二叉树的方式,真正创建二叉树方式后序详解重点讲解。

再看二叉树基本操作前,再回顾下二叉树的概念,二叉树是:

1. 空树
2. 非空:根节点,根节点的左子树、根节点的右子树组成的。

 从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。 

4.2二叉树的遍历

4.2.1 前序、中序以及后序遍历

学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉 树中的节点进行相应的操作,并且每个节点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。

 

按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:

  • 1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
  • 2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
  • 3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。 

由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。

前序遍历:

中序遍历:

 

后序遍历: 

 

// 二叉树前序遍历
void PreOrder(BTNode* root);
// 二叉树中序遍历
void InOrder(BTNode* root);
// 二叉树后序遍历
void PostOrder(BTNode* root);void PreOrder(BTNode* root)
{if (root == NULL){printf("NULL");return;}printf("%c ", root->data);PreOrder(root->left);PreOrder(root->right);
}void InOrder(BTNode* root)
{if (root == NULL){printf("NULL");return;}InOrder(root->left);printf("%C ", root->data);InOrder(root->right);
}
void PostOrder(BTNode* root)
{if (root == NULL) {printf("NULL ");return;}PostOrder(root->left);PostOrder(root->right);printf("%C ", root->data);
}

 前序遍历递归图解:

 


http://www.ppmy.cn/news/19289.html

相关文章

javascript中Math.random()产生随机数进行随机点名

Math.random()是令系统随机选取大于等于 0.0 且小于 1.0 的小数&#xff0c;即[0.0,1.0&#xff09;。 Math.floor()返回小于参数x的最大整数,即对浮点数向下取整&#xff0c;比如Math.floor(3.8)为3。 一、 在连续整数中取得一个随机整数 随机值 Math.floor(Math.random()…

16 Java网络编程(计算机网络+网络模型OSI/TCP/IP+通信协议等)

网络编程16.1 网络概述16.1.1 概念16.1.2 计算机网络16.1.3 网络模型16.1.3.1 OSI参考模型16.1.3.2 TCP/IP模型16.1.4 网络编程总结 【掌握】16.2 常见协议16.2.1 IP协议概述16.2.2 InetAddress类16.3 端口号16.3.1 端口号概述 【了解】16.4 通信协议16.4.1 通信协议概述 【掌握…

项目整体管理

项目整体管理过程: 一、制定项目章程,正式批准(授权)项目或项目阶段的开始。 项目章程作用: (1)宣布项目成立。(2)任命项目经理并授权。(3)粗略规定范围。 项目章程内容: 概括性的项目描述和项目产品描述 (项目产品描述)项目目的或批准项目的理由,即为什么要…

node文件上传与下载(基于express和multer实现)

node文件上传与下载 所需要的前置知识&#xff1a;基本的html标签&#xff0c;基本DOM, AJAX, express 等 创建基本的 express 项目 使用express-generator生成基本的项目结构 全局安装express-generator npm install -g express-generator 创建express 项目 express upload-…

十大经典排序算法(动态演示+代码)-快速排序与希尔排序

快速排序 1.什么是快速排序 我们知道排序有很多种&#xff0c;常见的如希尔排序&#xff0c;插入排序&#xff0c;选择排序&#xff0c;堆排序等等&#xff0c;而快速排序也是排序家族中的一员。因为其在大多数情况下有着优秀的综合性能&#xff0c;快速排序的快速也算是实至…

Qt之QLCDNumber

文章目录一、QLCDNumber简介二、QLCDNumber属性示例获取系统时间示例代码提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、QLCDNumber简介 QLCDNumber控件用于显示一个LCD数字。 它可以显示几乎任意大小的数字。可以显示十进制、十六进制、八进制或…

从零搭建一个组件库(二)创建代码规范

文章目录前言集成eslint1.安装2.替换默认解析器3.创建.eslintrc.yml配置文件4.创建忽略文件.eslintignore集成 prettier1.安装2.创建配置文件.prettierrc集成# commitizen1.安装2.修改package.json3.测试className的BEM规范1.安装2.BEM概述3.创建hooks函数4.使用hooks函数5.封装…

Cesium:Indexed 3D Scene Layers (I3S)加载

点击此处,查看完整的OGC标准列表项。Indexed 3D Scene Layers(I3S)标准官网介绍地址为:I3S,相关的GitHub主页地址为:Esri/i3s-spec,其详细介绍文档地址可点击此处查阅。我们的核心点在于介绍如何通过Cesium.js开发框架加载I3S三维场景服务。 目录 Cesium.js:I3S支持情…