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

JavaScript中的常见算法

一.排序算法

1.冒泡排序


        冒泡排序比较所有相邻的两个项,如果第一个比第二个大,则交换它们。元素项向上移动至 正确的顺序,就好像气泡升至表面一样。

function bubbleSort(arr) {const { length } = arrfor (let i = 0; i < length - 1; i++) {for (let j = 0; j < length - i - 1; j++) {if (arr[j] > arr[j + 1]) {[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]}}}return arr
}arr = [3, 2, 5, 4, 7, 1]
console.log(bubbleSort(arr));  //[1, 2, 3, 4, 5, 7]

2.选择排序


         选择排序算法是一种原址比较排序算法。选择排序大致的思路是找到数据结构中的最小值并 将其放置在第一位,接着找到第二小的值并将其放在第二位,以此类推。

function selectSort(arr) {const { length } = arrlet minfor (let i = 0; i < length - 1; i++) {min = ifor (let j = i; j < length; j++) {if (arr[j] < arr[min]) {min = j}}if (min !== i) {[arr[i], arr[min]] = [arr[min], arr[i]]}}return arr
}

3.插入排序 


如图所示,只可意会不可言传 

function insertSort(arr) {const { length } = arrlet tempfor (let i = 1; i < length; i++) {temp = arr[i]let j = iwhile (j > 0 && arr[j - 1] > temp) {arr[j] = arr[j - 1]j--}arr[j] = temp}return arr
}

4.归并排序 


        归并排序是一种分而治之算法。其思想是将原始数组切分成较小的数组,直到每个小数组只 有一个位置,接着将小数组归并成较大的数组,直到最后只有一个排序完毕的大数组。

 图片详解:

function mergeSort(array) {if (array.length > 1) {const {length} = array;const middle = Math.floor(length / 2);const left = mergeSort(array.slice(0, middle));const right = mergeSort(array.slice(middle, length));array = merge(left, right);}return array;
}function merge(left, right) {let i = 0;let j = 0;const result = [];while (i < left.length && j < right.length) {result.push(left[i] < right[j] ? left[i++] : right[j++]);console.log(result)//先push ,再++}return result.concat(i < left.length ? left.slice(i) : right.slice(j));
}

例如,如果 left = [3, 4]right = [1, 2],那么 merge 函数会按以下步骤操作:

  • 比较 left[0]right[0](3 和 1),因为 1 < 3,所以将 1 添加到 result,打印 [1]
  • 然后比较 left[0]right[1](3 和 2),因为 2 < 3,所以将 2 添加到 result,打印 [1, 2]
  • 由于 right 已经被完全遍历,将 left 剩余的元素(3 和 4)添加到 result,最终 result[1, 2, 3, 4]

 5.快速排序


         确立基准元素,根据其它元素与基准元素的大小比较,大的分为一组,小的分为一组,再在连接字符串的时候递归调用相应的方法,直至碰到递归调用的结束条件。

function quickSort(arr) {const { length } = arr//结束条件if (length < 2) {return arr}let base = arr[0]let minArr = arr.slice(1).filter(item => item <= base)let maxArr = arr.slice(1).filter(item => item >= base)return quickSort(minArr).concat(base).concat(quickSort(maxArr))
}

6.计数排序


        计数排序使用一个用来存储每个元素在原始数组中出现次数的临时数组。在所有元素都计数完成后,临时数组已排好序并可迭代以构建排序 后的结果数组。  

//缺点是浪费数组空间,最好是在要排序的数字比较连续紧凑的时候使用
function countSort(arr) {if (arr.length < 2) {return arr}const maxValue = Math.max(...arr)const counts = new Array(maxValue + 1)//让数组的值作为新数组的索引值,再进行计数arr.forEach(item => {if (!counts[item]) {counts[item] = 0}counts[item]++});let newArr = []let sortIndex = 0counts.forEach((item, index) => {while (item > 0) {newArr[sortIndex++] = indexitem--}})return newArr
}

7.桶排序

         桶排序(也被称为箱排序)也是分布式排序算法,它将元素分为不同的桶(较小的数组), 再使用一个简单的排序算法,例如插入排序(用来排序小数组的不错的算法),来对每个桶进行 排序。然后,它将所有的桶合并为结果数组。  

function bucketSort(arr, bucketSize = 3) {if (arr.length < 2) {return arr}//根据数字的个数和大小,以及桶的容量创建数量合适的桶,将各数字分配到相应的桶里const buckets = createBuckets(arr, bucketSize)//调用相应的方法并返回成功排序了的数组return sortBucketsElement(buckets)
}function createBuckets(arr, bucketSize) {let maxValue = Math.max(...arr)let minValue = Math.min(...arr)const bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1const buckets = [...Array(bucketCount)].map(() => [])for (let i = 0; i < arr.length; i++) {const bucketIndex = Math.floor((maxValue - minValue) / bucketSize)buckets[bucketIndex].push(arr[i])}return buckets
}function sortBucketsElement(buckets) {const sortArr = []for (let i = 0; i < buckets.length; i++) {if (buckets[i]) {let newBucket = insertSort(buckets[i])sortArr.push(...newBucket)}}return sortArr}function insertSort(arr) {const { length } = arrlet tempfor (let i = 1; i < length; i++) {temp = arr[i]let j = iwhile (j > 0 && arr[j - 1] > temp) {arr[j] = arr[j - 1]j--}arr[j] = temp}return arr
}console.log(bucketSort([5, 4, 3, 2, 6, 1, 7, 10, 9, 8]));//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

8. 基数排序


         基数排序也是一个分布式排序算法,它根据数字的有效位或基数(这也是它为什么叫基数排 序)将整数分布到桶中。简单来说是根据最大数的位数来确定排序的次数,排序的时候分别从个位,十位,百位等分别进行排序。

function radixSort(arr) {const base = 10let divider = 1let max = Math.max(...arr)while (divider <= max) {const buckets = [...Array(10)].map(() => [])for (let i of arr) {buckets[Math.floor(i / divider) % base].push(i)}arr = [].concat(...buckets)console.log(arr);divider *= 10}return arr
}

二.搜索算法 

1.顺序搜索

        顺序或线性搜索是最基本的搜索算法。它的机制是,将每一个数据结构中的元素和我们要找 的元素做比较。顺序搜索是最低效的一种搜索算法。

function search(arr, value) {for (let i = 0; i < arr.length; i++) {if (value === arr[i]) {return i}}return -1
}

2. 二分搜索

function binarySearch(find, arr, start, end) {start = start || 0end = end || arr.length - 1arr = quickSort(arr)if (start <= end && find >= arr[start] && find <= arr[end]) {if (find === arr[start]) {return start}if (find === arr[end]) {return end}let mid = Math.ceil((start + end) / 2)if (arr[mid] === find) {return mid} else if (arr[mid] > find) {return binarySearch(find, arr, start, mid - 1)} else {return binarySearch(find, arr, mid + 1, end)}}return -1
}function quickSort(arr) {const { length } = arrif (length < 2) {return arr}let base = arr[0]let minArr = arr.slice(1).filter(item => item <= base)let maxArr = arr.slice(1).filter(item => item >= base)return quickSort(minArr).concat(base).concat(quickSort(maxArr))
}

3.内插搜索 

        内插搜索是改良版的二分搜索。二分搜索总是检查 mid 位置上的值,而内插搜索可能会根 据要搜索的值检查数组中的不同地方。

//适合数值分布比较均匀的数组
function insertSearch(find, arr, start, end) {start = start || 0end = end || arr.length - 1arr = quickSort(arr)if (start <= end && find >= arr[start] && find <= arr[end]) {if (find === arr[start]) {return start}if (find === arr[end]) {return end}let mid = start + Math.floor((find - arr[start]) / (arr[end] - arr[start]) * (end - start))if (arr[mid] === find) {return mid} else if (arr[mid] > find) {return insertSearch(find, arr, start, mid - 1)} else {return insertSearch(find, arr, mid + 1, end)}}return -1
}function quickSort(arr) {const { length } = arrif (length < 2) {return arr}let base = arr[0]let minArr = arr.slice(1).filter(item => item <= base)let maxArr = arr.slice(1).filter(item => item >= base)return quickSort(minArr).concat(base).concat(quickSort(maxArr))
}

 三.随机算法(洗牌算法)

        迭代数组,从最后一位开始并将当前位置和一个随机位置进行交换。这个随机位置比当前位置小。这样,这个算法可以保证随机过的位置不会再被随机一次。

function shuffleArray(array) {let n = array.lengthlet randomwhile (n != 0) {//对非负数进行向下取整random = (Math.random() * n--) >>> 0;[arr[n], arr[random]] = [arr[random], arr[n]]}
}

四.算法设计

1.分而治之

分而治之算法可以分成三个部分。

(1) 分解原问题为多个子问题(原问题的多个小实例)。

(2) 解决子问题,用返回解决子问题的方式的递归算法。递归算法的基本情形可以用来解决子 问题。

(3) 组合这些子问题的解决方式,得到原问题的解。

2.动态规划 

 2.1背包问题

背包问题是一个组合优化问题。它可以描述如下:给定一个固定大小、能够携重量 W 的背 包,以及一组有价值和重量的物品,找出一个最佳解决方案,使得装入背包的物品总重量不超过 W,且总价值最大。  

function knapSack(weights, values, w) {let n = weights.length - 1let f = [[]]for (let j = 0; j <= w; j++) {if (j < weights[0]) {f[0][j] = 0} else {f[0][j] = values[0]}}for (let j = 0; j <= w; j++) {for (let i = 1; i <= n; i++) {if (!f[i]) {f[i] = []}if (j < weights[i]) {f[i][j] = f[i - 1][j]} else {f[i][j] = Math.max(f[i - 1][j], f[i - 1][j - weights[i]] + values[i])}}}return f[n][w]
}console.log(knapSack([2, 2, 6, 5, 4], [6, 3, 5, 4, 6], 10)); 

2.2找出最长公共子序列

找出两个字符 串序列的最长子序列的长度。最长子序列是指,在两个字符串序列中以相同顺序出现,但不要求 连续(非字符串子串)的字符串序列。  

 

function LCS(str1, str2) {var m = str1.lengthvar n = str2.lengthvar dp = [new Array(n + 1).fill(0)] //第一行全是0console.log(dp);for (var i = 1; i <= m; i++) { //一共有m+1行dp[i] = [0] //第一列全是0for (var j = 1; j <= n; j++) { //一共有n+1列if (str1[i - 1] === str2[j - 1]) {//注意这里,str1的第一个字符是在第二列中,因此要减1,str2同理dp[i][j] = dp[i - 1][j - 1] + 1 //对角+1} else {dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1])}}}let res = printLCS(dp, str1, str2, m, n)return dp[m][n];
}function printLCS(dp, str1, str2, i, j) {if (i === 0 || j === 0) {return ''}if (str1[i - 1] === str2[j - 1]) {return printLCS(dp, str1, str2, i - 1, j - 1) + str1[i - 1]} else {if (dp[i][j - 1] > dp[i - 1][j]) {return printLCS(dp, str1, str2, i, j - 1)} else {return printLCS(dp, str1, str2, i - 1, j)}}}console.log(LCS("abcadf", "acbaed")) //4

3.贪心算法

 在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,但对范围相当广泛的许多问题他能产生整体最优解或者是整体最优解的近似解。

function tanxin(capacity, weights, values) {let list = []let select = []let total = 0for (let i = 0; i < weights.length; i++) {list.push({num: i + 1,weight: weights[i],value: values[i],rate: values[i] / weights[i]})}list.sort((a, b) => b.rate - a.rate) //降序for (let j = 0; j < list.length; j++) {let item = list[i]if (item.weight <= capacity) {select.push({num: item.num,weight: item.weight,value: item.value,rate: 1})total = total + item.valuecapacity = capacity - item.weight} else if (item.capacity > capacity && capacity > 0) {let rate = capacity / item.weightselect.push({num: item.num,weight: item.weight * rate,value: item.value * rate,rate})total = total + item.value * ratebreak} else {break}}return { select, total }
}

 4.回溯算法

 回溯法采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。

 给定一个 二维字符网格 board 和一个字符串单词 word
如果 word 存在于网格中,返回 true ;否则,返回 false
单词必须按照字母顺序,通过相邻的单元格内的字母构成
**二维数组:** board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]],
**目标:** word = "ABCCED"

function exist(board, word) {let row = board.length;  //行let col = board[0].length;  //列for (let i = 0; i < row; i++) {for (let j = 0; j < col; j++) {const ret = find(i, j, 0);if (ret) {return ret;}}}return false;function find(r, c, cur) {if (r >= row || r < 0) return false;if (c >= col || c < 0) return false;if (board[r][c] !== word[cur]) return false;if (cur == word.length - 1) return true;let letter = board[r][c];board[r][c] = null;const ret =find(r - 1, c, cur + 1) ||find(r + 1, c, cur + 1) ||find(r, c - 1, cur + 1) ||find(r, c + 1, cur + 1);//用null做标记是避免重复查找board[r][c] = letter;return ret;}
};

 五.总结

        还有诸多算法没有详细解读,随着自己的学习慢慢补充吧。 

相关文章:

JavaScript中的常见算法

一.排序算法 1.冒泡排序 冒泡排序比较所有相邻的两个项&#xff0c;如果第一个比第二个大&#xff0c;则交换它们。元素项向上移动至 正确的顺序&#xff0c;就好像气泡升至表面一样。 function bubbleSort(arr) {const { length } arrfor (let i 0; i < length - 1; i)…...

桥接模式:连接抽象与实现的设计艺术

桥接模式&#xff1a;连接抽象与实现的设计艺术 在软件开发中&#xff0c;设计模式是帮助我们以优雅的方式解决问题的模板。桥接模式&#xff08;Bridge Pattern&#xff09;是一种结构型设计模式&#xff0c;它的主要目标是将抽象部分与实现部分分离&#xff0c;这样两者可以…...

C语言——oj刷题——字符串左旋

问题&#xff1a; 实现一个函数&#xff0c;可以左旋字符串中的k个字符。 例如&#xff1a; ABCD左旋一个字符得到BCDA ABCD左旋两个字符得到CDAB 实现&#xff1a; 当我们谈到字符串左旋时&#xff0c;我们指的是将字符串中的字符向左移动一定数量的位置。这个问题在编程中…...

神经网络(Nature Network)

最近接触目标检测较多&#xff0c;再此对最基本的神经网络知识进行补充&#xff0c;本博客适合想入门人工智能、其含有线性代数及高等数学基础的人群观看 1.构成 由输入层、隐藏层、输出层、激活函数、损失函数组成。 输入层&#xff1a;接收原始数据隐藏层&#xff1a;进行…...

【Unity】QFramework通用背包系统优化:使用Odin优化编辑器

前言 在学习凉鞋老师的课程《QFramework系统设计&#xff1a;通用背包系统》第四章时&#xff0c;笔者使用了Odin插件&#xff0c;对Item和ItemDatabase的SO文件进行了一些优化&#xff0c;使物品页面更加紧凑、更易拓展。 核心逻辑和功能没有改动&#xff0c;整体代码量减少…...

基本算法--贪心

1.简述 贪心法的效率非常高&#xff0c;复杂度常常为O&#xff08;1&#xff09;&#xff0c;是一种局部最优的解题方法&#xff0c;而很多问题都需要求全局最优&#xff0c;&#xff0c;所以在使用贪心法之前需要评估是否能从局部最优推广到全局最优。 2.思路 作为算法的贪…...

13. 串口接收模块的项目应用案例

1. 使用串口来控制LED灯工作状态 使用串口发送指令到FPGA开发板&#xff0c;来控制第7课中第4个实验的开发板上的LED灯的工作状态。 LED灯的工作状态&#xff1a;让LED灯按指定的亮灭模式亮灭&#xff0c;亮灭模式未知&#xff0c;由用户指定&#xff0c;8个变化状态为一个循…...

Python re找到特定pattern并将此pattern重复n次

要找到字符串s中的数字&#xff0c;并将这些数字重复3次&#xff1a; import re s "abc123def456ghi789" # 找到所有的数字 numbers re.findall(r\d, s) # 重复每个数字3次 repeated_numbers [num * 3 for num in numbers] # 将重复的数字放回原位置 #…...

ChatGpt报错:We ran into an issue while authenticating you解决办法

在登录ChatGpt时报错&#xff1a;Oops&#xff01;,We ran into an issue while authenticating you.(我们在验证您时遇到问题)&#xff0c;记录一下解决过程。 完整报错&#xff1a; We ran into an issue while authenticating you. If this issue persists, please contact…...

如何从 iPhone 恢复已删除的视频:简单有效方法

无论您是在尝试释放空间时不小心删除了 iPhone 上的视频&#xff0c;还是在出厂时清空了手机&#xff0c;现在所有数据都消失了&#xff0c;都不要放弃。有一些方法可以恢复这些视频。 在本文中&#xff0c;我们将向您展示六种最有效的数据恢复方法&#xff0c;可以帮助您从 i…...

【python量化交易】qteasy使用教程02 - 获取和管理金融数据

qteasy教程2 - 获取并管理金融数据 qteasy教程2 - 获取并管理金融数据开始前的准备工作获取基础数据以及价格数据下载交易日历和基础数据查看股票和指数的基础数据下载沪市股票数据从本地获取股价数据生成K线图 数据类型的查找定期下载数据到本地回顾总结 qteasy教程2 - 获取并…...

数据库学习案例20240206-ORACLE NEW RAC agent and resource关系汇总。

1 集群架构图 整体集群架构图如下&#xff1a; 1 数据库启动顺序OHASD层面 操作系统进程init.ohasd run启动ohasd.bin init.ohasd run 集群自动启动是否被禁用 crsctl enable has/crsGIHOME所在文件系统是否被正常挂载。管道文件npohasd是否能够被访问&#xff0c; cd /var/t…...

TypeScript 入门

课程地址 ts 开发环境搭建 npm i -g typescript查看安装位置&#xff1a; $ npm root -g C:\Users\Daniel\AppData\Roaming\npm\node_modules创建 hello.ts&#xff1a; console.log("hello, ts");编译 ts 文件&#xff0c;得到 js 文件&#xff1a; $ tsc foo.…...

linux 磁盘相关操作

1.U盘接入虚拟机 &#xff08;1&#xff09;在插入u盘时&#xff0c;虚拟机会检测usb设备&#xff0c;在弹出窗口选择连接到虚拟机即可。 &#xff08;2&#xff09;或 直接在虚拟机--->可移动设备--->找到U盘---->连接 2.检测U盘是否被虚拟机识别 ls /dev/sd* 查…...

PyTorch: torch.max()函数详解

torch.max函数详解&#xff1a;基于PyTorch的深入探索 &#x1f335;文章目录&#x1f335; &#x1f333;引言&#x1f333;&#x1f333;torch.max()函数简介&#x1f333;&#x1f333;torch.max()的返回值&#x1f333;&#x1f333;torch.max()的应用示例&#x1f333;&am…...

Rust基础拾遗--核心功能

Rust基础拾遗 前言1.所有权与移动1.1 所有权 2.引用3.特型与泛型简介3.1 使用特型3.2 特型对象3.3 泛型函数与类型参数 4.实用工具特型5.闭包 前言 通过Rust程序设计-第二版笔记的形式对Rust相关重点知识进行汇总&#xff0c;读者通读此系列文章就可以轻松的把该语言基础捡起来…...

MySQL:常用指令

MySQL官网 一、在Windows 系统 cmd窗口里执行的命令 启动:net start MySQL停止:net stop MySQL卸载:sc delete MySQL 二、在macOS系统终端里执行的命令 启动&#xff1a;mysql.server start停止&#xff1a;mysql.server stop重启&#xff1a;mysql.server restart 三、执行帮…...

Scrapy:Python中强大的网络爬虫框架

Scrapy&#xff1a;Python中强大的网络爬虫框架 在当今信息爆炸的时代&#xff0c;从互联网上获取数据已经成为许多应用程序的核心需求。Scrapy是一款基于Python的强大网络爬虫框架&#xff0c;它提供了一种灵活且高效的方式来提取、处理和存储互联网上的数据。本文将介绍Scrap…...

linux系统非关系型数据库redis的配置文件

redis配置文件 Redis的配置文件位于Redis安装目录下&#xff0c;文件名为redis.conf&#xff0c;配置项说明如下 Redis默认不是以守护进程的方式运行&#xff0c;可以通过该配置项修改&#xff0c;使用yes启用守护进程 daemonize no当Redis以守护进程方式运行时&#xff0c;Red…...

电力负荷预测 | 基于LSTM、TCN的电力负荷预测(Python)

文章目录 效果一览文章概述源码设计参考资料效果一览 文章概述 电力负荷预测 | 基于LSTM、TCN的电力负荷预测(Python) 源码设计 #------------------...

CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型

CVPR 2025 | MIMO&#xff1a;支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题&#xff1a;MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者&#xff1a;Yanyuan Chen, Dexuan Xu, Yu Hu…...

K8S认证|CKS题库+答案| 11. AppArmor

目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作&#xff1a; 1&#xff09;、切换集群 2&#xff09;、切换节点 3&#xff09;、切换到 apparmor 的目录 4&#xff09;、执行 apparmor 策略模块 5&#xff09;、修改 pod 文件 6&#xff09;、…...

STM32+rt-thread判断是否联网

一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

React19源码系列之 事件插件系统

事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类&#xff1a;块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

Python如何给视频添加音频和字幕

在Python中&#xff0c;给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加&#xff0c;包括必要的代码示例和详细解释。 环境准备 在开始之前&#xff0c;需要安装以下Python库&#xff1a;…...

在WSL2的Ubuntu镜像中安装Docker

Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包&#xff1a; for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...

GC1808高性能24位立体声音频ADC芯片解析

1. 芯片概述 GC1808是一款24位立体声音频模数转换器&#xff08;ADC&#xff09;&#xff0c;支持8kHz~96kHz采样率&#xff0c;集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器&#xff0c;适用于高保真音频采集场景。 2. 核心特性 高精度&#xff1a;24位分辨率&#xff0c…...

Go 语言并发编程基础:无缓冲与有缓冲通道

在上一章节中&#xff0c;我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道&#xff0c;它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好&#xff0…...

CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝

目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为&#xff1a;一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...