矩阵连乘 动态规划_Java动态规划

1. 介绍

动态规划典型的被用于优化递归算法,因为它们倾向于以指数的方式进行扩展。动态规划主要思想是将复杂问题(带有许多递归调用)分解为更小的子问题,然后将它们保存到内存中,这样我们就不必在每次使用它们时重新计算它们。

要理解动态规划的概念,我们需要熟悉一些主题:

  1. 什么是动态规划?
  2. 贪心算法
  3. 简化的背包问题
  4. 传统的背包问题
  5. Levenshtein Distance
  6. LCS-最长的共同子序列
  7. 利用动态规划的其他问题
  8. 结论

本文所有代码均为java代码实现。

2. 什么是动态规划?

动态规划是一种编程原理,可以通过将非常复杂的问题划分为更小的子问题来解决。这个原则与递归很类似,但是与递归有一个关键点的不同,就是每个不同的子问题只能被解决一次。

为了理解动态规划,我们首先需要理解递归关系的问题。每个单独的复杂问题可以被划分为很小的子问题,这表示我们可以在这些问题之间构造一个递归关系。

让我们来看一个我们所熟悉的例子:斐波拉契数列,斐波拉契数列的定义具有以下的递归关系:

9797868a7ab6b237e12658cfca1c2ad7.png

注意:递归关系是递归地定义下一项是先前项的函数的序列的等式。Fibonacci序列就是一个很好的例子。

所以,如果我们想要找到斐波拉契数列序列中的第n个数,我们必须知道序列中第n个前面的两个数字。

但是,每次我们想要计算Fibonacci序列的不同元素时,我们在递归调用中都有一些重复调用,如下图所示,我们计算Fibonacci(5):

428809edddfea2a7cff0a3159cf9a890.png

例如:如果我们想计算F(5),明显的我们需要计算F(3)和F(4)作为计算F(5)的先决条件。然而,为了计算F(4),我们需要计算F(3)和F(2),因此我们又需要计算F(2)和F(1)来得到F(3),其他的求解诸如此类。

这样的话就会导致很多重复的计算,这些重复计算本质上是冗余的,并且明显的减慢了算法的效率。为了解决这种问题,我们介绍动态规划。

在这种方法中,我们对解决方案进行建模,就像我们要递归地解决它一样,但我们从头开始解决它,记忆到达顶部采取的子问题(子步骤)的解决方案。

因此,对于Fibonacci序列,我们首先求解并记忆F(1)和F(2),然后使用两个记忆步骤计算F(3),依此类推。这意味着序列中每个单独元素的计算都是O(1),因为我们已经知道前两个元素。

当使用动态规划解决问题的时候,我们一般会采用下面三个步骤:

  1. 确定适用于所述问题的递归关系
  2. 初始化内存、数组、矩阵的初始值
  3. 确保当我们进行递归调用(可以访问子问题的答案)的时候它总是被提前解决。

遵循这些规则,让我们来看一下使用动态规划的算法的例子:

3. 贪心算法

下面来以这个为例子:

Given a rod of length n and an array that contains prices of all pieces of size smaller than n. Determine the maximum value obtainable by cutting up the rod and selling the pieces.

3.1. 对于没有经验的开发者可能会采取下面这种做法

这个问题实际上是为动态规划量身定做的,但是因为这是我们的第一个真实例子,让我们看看运行这些代码会遇到多少问题:

public class naiveSolution {  static int getValue(int[] values, int length) { if (length <= 0) return 0; int tmpMax = -1; for (int i = 0; i < length; i++) { tmpMax = Math.max(tmpMax, values[i] + getValue(values, length - i - 1)); } return tmpMax; } public static void main(String[] args) { int[] values = new int[]{3, 7, 1, 3, 9}; int rodLength = values.length; System.out.println("Max rod value: " + getValue(values, rodLength)); }}

输出结果:

Max rod value: 17

该解决方案虽然正确,但效率非常低,递归调用的结果没有保存,所以每次有重叠解决方案时,糟糕的代码不得不去解决相同的子问题。

3.2.动态方法

利用上面相同的基本原理,添加记忆化并排除递归调用,我们得到以下实现:

public class dpSolution {  static int getValue(int[] values, int rodLength) { int[] subSolutions = new int[rodLength + 1]; for (int i = 1; i <= rodLength; i++) { int tmpMax = -1; for (int j = 0; j < i; j++) tmpMax = Math.max(tmpMax, values[j] + subSolutions[i - j - 1]); subSolutions[i] = tmpMax; } return subSolutions[rodLength]; } public static void main(String[] args) { int[] values = new int[]{3, 7, 1, 3, 9}; int rodLength = values.length; System.out.println("Max rod value: " + getValue(values, rodLength)); }}

输出结果:

Max rod value: 17

正如我们所看到的的,输出结果是一样的,所不同的是时间和空间复杂度。

通过从头开始解决子问题,我们消除了递归调用的需要,利用已解决给定问题的所有先前子问题的事实。

性能的提升

为了给出动态方法效率更高的观点的证据,让我们尝试使用30个值来运行该算法。 一种算法需要大约5.2秒来执行,而动态解决方法需要大约0.000095秒来执行。

4. 简化的背包问题

简化的背包问题是一个优化问题,没有一个解决方案。这个问题的问题是 - “解决方案是否存在?”:

Given a set of items, each with a weight w1, w2... determine the number of each item to put in a knapsack so that the total weight is less than or equal to a given limit K.

给定一组物品,每个物品的重量为w1,w2 …确定放入背包中的每个物品的数量,以使总重量小于或等于给定的极限K

首先让我们把元素的所有权重存储在W数组中。接下来,假设有n个项目,我们将使用从1到n的数字枚举它们,因此第i个项目的权重为W [i]。我们将形成(n + 1)x(K + 1)维的矩阵M。M [x] [y]对应于背包问题的解决方案,但仅包括起始数组的前x个项,并且最大容量为y

例如

假设我们有3个元素,权重分别是w1=2kg,w2=3kg,w3=4kg。利用上面的方法,我们可以说M [1] [2]是一个有效的解决方案。

这意味着我们正在尝试用重量阵列中的第一个项目(w1)填充容量为2kg的背包。

在M [3] [5]中,我们尝试使用重量阵列的前3项(w1,w2,w3)填充容量为5kg的背包。

这不是一个有效的解决方案,因为我们过度拟合它。

4.1. 矩阵初始化

当初始化矩阵的时候有两点需要注意:

Does a solution exist for the given subproblem (M[x][y].exists) AND does the given solution include the latest item added to the array (M[x][y].includes).

给定子问题是否存在解(M [x] [y] .exists)并且给定解包括添加到数组的最新项(M [x] [y] .includes)。

因此,初始化矩阵是相当容易的,M[0][k].exists总是false,如果k>0,因为我们没有把任何物品放在带有k容量的背包里。

另一方面,M[0][0].exists = true,当k=0的时候,背包应该是空的,因此我们在里面没有放任何东西,这个是一个有效的解决方案。

此外,我们可以说M[k][0].exists = true,但是对于每个k来说 M[k][0].includes = false。

注意:仅仅因为对于给定的M [x] [y]存在解决方案,它并不一定意味着该特定组合是解决方案。

在M [10] [0]的情况下,存在一种解决方案 - 不包括10个元素中的任何一个。

这就是M [10] [0] .exists = true但M [10] [0] .includes = false的原因。

4.2.算法原则

接下来,让我们使用以下伪代码构造M [i] [k]的递归关系:

if (M[i-1][k].exists == True):  M[i][k].exists = True M[i][k].includes = Falseelif (k-W[i]>=0):  if(M[i-1][k-W[i]].exists == true): M[i][k].exists = True M[i][k].includes = Trueelse:  M[i][k].exists = False

因此,解决方案的要点是将子问题分为两种情况:

  1. 对于容量k,当存在第一个i-1元素的解决方案
  2. 对于容量k-W [i],当第一个i-1元素存在解决方案

第一种情况是不言自明的,我们已经有了问题的解决方案。

第二种情况是指了解第一个i-1元素的解决方案,但是容量只有一个第i个元素不满,这意味着我们可以添加一个第i个元素,并且我们有一个新的解决方案!

4.3. 实现

下面这何种实现方式,使得事情变得更加容易,我们创建了一个类Element来存储元素:

public class Element {  private boolean exists; private boolean includes; public Element(boolean exists, boolean includes) { this.exists = exists; this.includes = includes; } public Element(boolean exists) { this.exists = exists; this.includes = false; } public boolean isExists() { return exists; } public void setExists(boolean exists) { this.exists = exists; } public boolean isIncludes() { return includes; } public void setIncludes(boolean includes) { this.includes = includes; }}

接着,我们可以深入了解主要的类:

public class Knapsack {  public static void main(String[] args) { Scanner scanner = new Scanner (System.in); System.out.println("Insert knapsack capacity:"); int k = scanner.nextInt(); System.out.println("Insert number of items:"); int n = scanner.nextInt(); System.out.println("Insert weights: "); int[] weights = new int[n + 1]; for (int i = 1; i <= n; i++) { weights[i] = scanner.nextInt(); } Element[][] elementMatrix = new Element[n + 1][k + 1]; elementMatrix[0][0] = new Element(true); for (int i = 1; i <= k; i++) { elementMatrix[0][i] = new Element(false); } for (int i = 1; i <= n; i++) { for (int j = 0; j <= k; j++) { elementMatrix[i][j] = new Element(false); if (elementMatrix[i - 1][j].isExists()) { elementMatrix[i][j].setExists(true); elementMatrix[i][j].setIncludes(false); } else if (j >= weights[i]) { if (elementMatrix[i - 1][j - weights[i]].isExists()) { elementMatrix[i][j].setExists(true); elementMatrix[i][j].setIncludes(true); } } } } System.out.println(elementMatrix[n][k].isExists()); }}

唯一剩下的就是解决方案的重建,在上面的类中,我们知道解决方案是存在的,但是我们不知道它是什么。

为了重建,我们使用下面的代码:

List solution = new ArrayList<>(n);if (elementMatrix[n][k].isExists()) {  int i = n; int j = k; while (j > 0 && i > 0) { if (elementMatrix[i][j].isIncludes()) { solution.add(i); j = j - weights[i]; } i = i - 1; }}System.out.println("The elements with the following indexes are in the solution:" + (solution.toString())); 

输出:

Insert knapsack capacity: 12 Insert number of items: 5 Insert weights: 9 7 4 10 3 true The elements with the following indexes are in the solution: [5, 1]

背包问题的一个简单变化是在没有价值优化的情况下填充背包,但现在每个单独项目的数量无限。

通过对现有代码进行简单调整,可以解决这种变化:

// Old code for simplified knapsack problemelse if (j >= weights[i]) {  if (elementMatrix[i - 1][j - weights[i]].isExists()) { elementMatrix[i][j].setExists(true); elementMatrix[i][j].setIncludes(true); }}// New code, note that we're searching for a solution in the same// row (i-th row), which means we're looking for a solution that// already has some number of i-th elements (including 0) in it's solutionelse if (j >= weights[i]) {  if (elementMatrix[i][j - weights[i]].isExists()) { elementMatrix[i][j].setExists(true); elementMatrix[i][j].setIncludes(true); }}

5. 传统的背包问题

利用以前的两种变体,现在让我们来看看传统的背包问题,看看它与简化版本的不同之处:

Given a set of items, each with a weight w1, w2... and a value v1, v2... determine the number of each item to include in a collection so that the total weight is less than or equal to a given limit k and the total value is as large as possible.

在简化版中,每个解决方案都同样出色。但是,现在我们有一个找到最佳解决方案的标准(也就是可能的最大值)。请记住,这次我们每个项目都有无限数量,因此项目可以在解决方案中多次出现。

在实现中,我们将使用旧的类Element,其中添加了私有字段value,用于存储给定子问题的最大可能值:

public class Element {  private boolean exists; private boolean includes; private int value; // appropriate constructors, getters and setters}

实现非常相似,唯一的区别是现在我们必须根据结果值选择最佳解决方案:

public static void main(String[] args) {  // Same code as before with the addition of the values[] array System.out.println("Insert values: "); int[] values = new int[n + 1]; for (int i=1; i <= n; i++) { values[i] = scanner.nextInt(); } Element[][] elementMatrix = new Element[n + 1][k + 1]; // A matrix that indicates how many newest objects are used // in the optimal solution. // Example: contains[5][10] indicates how many objects with // the weight of W[5] are contained in the optimal solution // for a knapsack of capacity K=10 int[][] contains = new int[n + 1][k + 1]; elementMatrix[0][0] = new Element(0); for (int i = 1; i <= n; i++) { elementMatrix[i][0] = new Element(0); contains[i][0] = 0; } for (int i = 1; i <= k; i++) { elementMatrix[0][i] = new Element(0); contains[0][i] = 0; } for (int i = 1; i <= n; i++) { for (int j = 0; j <= k; j++) { elementMatrix[i][j] = new Element(elementMatrix[i - 1][j].getValue()); contains[i][j] = 0; elementMatrix[i][j].setIncludes(false); elementMatrix[i][j].setValue(M[i - 1][j].getValue()); if (j >= weights[i]) { if ((elementMatrix[i][j - weights[i]].getValue() > 0 || j == weights[i])) { if (elementMatrix[i][j - weights[i]].getValue() + values[i] > M[i][j].getValue()) { elementMatrix[i][j].setIncludes(true); elementMatrix[i][j].setValue(M[i][j - weights[i]].getValue() + values[i]); contains[i][j] = contains[i][j - weights[i]] + 1; } } } System.out.print(elementMatrix[i][j].getValue() + "/" + contains[i][j] + " "); } System.out.println(); } System.out.println("Value: " + elementMatrix[n][k].getValue());}

输出:

Insert knapsack capacity: 12 Insert number of items: 5 Insert weights: 9 7 4 10 3 Insert values: 1 2 3 4 5 0/0 0/0 0/0 0/0 0/0 0/0 0/0 0/0 0/0 1/1 0/0 0/0 0/0 0/0 0/0 0/0 0/0 0/0 0/0 0/0 2/1 0/0 1/0 0/0 0/0 0/0 0/0 0/0 0/0 0/0 3/1 0/0 0/0 2/0 6/2 1/0 0/0 5/1 9/3 0/0 0/0 0/0 0/0 3/0 0/0 0/0 2/0 6/0 1/0 4/1 5/0 9/0 0/0 0/0 0/0 5/1 3/0 0/0 10/2 8/1 6/0 15/3 13/2 11/1 20/4 Value: 20 

6. Levenshtein Distance

另一个使用动态规划的非常好的例子是Edit Distance或Levenshtein Distance。

Levenshtein Distance就是两个字符串A,B,我们需要使用原子操作将A转换为B:

  1. 字符串删除
  2. 字符串插入
  3. 字符替换(从技术上讲,它不止一个操作,但为了简单起见,我们称之为原子操作)

这个问题是通过有条理地解决起始字符串的子串的问题来处理的,逐渐增加子字符串的大小,直到它们等于起始字符串。

我们用于此问题的递归关系如下:

a9b049e29891b6373d116e7e28e6dac7.png

如果a == b则c(a,b)为0,如果a = = b则c(a,b)为1。

实现:

public class editDistance {  public static void main(String[] args) { String s1, s2; Scanner scanner = new Scanner(System.in); System.out.println("Insert first string:"); s1 = scanner.next(); System.out.println("Insert second string:"); s2 = scanner.next(); int n, m; n = s1.length(); m = s2.length(); // Matrix of substring edit distances // example: distance[a][b] is the edit distance // of the first a letters of s1 and b letters of s2 int[][] distance = new int[n + 1][m + 1]; // Matrix initialization: // If we want to turn any string into an empty string // the fastest way no doubt is to just delete // every letter individually. // The same principle applies if we have to turn an empty string // into a non empty string, we just add appropriate letters // until the strings are equal. for (int i = 0; i <= n; i++) { distance[i][0] = i; } for (int j = 0; j <= n; j++) { distance[0][j] = j; } // Variables for storing potential values of current edit distance int e1, e2, e3, min; for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { e1 = distance[i - 1][j] + 1; e2 = distance[i][j - 1] + 1; if (s1.charAt(i - 1) == s2.charAt(j - 1)) { e3 = distance[i - 1][j - 1]; } else { e3 = distance[i - 1][j - 1] + 1; } min = Math.min(e1, e2); min = Math.min(min, e3); distance[i][j] = min; } } System.out.println("Edit distance of s1 and s2 is: " + distance[n][m]); }}

输出:

Insert first string: man Insert second string: machine Edit distance of s1 and s2 is: 3 

如果你想了解更多关于Levenshtein Distance的解决方案,我们在另外的一篇文章中用python实现了 Levenshtein Distance and Text Similarity in Python,

使用这个逻辑,我们可以将许多字符串比较算法归结为简单的递归关系,它使用Levenshtein Distance的基本公式

7. 最长共同子序列(LCS)

这个问题描述如下:

Given two sequences, find the length of the longest subsequence present in both of them. A subsequence is a sequence that appears in the same relative order, but not necessarily contiguous.

给定两个序列,找到两个序列中存在的最长子序列的长度。子序列是以相同的相对顺序出现的序列,但不一定是连续的.

阐明:

如果我们有两个字符串s1="MICE"和s2="MINCE

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

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

相关文章

分布式光伏补贴_光伏发电上网电价政策综述

原标题&#xff1a;光伏发电上网电价政策综述近些年&#xff0c;光伏发电上网电价相关政策不断推陈出新&#xff0c;在推进平价上网的同时&#xff0c;也积极对接了电力市场建设需要。本文梳理了光伏发电上网电价及补贴标准有关政策&#xff0c;供参考使用&#xff1a;1.普通光…

单片机float数发给上位机_上位机倒计时器

1. 设置窗口在VS2019新建一个Windows窗体应用(.NET Framework)项目&#xff0c;新建之后出现一个可视化页面(From1.cs[设计])设置窗口。在左侧的工具箱里面拉拽控件到操作面板上&#xff0c;按照自己的喜好摆放位置和调大小。这里要注意的是用到的分别是什么控件&#xff1a;文…

土地利用覆被变化的概念_欠压实概念极其荒谬

●李传亮地表疏松的沉积物&#xff0c;孔隙度极高&#xff0c;有时可高达80%以上&#xff0c;但随着埋藏深度的加大&#xff0c;在上覆压力的作用下&#xff0c;其排列方式不断由松散排列趋于致密排列&#xff0c;同时伴随有孔隙度的大幅度减小&#xff0c;此即岩石的压实阶段&…

java足球游戏毕业设计,java毕业设计_springboot框架的校园足球管理平台

今天介绍一个java毕设题目, 题目内容为springboot框架的校园足球管理平台, 是一个采用b/s结构的javaweb项目, 采用java语言编写开发工具eclipse, 项目框架jspspringbootmybatis, 校园足球管理平台的信息存储于mysql中, 并基于mybatis进行了orm封装, 该校园足球管理平台系统通过…

Java 11功能– Java飞行记录器

在本文中&#xff0c;我们将看到如何利用Java Flight Recorder功能作为Java 11的一部分。之前&#xff0c;它是商业功能之一。 但是&#xff0c;对于带有JEP 328的 Java 11&#xff0c;它是开源的。 Java Flight Recorder将OS和JVM事件记录到一个文件中&#xff0c;可以使用Jav…

qstandarditemmodel 重写data函数后无法实现拖拽_实现类似百度网盘上传的功能

今天写到了上传文件部分&#xff0c;记录一下。前端使用了dropzone.js(用于实现文件上传的JS库)&#xff0c;并对此文件进行了细微修改。说一下思路吧&#xff1a;首先界面长这样&#xff1a;点击上传文件&#xff0c;需要弹出一个对话框&#xff0c;对话框中有支持拖拽文件的文…

逻辑斯蒂回归_逻辑斯蒂回归详细解析 | 统计学习方法学习笔记 | 数据分析 | 机器学习...

本文包括&#xff1a;重要概念逻辑斯蒂回归和线性回归二项逻辑斯谛回归模型逻辑斯蒂回顾与几率模型参数估计多项逻辑斯谛回归其它有关数据分析&#xff0c;机器学习的文章及社群1.重要概念&#xff1a;在正式介绍逻辑斯蒂回归模型之前&#xff0c;需要先对一些基本概念有所了解…

交通流预测 | Matlab基于KNN-BiLSTM的交通流预测(对比SVR、LSTM、GRU、KNN-LSTM)

目录 预测效果基本介绍程序设计参考资料 预测效果 基本介绍 交通流预测 | Matlab基于KNN-BiLSTM的交通流预测&#xff08;对比SVR、LSTM、GRU、KNN-LSTM&#xff09; 程序设计 完整程序和数据获取方式&#xff1a;私信博主回复Matlab基于KNN-BiLSTM的交通流预测&#xff08;对…

JMetro“ Metro”选项卡,Java的TreeView和ContextMenu(JavaFX)

我刚刚发布了JMetro的新更新&#xff0c;将版本升级到3.3.0。 在此更新中&#xff0c;您将找到&#xff1a; 新的TreeView Fluent设计系统&#xff08;Metro&#xff09;启发风格&#xff1b; 新的Tabs和TabPane FDS启发风格&#xff1b; 新的上下文菜单采用深色FDS启发的样…

数据挖掘原理与算法_技术分享|大数据挖掘算法之FPGrowth算法

程一舰数据技术处我们常说我们生活在信息时代&#xff0c;实际上&#xff0c;我们更多的还是生活在数据时代。因为从过去到现在累积了大量的数据&#xff0c;对数据的挖掘和分析也仅是从最近几年大数据和人工智能技术的发展而兴起。我们对现有数据价值的利用还远低于数据本身拥…

neo4j数据浏览器_Neo4j:在Neo4j浏览器的帮助下探索新数据集

neo4j数据浏览器在查看新的Neo4j数据库时&#xff0c;我发现遇到的一件困难就是确定其中包含的数据的结构。 我习惯于关系数据库&#xff0c;在该数据库中您可以轻松获取表列表和外键&#xff0c;以便您将它们相互连接。 传统上&#xff0c;使用Neo4j时很难做到这一点&#x…

mfc中怎么集成文件_怎么把几个pdf合并并打印在一份文件中?

a为什么我们要把pdf文件合并呢&#xff1f;当然我们要知道什么是pdf文件才能进行更多的操作&#xff0c;Pdf是我们许多人都会用到的一种存储文件的格式&#xff0c;它的内容基本是以图像的形式表现&#xff0c;所以我们在播放和打印的时候可以不用担心出现格式错乱的问题。PDF格…

php中日期选择代码,实现JS日期时间选择器

这次给大家带来实现JS日期时间选择器&#xff0c;实现JS日期时间选择器的注意事项有哪些&#xff0c;下面就是实战案例&#xff0c;一起来看一下。flatpickr 是一个轻量级、注重精益、由 UX 驱动和可扩展的 JavaScript 日期时间选择器。下载 演示 GitHubflatpickr 不依赖于任何…

php steam 第三方登录,Steam第三方登录

Steam第三方登录Laravel PHP7.3前端登录界面if(isset($steamprofile))window.οnlοadfunction(){var steamprofile {!!$steamprofile!!};var steamid {{$steamid}};if(steamid){$.post(/ajax/SocilaAuthLogin,{data:{userdata:steamprofile,type:steamLogin},},function(dat…

setitimer 创建两个定时器_UE4 Timer(定时器)相关源码分析

前言文章属于旧有文章搬运, 之前在csdn上面2019.11.6修改Timer是定时器, 用途:延时执行事件周期性执行事件参考官方文档Using TimersTimer可以...Timer能...Timer...... 编不下去了, 自己百度谷歌吧(手动狗头)TimerManagerTimer的管理类是FTimerManager, 是一个全局的Timer管理…

is array php,PHP 源码 — is_array 函数源码分析

php 中的 is_arrayphp 中的 is_array&#xff0c;它的签名是 is_array ( mixed $var ) : bool实现的源码在\ext\standard\type.c中可以找到PHP_FUNCTION(is_array)所处的位置&#xff0c;大概位于 273 行。在 PHP 中&#xff0c;这个系列的函数&#xff0c;是由很多个&#xff…

apache mahout_使用Apache Mahout创建在线推荐系统

apache mahout最近&#xff0c; 我们一直在为Yap.TV实施推荐系统&#xff1a;您可以在安装应用程序并转到“ Just for you”标签后才能看到它的运行情况。 我们以Apache Mahout为基础进行建议。 Mahout是一个“可扩展的机器学习库”&#xff0c;包含使用协作过滤算法的基于用户…

Java应用程序的简单令牌认证

“我喜欢编写身份验证和授权代码。” 〜从来没有Web开发人员。 厌倦了一次又一次地建立相同的登录屏幕&#xff1f; 尝试使用Okta API进行托管身份验证&#xff0c;授权和多因素身份验证。 JSON Web令牌已Swift成为保护Web应用程序安全的标准&#xff0c;并取代了Cookie和会话…

matlab中rb代表什么意思,你知道“川藏线上”女游客举着“求RB”的牌子是啥意思吗?暗语啊!...

现在穷游好像一种旅行圈的时髦&#xff0c;很多人都想着穷游能够锻炼人的意志&#xff0c;能够促进人与人的交流与合作&#xff0c;能够在路上遇到不一样的精彩&#xff0c;能够发现人性与真善美。现在穷游的人不在少数&#xff0c;尤其是在川藏线上&#xff0c;想要穷游入藏的…

华为主题锁屏壁纸换不掉_华为手机总多出莫名的照片?那是因为这3个设置没关闭,赶紧自查...

随着华为在科技、5G以及数码领域的表现越来越好&#xff0c;使用华为手机的人也越来越多。不知道你在使用华为手机的过程中&#xff0c;有没有这样的感觉&#xff0c;华为手机总是多出莫名其妙的照片&#xff0c;即使删除了下次还会出现。如果有这样的情况&#xff0c;可能是手…