「mds算法java」mds算法代码
本篇文章给大家谈谈mds算法java,以及mds算法代码对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。
本文目录一览:
- 1、(十一)MDS算法
- 2、第11章 降维
- 3、MDS加密是什么意思?
- 4、java登录模块验证出现问题求解答
- 5、为什么在drtoolbox中MDS 和PCA算法一样的?那么MDS 不是名不符实了吗?
(十一)MDS算法
从降维的层面来说,由于MDS是一种降维方法,那么它和PCA等其他降维方式有什么不同呢,什么样的场景适用于使用MDS呢?与PCA不同的是, MDS保证了原始数据点之间的距离与降维后数据点的距离一致 。
另一方面,假如我们只知道一组点之间的距离,我们如何反演出它的相对坐标?这也是MDS可以做到的。
MDS的这种降维是继承了原始数据空间中的欧式距离度量。使得数据点能在低维空间重构其相对位置。
举个例子,比如如果用欧式距离度量两种商品的相似度,那么,商品特征繁多,我们就需要在不破坏商品之间的这种相似度的情况下对数据进行降维。
再比如我们手头只有一个邻接矩阵,代表飞机坠机后的残骸之间的距离,现在新来了一个残骸,多个声呐探测后能够计算出这个残骸与其他所有残骸的距离,但是我们无法得知其具体位置,那么我们可以通过MDS反演出其相对距离,那么我们只要知道其中一个残骸的位置,这个残骸的位置也就很明朗了。根据这个相对距离缩放到真实距离就行。
我们现在给出构造性证明:
首先是必要性:
即:
证明:
矩阵A的列平均:
矩阵A的行平均:
矩阵A的整体平均:
那么:
所以:
由此可得,B一定是一个对称半正定矩阵,必要性证毕。
接着证明充分性:
即:
证明:
假设特征值对应的矩阵为:
令 因为
那么 ;
所以可以将 特征分解为:
即: ;
我们算一下第 个特征向量和第 个特征向量的欧式距离,以确定其确实可以由距离矩阵给出。
因为
那么
那么
且:
所以:
这恰好证明了那么在 维欧式空间中,点集 之间的距离可由距离矩阵 给出。充分性证毕。
那么也就是说只要 是半正定的矩阵,那么我们总能找到它的特征向量 ,将对应的坐标 求出。
我们还能发现一些好玩的性质,比如因为 所以:
由于不同特征值对应的特征向量是正交的,那么这个式子就告诉我们 是其中一个特征向量。且对应特征值为0。
也就是说:
这表明实际上,我们在保持其欧式距离结构的前提下,将坐标原点定在了所有点的均值处。
回顾一下我们做了什么,我们有一个距离矩阵 ,这个距离矩阵的来源可以是我们手上有一堆已有数据产生的,或者是直接给出的;我们试图在低维或者同维度下重构它的数据点,我们首先通过 矩阵找到 矩阵, 矩阵构成 矩阵,由于距离矩阵一定是对称半正定的, 矩阵一定也是对称半正定的。由于 是对称的,所以我们一定可以对它进行特征分解: ;如果此时需要在低维下重构,那么对特征值排序后剔除小的,如果在同维度下重构,那么保持原样即可。我们最后就直接还原出其相对坐标: 。
我们要是新来一个数据点,或者说新来一点,我只是知道它与其他所有点的距离,这样的情况怎么办。
我们可能会不假思索的回答说,这个很简单啊,只要把点加入矩阵D中,由 变成 就好了,其他重头再来一遍得到一个新的坐标点集 就好了。
在数据量不是很大的情况下,这确实不失为一种好方法,但是万一数据量很大,重头再来的代价就让人有点头疼了,这就是为什么我们要看看有什么办法可以绕过这样重头再来的过程。
假设我们新的数据点是
我们得计算一下它和已有的第 个点之间的距离:
这里面我们什么是已知的,什么是未知的呢?
是我们未知的,其他都是已知的。
我们对 求和,看看是否能将一个未知量给表示成另一个未知量。
由于上面我们有说过:
所以:
因此:
将这个结果代入原式:
我们令
写成矩阵形式:
因为
所以新加入的数据点可以由下面公式导出:
第11章 降维
去掉数据集中关联性不大和冗余的数据,确保不出现过度适应的前提下降低计算的成本,需要对特征进行无损规约,数学上叫降维。广泛用于模式识别、文本检索以及机器学习领域,主要分为两类,特征提取和特征筛选,前者是高维数据投影到低维空间,后者是特征子集代替原始特征集,包括特征分级和特征筛选,分级是找到优化后的特征子集。
特征提取可以分成线性抽取和非线性抽取两种方法,前者是试图找到一个仿射空间能够最好的说明数据分布的变化,后者对高维非线性曲线平面分布的数据非常有效。
线性特征的抽取方法:
首先设定一些标准,然后挑选出满足标准的特征。
算法首先调用一个权重函数得到每个特征的权重值,权重评价指标是平均精确度下降 importance.type = 1 ,除了上面用的随机森林,还可以使用 chi.squared, information.gain 。
然后获取优化的特征子集,首先5折交叉验证评估特征子集的重要性,爬山搜索算法从原始特征集中选出优化的特征子集,也可以选择其他算法,比如 forward.search 。还可以使用caret包进行特征筛选,据说这个包是个宝呀,包罗万象。
主成分分析是一种应用非常广泛的线性降维方法,适合数据集包含非常多的特征,并且特征间彼此冗余(相关的情况)。通过将特征集缩减成一小部分能代表原始特征集最主要变化的主要特征分量,实现高维数据到低维数据空间的映射。
特征选择过程中会去掉一些彼此关联但有价值的特征,需要在特征制取过程中考虑将这些特征综合到单特征中,PCA采用正交变换将彼此有关联的特征转化为主成分,以便我们确定方差趋势。
算法主要包括以下步骤:1)找到平均向量的数据点;2)计算
协方差矩阵;3)计算特征向量;4)对特征向量排序并选择前k个特征向量;5)构建特征向量矩阵;最后,将数据样本转换成新的子集。
拓展
princomp 是另一个高不成分分析函数,与上面的 prcomp 采用奇异值分解不同,采用相关矩阵或协方差矩阵的特征值计算方法,一般更习惯用后者。
以上两个函数均来自stats包,还可以使用psych包中的principal函数进行:
Kaiser方法、scree(碎石测试)和依据挑选规则使用解释变量比例都可以。碎石测试的主要目的是将主成分结果以碎石图方式表达,从图中找到引起曲线斜率变化最快的因素。
主成分为2时,斜率变化最快。也可以使用nfactors以并行分析非图形方式作Cattell碎石来测试。
biplot绘制数据与原始特征在前两个主成分上的投影图
biplot绘制数据及原始特征在前两个主成分上的投影,农业高,教育和检查低的省份在PC1上得分高;婴儿死亡率高,农业低的省份在主成分PC2上得分较高。
多维尺度分析通过图形方式展示多个对象之间的相似或相异程度距离),多维是指映射到一维、二维或多维空间表达CF全家人相对距离,一般使用一或二维空间。
分成计量和非计量两类,前者是主要考虑如何保证降维后各对象之间的距离尽可能接近它们在原始空间的距离,后者则假设两个空间中对象的距离排名已知,而且变换后排名不变。
可以通过将投影维度绘制在一个散点图中比较MDS和PCA的差异,如果MDS采用欧氏距离,投影维度将与PCA完全一致。
奇异值分解是矩阵分解的一种形式,可以将一个矩阵分解为两个正交矩阵和一个对角矩阵,原始矩阵可由这三个矩阵相乘得到。可以帮助去掉那些从线性代数角度观察存在线性相关冗余的矩阵,可以应用在特征筛选,图像处理和聚类等。
SVD是一类分解实数或复数矩阵的常见方法,PCA可以被看成SVD的一种特例:
两个矩阵基本相同。
[图片上传失败...(image-be0ae8-1639570485003)]
图像压缩领域应用最为广泛的标准测试图像,花花公子当年的模特图呀!
不知为啥,读什么图片都是负片呢?先继续:
ISOMAP属于流形学习方法,支持线性空间到非线性数据结构的转换,与MDS类似,它也能够以图形方式展现对象之间的相似性或相异性(距离),不过,由于数据采用非线性结构表示,以几何距离代替MDS中有欧氏距离。
ISOMAP是一种等距映射非线性降维方法,如果将计量MDS方法中数据点间成对的欧氏距离替换成邻接图间的测地距离,就可以将ISOMAP当做计量MDS方法的扩展。
算法分为4步:确定邻近点,构建邻接图,计算最短路径和MDS分析找到数据间的低维嵌入。
扩展
可以将RnavGraph包将图形作为数据浏览的基础方式来实现高维数据的可视化。
LLE算法是PCA算法的扩展,通过嵌入高维空间内的流形映射到低维空间来实现数据压缩。ISOMAP是全局性非线性降维,LLE主要是局部母性降维算法,假设每个数据点可以由k个邻近点的母性组合构成,映射后能保持原来的数据性质。
LLE是一种非线性降维算法,基于它我们可以得到高维数据在低维空间保持原有数据邻近嵌入关系的映射。算法主要分成三步:计算每个点的k个邻近,然后计算每个邻近点的权值,使得每个点都能最优地由其邻近点组合重构,即残差和最小。
扩展
还可以选择RDRTollbox包实现非线性降维,支持ISOMAP和LLE算法。
MDS加密是什么意思?
是MD5加密吧
MD5的全称是Message-Digest Algorithm 5,在90年代初由MIT的计算机科学实验室和RSA Data Security Inc发明,经MD2、MD3和MD4发展而来。
Message-Digest泛指字节串(Message)的Hash变换,就是把一个任意长度的字节串变换成一定长的大整数。请注意我使用了“字节串”而不是“字符串”这个词,是因为这种变换只与字节的值有关,与字符集或编码方式无关。
MD5将任意长度的“字节串”变换成一个128bit的大整数,并且它是一个不可逆的字符串变换算法,换句话说就是,即使你看到源程序和算法描述,也无法将一个MD5的值变换回原始的字符串,从数学原理上说,是因为原始的字符串有无穷多个,这有点象不存在反函数的数学函数。
MD5的典型应用是对一段Message(字节串)产生fingerprint(指纹),以防止被“篡改”。举个例子,你将一段话写在一个叫readme.txt文件中,并对这个readme.txt产生一个MD5的值并记录在案,然后你可以传播这个文件给别人,别人如果修改了文件中的任何内容,你对这个文件重新计算MD5时就会发现。如果再有一个第三方的认证机构,用MD5还可以防止文件作者的“抵赖”,这就是所谓的数字签名应用。
MD5还广泛用于加密和解密技术上,在很多操作系统中,用户的密码是以MD5值(或类似的其它算法)的方式保存的,用户Login的时候,系统是把用户输入的密码计算成MD5值,然后再去和系统中保存的MD5值进行比较,而系统并不“知道”用户的密码是什么。
一些黑客破获这种密码的方法是一种被称为“跑字典”的方法。有两种方法得到字典,一种是日常搜集的用做密码的字符串表,另一种是用排列组合方法生成的,先用MD5程序计算出这些字典项的MD5值,然后再用目标的MD5值在这个字典中检索。
即使假设密码的最大长度为8,同时密码只能是字母和数字,共26+26+10=62个字符,排列组合出的字典的项数则是P(62,1)+P(62,2)….+P(62,8),那也已经是一个很天文的数字了,存储这个字典就需要TB级的磁盘组,而且这种方法还有一个前提,就是能获得目标账户的密码MD5值的情况下才可以。
在很多电子商务和社区应用中,管理用户的Account是一种最常用的基本功能,尽管很多Application Server提供了这些基本组件,但很多应用开发者为了管理的更大的灵活性还是喜欢采用关系数据库来管理用户,懒惰的做法是用户的密码往往使用明文或简单的变换后直接保存在数据库中,因此这些用户的密码对软件开发者或系统管理员来说可以说毫无保密可言,本文的目的是介绍MD5的Java Bean的实现,同时给出用MD5来处理用户的Account密码的例子,这种方法使得管理员和程序设计者都无法看到用户的密码,尽管他们可以初始化它们。但重要的一点是对于用户密码设置习惯的保护。
有兴趣的读者可以从这里取得MD5也就是RFC 1321的文本。
java登录模块验证出现问题求解答
前期准备
首先要先明确有个大体的思路,要实现什么样的功能,了解完成整个模块要运用到哪些方面的知识,以及从做的过程中去发现自己的不足。技术方面的进步大都都需要从实践中出来的。
功能:用户注册功能+系统登录功能+生成验证码
知识:窗体设计、数据库设计、JavaBean封装属性、JDBC实现对数据库的连接、验证码(包括彩色验证码)生成技术,还有就些比如像使用正则表达式校验用户注册信息、随机获得字符串、对文本可用字符数的控制等
设计的模块预览图:
彩色验证码预览图:
所用数据库:MySQL
数据库设计
创建一个数据库db_database01,其中包含一个表格tb_user,用来保存用户的注册的数据。
其中包含4个字段
id int(11)
username varchar(15)
password varchar(20)
email varchar(45)
MySQL语句可以这样设计:
create schema db_database01;
use db_database01;
create table tb_user(
id int(11) not null auto_increment primary key,
username varchar(15) not null,
password varchar(20) not null,
email varchar(45) not null
);
insert into tb_user values(1,"lixiyu","lixiyu",lixiyu419@gmail.com);
这样把lixiyu作为用户名。
select语句检查一下所建立的表格:
编写JavaBean封装用户属性
package com.lixiyu.model;
public class User {
private int id;// 编号
private String username;// 用户名
private String password;// 密码
private String email;// 电子邮箱
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
编写JDBC工具类
将与数据库操作相关的代码放置在DBConfig接口和DBHelper类中
DBConfig接口用于保存数据库、用户名和密码信息
代码:
package com.lixiyu.util;
public interface DBConfig {
String databaseName = "db_database01";// 数据库名称
String username = "root";// 数据库用户名
String password = "lixiyu";// 数据库密码
}
为简化JDBC开发,DBHelper使用了了Commons DbUtil组合。
DBHelper类继承了DBConfig接口,该类中包含4种方法:
(1)getConnection()方法:获得数据库连接,使用MySQL数据源来简化编程,避免因加载数据库驱动而发生异常。
(2)exists()方法:判断输入的用户名是否存在。
(3)check()方法:当用户输入用户名和密码,查询使用check()方法是否正确。
(4)save()方法:用户输入合法注册信息后,,将信息进行保存。
详细代码:
package com.lixiyu.util;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.ColumnListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.apache.commons.lang.StringEscapeUtils;
import com.lixiyu.model.User;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
public class DBHelper implements DBConfig {
/*
* 使用MySQL数据源获得数据库连接对象
*
* @return:MySQL连接对象,如果获得失败返回null
*/
public static Connection getConnection() {
MysqlDataSource mds = new MysqlDataSource();// 创建MySQL数据源
mds.setDatabaseName(databaseName);// 设置数据库名称
mds.setUser(username);// 设置数据库用户名
mds.setPassword(password);// 设置数据库密码
try {
return mds.getConnection();// 获得连接
} catch (SQLException e) {
e.printStackTrace();
}
return null;// 如果获取失败就返回null
}
/*
* 判断指定用户名的用户是否存在
*
* @return:如果存在返回true,不存在或者查询失败返回false
*/
public static boolean exists(String username) {
QueryRunner runner = new QueryRunner();// 创建QueryRunner对象
String sql = "select id from tb_user where username = '" + username + "';";// 定义查询语句
Connection conn = getConnection();// 获得连接
ResultSetHandlerListObject rsh = new ColumnListHandler();// 创建结果集处理类
try {
ListObject result = runner.query(conn, sql, rsh);// 获得查询结果
if (result.size() 0) {// 如果列表中存在数据
return true;// 返回true
} else {// 如果列表中没有数据
return false;// 返回false
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DbUtils.closeQuietly(conn);// 关闭连接
}
return false;// 如果发生异常返回false
}
/*
* 验证用户名和密码是否正确 使用Commons Lang组件转义字符串避免SQL注入
*
* @return:如果正确返回true,错误返回false
*/
public static boolean check(String username, char[] password) {
username = StringEscapeUtils.escapeSql(username);// 将用户输入的用户名转义
QueryRunner runner = new QueryRunner();// 创建QueryRunner对象
String sql = "select password from tb_user where username = '" + username + "';";// 定义查询语句
Connection conn = getConnection();// 获得连接
ResultSetHandlerObject rsh = new ScalarHandler();// 创建结果集处理类
try {
String result = (String) runner.query(conn, sql, rsh);// 获得查询结果
char[] queryPassword = result.toCharArray();// 将查询到得密码转换成字符数组
if (Arrays.equals(password, queryPassword)) {// 如果密码相同则返回true
Arrays.fill(password, '0');// 清空传入的密码
Arrays.fill(queryPassword, '0');// 清空查询的密码
return true;
} else {// 如果密码不同则返回false
Arrays.fill(password, '0');// 清空传入的密码
Arrays.fill(queryPassword, '0');// 清空查询的密码
return false;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DbUtils.closeQuietly(conn);// 关闭连接
}
return false;// 如果发生异常返回false
}
/*
* 保存用户输入的注册信息
*
* @return:如果保存成功返回true,保存失败返回false
*/
public static boolean save(User user) {
QueryRunner runner = new QueryRunner();// 创建QueryRunner对象
String sql = "insert into tb_user (username, password, email) values (?, ?, ?);";// 定义查询语句
Connection conn = getConnection();// 获得连接
Object[] params = { user.getUsername(), user.getPassword(), user.getEmail() };// 获得传递的参数
try {
int result = runner.update(conn, sql, params);// 保存用户
if (result 0) {// 如果保存成功返回true
return true;
} else {// 如果保存失败返回false
return false;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DbUtils.closeQuietly(conn);// 关闭连接
}
return false;// 如果发生异常返回false
}
}
系统登录
1.1窗体设计
使用BoxLayout布局,将控件排列方式设置从上至下:
复制代码代码如下:
contentPane.setLayout(new BoxLayout(contentPane,BoxLayout.PAGE_AXIS));
窗体使用了标签、文本域、密码域和按钮等控件
实现代码:
public class login extends JFrame{
private static final long serialVersionUID = -4655235896173916415L;
private JPanel contentPane;
private JTextField usernameTextField;
private JPasswordField passwordField;
private JTextField validateTextField;
private String randomText;
public static void main(String args[]){
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
} catch (Throwable e) {
e.printStackTrace();
}
EventQueue.invokeLater(new Runnable(){
public void run(){
try{
login frame=new login();
frame.setVisible(true);
}catch(Exception e){
e.printStackTrace();
}
}
});
}
public login(){
setTitle("系统登录");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
contentPane=new JPanel();
setContentPane(contentPane);
contentPane.setLayout(new BoxLayout(contentPane,BoxLayout.PAGE_AXIS));
JPanel usernamePanel=new JPanel();
contentPane.add(usernamePanel);
JLabel usernameLable=new JLabel("\u7528\u6237\u540D\uFF1A");
usernameLable.setFont(new Font("微软雅黑", Font.PLAIN, 15));
usernamePanel.add(usernameLable);
usernameTextField=new JTextField();
usernameTextField.setFont(new Font("微软雅黑", Font.PLAIN, 15));
usernamePanel.add(usernameTextField);
usernameTextField.setColumns(10);
JPanel passwordPanel = new JPanel();
contentPane.add(passwordPanel);
JLabel passwordLabel = new JLabel("\u5BC6 \u7801\uFF1A");
passwordLabel.setFont(new Font("微软雅黑", Font.PLAIN, 15));
passwordPanel.add(passwordLabel);
passwordField = new JPasswordField();
passwordField.setColumns(10);
passwordField.setFont(new Font("微软雅黑", Font.PLAIN, 15));
passwordPanel.add(passwordField);
JPanel validatePanel = new JPanel();
contentPane.add(validatePanel);
JLabel validateLabel = new JLabel("\u9A8C\u8BC1\u7801\uFF1A");
validateLabel.setFont(new Font("微软雅黑", Font.PLAIN, 15));
validatePanel.add(validateLabel);
validateTextField = new JTextField();
validateTextField.setFont(new Font("微软雅黑", Font.PLAIN, 15));
validatePanel.add(validateTextField);
validateTextField.setColumns(5);
randomText = RandomStringUtils.randomAlphanumeric(4);
CAPTCHALabel label = new CAPTCHALabel(randomText);//随机验证码
label.setFont(new Font("微软雅黑", Font.PLAIN, 15));
validatePanel.add(label);
JPanel buttonPanel=new JPanel();
contentPane.add(buttonPanel);
JButton submitButton=new JButton("登录");
submitButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
do_submitButton_actionPerformed(e);
}
});
submitButton.setFont(new Font("微软雅黑", Font.PLAIN, 15));
buttonPanel.add(submitButton);
JButton cancelButton=new JButton("退出");
cancelButton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
do_cancelButton_actionPerformed(e);
}
});
cancelButton.setFont(new Font("微软雅黑",Font.PLAIN,15));
buttonPanel.add(cancelButton);
pack();// 自动调整窗体大小
setLocation(com.lixiyu.util.SwingUtil.centreContainer(getSize()));// 让窗体居中显示
}
窗体居中显示:
public class SwingUtil {
/*
* 根据容器的大小,计算居中显示时左上角坐标
*
* @return 容器左上角坐标
*/
public static Point centreContainer(Dimension size) {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();// 获得屏幕大小
int x = (screenSize.width - size.width) / 2;// 计算左上角的x坐标
int y = (screenSize.height - size.height) / 2;// 计算左上角的y坐标
return new Point(x, y);// 返回左上角坐标
}
}
1.2获取及绘制验证码
public class CAPTCHALabel extends JLabel {
private static final long serialVersionUID = -963570191302793615L;
private String text;// 用于保存生成验证图片的字符串
public CAPTCHALabel(String text) {
this.text = text;
setPreferredSize(new Dimension(60, 36));// 设置标签的大小
}
@Override
public void paint(Graphics g) {
super.paint(g);// 调用父类的构造方法
g.setFont(new Font("微软雅黑", Font.PLAIN, 16));// 设置字体
g.drawString(text, 5, 25);// 绘制字符串
}
}
*彩色验证码:
public class ColorfulCAPTCHALabel extends JLabel {
private static final long serialVersionUID = -963570191302793615L;
private String text;// 用于保存生成验证图片的字符串
private Color[] colors = { Color.BLACK, Color.BLUE, Color.CYAN, Color.DARK_GRAY, Color.GRAY, Color.GREEN, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE,
Color.PINK, Color.RED, Color.WHITE, Color.YELLOW };// 定义画笔颜色数组
public ColorfulCAPTCHALabel(String text) {
this.text = text;
setPreferredSize(new Dimension(60, 36));// 设置标签的大小
}
@Override
public void paint(Graphics g) {
super.paint(g);// 调用父类的构造方法
g.setFont(new Font("微软雅黑", Font.PLAIN, 16));// 设置字体
for (int i = 0; i text.length(); i++) {
g.setColor(colors[RandomUtils.nextInt(colors.length)]);
g.drawString("" + text.charAt(i), 5 + i * 13, 25);// 绘制字符串
}
}
}
1
为什么在drtoolbox中MDS 和PCA算法一样的?那么MDS 不是名不符实了吗?
当mds中的矩阵采用的是距离矩阵时,降维跟PCA方法本质上是一样的。
mds算法java的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于mds算法代码、mds算法java的信息别忘了在本站进行查找喔。
发布于:2022-11-30,除非注明,否则均为
原创文章,转载请注明出处。