有且仅有的10个常见的排序算法,东西不多,怎么就背不下来呢
就这么跟你说吧,面试中肯定会出排序算法的算法题,你只需要背下来代码+背下来他们的时间复杂度和空间复杂度就能蒙混过关。
不管你是前端还是后端,关于排序的算法有且仅有这 10个,如果你用心了,怎么会记不住呢。看完这篇文章你还没记住,就说明你是笨蛋。
好吧,最最坏的情况下,也需要记住这个8【冒泡、选择、插入、希尔、归并、快速、桶、堆】
可以把所有排序分成三类
第一类【冒泡 + 选择 + 插入 + 希尔】重点是使用【循环+交换】
第二类【归并 + 堆排序 + 快速】重点是【辅助函数 + 递归】
第三类【快速排序】他自己一组,说明很重要,使用【递归思想】
第三类【计数 + 桶 + 基数】重点是使用【额外空间桶】
一、循环+交换
1.1 冒泡排序
这个是你必须会的,我记得之前最先学的就是这个算法,因为他最简单,但是因为他的时间复杂度大,所以考的概率确实最小的,但是你怎么可以不会这么简单的排序呢?
冒泡排序分为三个,一个是基本的冒泡排序,二个改进的冒泡排序,我建议是至少记住一个基本的一个改进的。冒泡排序的时间复杂度都是 o(n^2)
改进的冒泡排序你一定要学会,因为如果你在面试中答了简单的冒泡排序,面试官一定会问你,有没有什么地方可以优化?如果你一步到位写出改进的冒泡排序说不定人家就放过你了。
1.1.1 基本的冒泡排序
记住一个关键字【双层 for 循环】
- 外层 for 循环 i < len
- 内层 for 循环 j 初始化为 0,j < len - i - 1
- 使用内层 for 循环交换元素
- 时间复杂度是 n^2, 因为有两层 for 循环
- 注意是升序排序,还是降序排序
function bubbleSort(arr) {let len = arr.lengthfor (let i = 0; i < len; i++) {for (let j = 0; j < len - i - 1; j++) {if (arr[j] > arr[j + 1]) {let temp = arr[j + 1]arr[j + 1] = arr[j]arr[j] = temp}}}return arr
}
var arr = [1, 10, 8, 2, 30];
console.log(bubbleSort(arr));
1.1.2 改进的冒泡排序
记住关键子【while 循环 + for 循环 + pos 位置信息】
- 设置一个位置信息 pos 记录每趟排序中最后一次进行交换的位置
- pos 初始化为0 ,在 while 循环内初始化,每次循环都重置
- pos 位置之后的记录均已经交换到位
- 在交换的时候 if 语句内更新 pos
- 内层 for 循环 j < i
- 在下一趟排序时,只需要扫描到 pos 位置即可【说明 posm是用来改变循环终止位置的】
- 注意 while 循环一定要有更改 while 循环条件的语句,这里就使用 pos
- 时间复杂度还是为O(N^2)
function bubbleSort(arr) {let i = arr.length - 1while (i > 0) {let pos = 0for (let j = 0; j < i; j++) {if (arr[j] > arr[j + 1]) {// 记录位置pos = jlet temp = arr[j + 1]arr[j + 1] = arr[j]arr[j] = temp}}// 更改 while 循环条件i = pos}return arr
}
var arr = [1, 10, 8, 2, 30];
console.log(bubbleSort(arr));
1.1.3 终极版的冒泡排序
关键值【while 循环和2个for循环 +下标 最大值和最小值】
- 这个是在 1.2 的基础上再进行优化的
- 关键是使用最大值和最小值的概念【这里是指的下标的最大最小值】
- 正向冒泡,找到最大值
- 反向冒泡, 找到最小值
- 2个 for 循环,不是双层 for 循环
- while 循环条件是 low < high 所以内部有high-- low ++的语句
- 要定义四个变量
- 排序趟数几乎减少了一半
function bubbleSort(arr) {let low = 0;let high = arr.length - 1let temp, jwhile (low < high) {// 注意这个是 j + 1 和 j 的交换for (j = low; j < high; j++) {if (arr[j] > arr[j + 1]) {temp = arr[j + 1]arr[j + 1] = arr[j]arr[j] = temp}}high--// 这个 for 循环是 j - 1 和 j的交换for (j = high; j > low; j--) {if (arr[j - 1] > arr[j]) {temp = arr[j - 1]arr[j - 1] = arr[j]arr[j] = temp}}low++}return arr
}
var arr = [1, 10, 8, 2, 30];
console.log(bubbleSort(arr));
1.2 选择排序
重点【双层 for 循环】
- 找到数据结构中的最小值,并把放在第一位。
- 选择排序的时间复杂度也是O(n^2)
- 也是使用 双层的for循环
- 外层 for 循环交换当前值和 最小值,外层循环小于 len - 1,因为内层循环初始化为i+1
- 内层 for 循环用来寻找最小值【j = i + 1】
- 使用 minIndex 存最小值的下标,初始化为 i
function selectSort(arr) {let len = arr.lengthlet temp, minIndexfor (let i = 0; i < len - 1; i++) {minIndex = ifor (let j = i + 1; j < len; j++) {if (arr[j] < arr[minIndex]) {minIndex = j}}temp = arr[i]arr[i] = arr[minIndex]arr[minIndex] = temp}return arr}
var arr = [1, 10, 8, 2, 30];
console.log(selectSort(arr));
1.3 插入排序
这个插入排序也一定要会
重点【外层 for + 内层 while 】
- 时间复杂度依旧为 O(n^2)
- 外层使用 for 循环,初始化 i = 1【这就意味着使用j-1】
- 内层使用 while 循环 j = i
- 关键是 while 循环的条件
- 比较放在 while 循环的条件中 arr[j-1] > arr[i]
- 使用的是 j - 1, j--
- 交换发生在 for 循环中
- 适用于排小数组
function insertSort(arr) {let len = arr.lengthlet tempfor (let i = 1; i < len; i++) {temp = arr[i]let j = iwhile (j > 0 && arr[j - 1] > temp) {arr[j] = arr[j - 1]j--}arr[j] = temp}return arr
}
var arr = [1, 10, 8, 2, 30];
console.log(insertSort(arr));
1.4 希尔排序
希尔排序可以说是特殊的插入排序
重点是有一个【间隔 + while 循环 + for 循环 + while 循环】
- gap 初始化为 len /2,更新 gap = gap /2
- while 循环条件是 gap > 0
- 在插入排序的基础上外面加了一个 gap 的while循环
- gap 是for 循环的初始值【普通插入排序是1】
- 插入排序的 j-1 变成 j -gap
- 大幅减少数据移动的次数
function shellSort(arr) {let len = arr.lengthlet gap = Math.floor(len / 2)while (gap > 0) {for (let i = gap; i < len; i++) {const temp = arr[i]let j = iwhile (j >= gap && arr[j - gap] > temp) {arr[j] = arr[j - gap]j -= gap}arr[j] = temp}gap = Math.floor(gap / 2)}return arr
}
var arr = [1, 10, 8, 2, 30];
console.log('希尔排序', shellSort(arr));
二、辅助函数+递归
2.1 归并排序
重点【两个函数 + 递归】
- 时间复杂度是O(nlogn)
- 将整个数组分为两个数组
- 对两个数组分别使用递归,调用两次
- 写一个辅助函数
- 第二个函数类似合并两个有序数组【使用双指针】
- 最后调用辅助函数
function merge(left, right) {let res = []let i = 0, j = 0while (i < left.length && j < right.length) {if (left[i] < right[j]) {res.push(left[i])i++}else {res.push(right[j])j++}}if (i < left.length) {res = res.concat(left.slice(i))}if (j < right.length) {res = res.concat(right.slice(j))}return res
}
function mergeSort(arr) {let len = arr.lengthif (len < 2) {return arr}let middle = Math.floor(len / 2)let left = arr.slice(0, middle)let right = arr.slice(middle)return merge(mergeSort(left), mergeSort(right))
}
var arr = [1, 10, 8, 2, 30];
console.log(mergeSort(arr));
2.2 堆排序
重点是【3个函数+递归】
- 总共需要3个函数,其中两个辅助函数,一个构建最大堆,一个调整堆
- 每个函数都需要调用调整堆的函数 heapify
- heapify 递归调用自身
- 两个 for 循环都是递减的
- 堆的性质:在一个最大堆(或最小堆)中,堆顶元素总是数组中最大(或最小)的元素
- 时间复杂度是O(n log n)
function heapSort(arr) {// 构建最大堆buildMaxHeap(arr);// 从最后一个非叶子节点开始向上调整堆for (let i = arr.length - 1; i > 0; i--) {// 将堆顶元素(最大值)与当前未排序部分的最后一个元素交换[arr[0], arr[i]] = [arr[i], arr[0]];// 调整堆,使其满足最大堆性质heapify(arr, 0, i);}return arr;
}// 构建最大堆
function buildMaxHeap(arr) {const len = arr.length;// 从最后一个非叶子节点开始向上调整堆for (let i = Math.floor(len / 2) - 1; i >= 0; i--) {heapify(arr, i, len);}
}// 调整堆
function heapify(arr, i, len) {let largest = i; // 当前节点const left = 2 * i + 1; // 左子节点const right = 2 * i + 2; // 右子节点// 如果左子节点比当前节点大,则更新最大值索引if (left < len && arr[left] > arr[largest]) {largest = left;}// 如果右子节点比当前节点大,则更新最大值索引if (right < len && arr[right] > arr[largest]) {largest = right;}// 如果最大值索引不是当前节点,则交换节点,并递归调整堆if (largest !== i) {[arr[i], arr[largest]] = [arr[largest], arr[i]];heapify(arr, largest, len);}
}var arr = [1, 10, 8, 2, 30];
console.log('堆-排序', heapSort(arr));
三、快速排序
快速排序是一定一定会考的,频率最高的算法,就算被也要背下来,你看他的名字里面有快速两个字,就说明他的重要性。
通过一趟排序将待排序记录分割成独立的两部分,其中一步跟记录的关键字均比另一部分的关键字小,则可分别将两部分记录继续进行排序,达到整个序列有序。
关键字【分治法 + 递归】
- 时间复杂度为 O(nlogn)
- 有一个基准,每次递归只初始化一次基准,不会手动修改
- 有一个小于基准的数组
- 一个大于基准的数组
- 有一个 for 循环,有一个 continue 语句
- 在函数返回的地方,使用递归
function quickSort(arr) {if (arr.length <=1) {return arr}let baseIndex = Math.floor(arr.length / 2)let base = arr[baseIndex]let less = []let greater = []for(let i =0; i < arr.length; i ++) {if (i === baseIndex) {continue}if(arr[i] < base) {less.push(arr[i])} else {greater.push(arr[i])}}return [...quickSort(less), base, ...quickSort(greater)]
}
var arr = [1, 10, 8, 2, 30];
console.log(quickSort(arr));
四、额外桶空间
4.1 计数排序
分布式排序,使用一个用来存储每个元素在原始数组中出现次数的临时数组,在所有元素都计数完成后,临时数组已排好序,并可以带以构建排序后的结果数组
重点【找到最大值和最小值+一个计数数组】
- 时间复杂度是O(o+k),k是临时计数数组的大小
- 找出数组中的最大值最小值
- 计算每个元素的出现次数 arr[i] -min
- 计数数组,重建排序后的数组
- 适用于整数数组的排序
function countSort(arr) {// 找出最大值和最小值let min = max = arr[0]for (let i = 1; i < arr.length; i++) {if (arr[i] < min) {min = arr[i]}if (arr[i] > max) {max = arr[i]}}// 由于是计数排序适用于整数数组,所以这样初始化数组const countArr = new Array(max - min + 1).fill(0)for (let i = 0; i < arr.length; i++) {countArr[arr[i] - min]++ // 用数组下标对应一个元素}// 结果数组的 indexlet sortedIndex = 0for (let i = 0; i < countArr.length; i++) {while (countArr[i] > 0) {// 因为计数数组中减去了 min// 所以这里面要使用循环的下标 i 加上 最小值 minarr[sortedIndex] = i + minsortedIndex++countArr[i]--}}return arr
}
var arr = [1, 10, 8, 2, 30];
console.log(countSort(arr));
4.2 桶排序
也是分布式排序,重点是【分桶+插入排序】
- 和计数排序一样,先找到最大值和最小值
- 最大最小值,用于计算桶的个数
- 最小值,还用于把元素加入桶
- 有两个函数
- 第一个个函数将元素分成不同的桶
- 有两个参数,第二个参数是桶的大小
- 将元素插入桶,重点是计算bucketIndex = (arr[i]-min )/ bucketSize
- 第二个函数对桶进行排序,使用插入排序,因为插入排序用来排小数组不错
- 时间复杂度 O(n+k)
function bucketSort(arr, bucketSize = 5) {if (arr.length ===0) {return arr}let min = arr[0]let max = arr[0]for(let i = 1; i < arr.length; i++) {if(arr[i] < min) {min = arr[i]}if (arr[i] > max) {max = arr[i]}}const bucketCount = Math.floor((max- min)/bucketSize ) + 1const bucketArr = Array.from({length: bucketCount}, () => [])for(let i =0; i < arr.length; i++) {let bucketIndex = Math.floor((arr[i] - min) / bucketSize)bucketArr[bucketIndex].push(arr[i])}let sortedArr = []for(let i = 0; i < bucketArr.length; i ++) {insertSort(bucketArr[i])sortedArr.push(...bucketArr[i])}return sortedArr
}
var arr = [1, 10, 8, 2, 30];
console.log(bucketSort(arr));
4.3 基数排序
重点是【获取最大数的位数+桶的个数时基数的大小】
- 需要获取数组中的最大值
- 桶的个数时基数的大小,通常是10进制就是10
- 使用一个辅助函数获取指定位置上的数字
- 时间复杂度是 O(n+k)
function radixSort(arr) {let maxNum = Math.max(...arr)let maxLen = `${maxNum}`.length// 通常创建桶的数量是基数的大小// Array.from 的第二个参数时map函数依次迭代let buckets = Array.from({ length: 10 }, () => [])// 获取数字num的指定位数上的数字const getDigit = (num, digit) => {// Math.pow返回基数得幂return Math.floor(Math.abs(num) / Math.pow(10, digit)) % 10}for (let i = 0; i < maxLen; i++) {for (let j = 0; j < arr.length; j++) {let digit = getDigit(arr[j], i)buckets[digit].push(arr[j])}arr = [].concat(...buckets)for (let j = 0; j < 10; j++) {buckets[j] = []}}return arr
}
var arr = [1, 10, 8, 2, 30];
console.log(radixSort(arr));
相关文章:
有且仅有的10个常见的排序算法,东西不多,怎么就背不下来呢
就这么跟你说吧,面试中肯定会出排序算法的算法题,你只需要背下来代码背下来他们的时间复杂度和空间复杂度就能蒙混过关。 不管你是前端还是后端,关于排序的算法有且仅有这 10个,如果你用心了,怎么会记不住呢。看完这篇…...

Mac安装配置ElasticSearch和Kibana 8.13.2
系统环境:Mac M1 (MacOS Sonoma 14.3.1) 一、准备 从Elasticsearch:官方分布式搜索和分析引擎 | Elastic上下载ElasticSearch和Kibana 笔者下载的是 elasticsearch-8.13.2-darwin-aarch64.tar.gz kibana-8.13.2-darwin-aarch64.tar.gz 并放置到个人…...

javaWeb项目-快捷酒店管理系统功能介绍
项目关键技术 开发工具:IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架:ssm、Springboot 前端:Vue、ElementUI 关键技术:springboot、SSM、vue、MYSQL、MAVEN 数据库工具:Navicat、SQLyog 1、Spring Boot框架 …...

闲不住,手写一个数据库文档生成工具
shigen坚持更新文章的博客写手,擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长,分享认知,留住感动。 个人IP:shigen 逛博客的时候,发现了一个很有意思的文章:数据库表结构导…...

在群晖上安装GPT4Free
什么是 GPT4Free ? GPT4Free 简称 G4F,是一个强大的大型语言模型命令行界面(LLM-CLI),旨在去中心化并提供免费访问先进人工智能技术的能力。G4F 的目标是通过提供用户友好和高效的工具,使人工智能民主化&am…...
C# 语言类型(四)—传递参数及其修饰符
总目录 C# 语法总目录 参考链接: C#语法系列:C# 语言类型(一)—预定义类型值之数值类型 C#语法系列:C# 语言类型(二)—预定义类型之字符串及字符类型简述 C#语法系列:C# 语言类型(三)—数组/枚举类型/结构体 C#语法系列:C# 语言类型(四)—传递参数及其修饰符 C#语法…...

刷穿力扣006-剑指offer一数组——02寻找目标值-二维数组
刷穿力扣006-剑指offer<一>数组——02寻找目标值-二维数组 基本面试题都是我带大家刷的力扣热题100和剑指offer的75道题,建议刷两遍!(ps:想找工作实习的同学,文末有面试八股和简历模板) 题目: 语言…...
爬虫(小案例)
点开其中一个链接, http://desk.zol.com.cn/dongman/huoyingrenzhe/(前面为浏览器自动补全,在代码里需要自己补全) 可以看到图片的下载地址以及打开本图集下一张图片的链接 了解完网站的图片构造后动手写代码,我们筛…...

环信 IM 客户端将适配鸿蒙 HarmonyOS
自华为推出了自主研发操作系统鸿蒙 HarmonyOS 后,国内许多应用软件开始陆续全面兼容和接入鸿蒙操作系统。环信 IM 客户端计划将全面适配统鸿蒙 HarmonyOS ,助力开发者快速实现社交娱乐、语聊房、在线教育、智能硬件、社交电商、在线金融、线上医疗等广泛…...
伪元素的使用
.box::after{content: ;display: block;// 定义元素位置margin-top: 12rpx;margin-right: 20rpx;// 定义元素宽高width: 36rpx;height: 36rpx;// background-image无法引用本地资源,故需要用网络地址background-image: url($urlcalendar.png);background-size: 100%…...
TensorFlow学习之:高级应用和扩展
生成对抗网络:了解GAN的基本原理,使用TensorFlow实现简单的GAN 生成对抗网络(Generative Adversarial Networks,GAN)由两部分组成:生成器(Generator)和判别器(Discrimin…...
maya模板导入动画
maya模板导入动画,第一帧为模板姿态 要将一个FBX文件中的动画数据导入另一个FBX文件的模板,并使得第一帧是模板的初始姿势,第二帧开始是动画,你可以在Maya中采用以下步骤来操作: 步骤 1: 导入模板FBX 首先ÿ…...

【微信小程序之分包】
微信小程序之分包 什么是分包分包的好处分包前的结构图分包后的结构图分包的加载规则分包的体积限制使用分包打包原则引用原则独立分包独立分包的配置方法独立分包的引用原则分包预下载配置分包的预下载分包预下载限制 什么是分包 分包指的是把一个完整小程序项目,…...

STM32-ADC(独立模式、双重模式)
ADC简介 18个通道:外部信号源就是16个GPIO回。在引脚上直接接模拟信号就行了,不需要侄何额外的电路。引脚就直接能测电压。2个内部信号源是内部温度传感器和内部参考电压。 逐次逼近型ADC: 它是一个独立的8位逐次逼近型ADC芯片,这个ADC0809是…...

03.卸载MySQL
卸载MySQL 1.Windows卸载MySQL8 停止服务 用命令停止或者在服务中停止都可以 net stop mysql(服务名字可以去服务里面看一下)控制面板卸载MySQL 卸载MySQL8.0的程序可以和其他桌面应用程序一样直接在控制面板选择卸载程序,并在程序列表中…...
2024.4.13 蓝桥杯软件类C++B组山东省赛 小记
大三老狗了 , 还是把精力放在考研上了 ,所以只是蓝桥杯的前一晚上把常用算法翻了翻。 其实还做了一场小模拟,两个题分值200分我狂砍了17分,bfs写半小时写不明白,所以晚上已经是心如死灰了,所以就早早睡觉了…...

Windows下IntelliJ IDEA远程连接服务器中Hadoop运行WordCount(详细版)
使用IDEA直接运行Hadoop项目,有两种方式,分别是本地式:本地安装HadoopIDEA;远程式:远程部署Hadoop,本地安装IDEA并连接, 本文介绍第二种。 一、安装配置Hadoop (1)虚拟机伪分布式 见上才艺&a…...

【每日刷题】Day16
【每日刷题】Day16 🥕个人主页:开敲🍉 🔥所属专栏:每日刷题🍍 🌼文章目录🌼 1. 24. 两两交换链表中的节点 - 力扣(LeetCode) 2. 160. 相交链表 - 力扣&…...

【K8s】:在 Kubernetes 集群中部署 MySQL8.0 高可用集群(1主2从)
【K8s】:在 Kubernetes 集群中部署 MySQL8.0 高可用集群(1主2从) 一、准备工作二、搭建nfs服务器2.1 安装 NFS 服务器软件包(所有节点执行)2.2 设置共享目录2.3 启动 NFS 服务器2.4 设置防火墙规则(可选&am…...
Vue内置组件TransitionGroup详细介绍
<TransitionGroup> 是一个内置组件,用于对 v-for 列表中的元素或组件的插入、移除和顺序改变添加动画效果。 和 <Transition> 的区别 <TransitionGroup> 支持和 <Transition> 基本相同的 props、CSS 过渡 class 和 JavaScript 钩子监听器…...

【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...

Ascend NPU上适配Step-Audio模型
1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统,支持多语言对话(如 中文,英文,日语),语音情感(如 开心,悲伤)&#x…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

算法岗面试经验分享-大模型篇
文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer (1)资源 论文&a…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...

免费数学几何作图web平台
光锐软件免费数学工具,maths,数学制图,数学作图,几何作图,几何,AR开发,AR教育,增强现实,软件公司,XR,MR,VR,虚拟仿真,虚拟现实,混合现实,教育科技产品,职业模拟培训,高保真VR场景,结构互动课件,元宇宙http://xaglare.c…...