1. 介绍

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

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

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

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

2. 什么是动态规划?

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

为了理解动态规划,我们首先需要理解递归关系的问题。每个单独的复杂问题可以被划分为很小的子问题,这表示我们可以在这些问题之间构造一个递归关系。
让我们来看一个我们所熟悉的例子:斐波拉契数列,斐波拉契数列的定义具有以下的递归关系:

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

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

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

例如:如果我们想计算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)维的矩阵MM [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 = trueM [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 = False
elif (k-W[i]>=0):
if(M[i-1][k-W[i]].exists == true):
M[i][k].exists = True
M[i][k].includes = True
else:
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<Integer> 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:\n" + (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 problem
else 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 solution
else 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

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

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

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

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

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

如果a == bc(a,b)为0,如果a = = bc(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",最长的共同子序列是MI或者CE。但是,最长的公共子序列将是“MICE”,因为结果子序列的元素不必是连续的顺序。

递归关系与一般逻辑:

我们可以看到,Levenshtein distanceLCS之间只有微小的差别,特别是移动成本。

LCS中,我们没有字符插入和字符删除的成本,这意味着我们只计算字符替换(对角线移动)的成本,如果两个当前字符串字符a [i]b [j] 是相同的,则成本为1。

LCS的最终成本是2个字符串的最长子序列的长度,这正是我们所需要的。

Using this logic, we can boil down a lot of string comparison algorithms to simple recurrence relations which utilize the base formula of the Levenshtein distance

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

实现:

public class LCS {
public static void main(String[] args) {
String s1 = new String("Hillfinger");
String s2 = new String("Hilfiger");
int n = s1.length();
int m = s2.length();
int[][] solutionMatrix = new int[n+1][m+1];
for (int i = 0; i < n; i++) {
solutionMatrix[i][0] = 0;
}
for (int i = 0; i < m; i++) {
solutionMatrix[0][i] = 0;
} for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int max1, max2, max3;
max1 = solutionMatrix[i - 1][j];
max2 = solutionMatrix[i][j - 1];
if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
max3 = solutionMatrix[i - 1][j - 1] + 1;
} else {
max3 = solutionMatrix[i - 1][j - 1];
}
int tmp = Math.max(max1, max2);
solutionMatrix[i][j] = Math.max(tmp, max3);
}
} System.out.println("Length of longest continuous subsequence: " + solutionMatrix[n][m]);
}
}

输出:

Length of longest continuous subsequence: 8

8.利用动态规划的其他问题

利用动态规划可以解决很多问题,下面列举了一些:

  1. 分区问题:给定一组整数,找出它是否可以分成两个具有相等和的子集
  2. 子集和问题:给你一个正整数的数组及元素还有一个合计值,是否在数组中存在一个子集的的元素之和等于合计值。
  3. 硬币变化问题:鉴于给定面额的硬币无限供应,找到获得所需变化的不同方式的总数
  4. k变量线性方程的所有可能的解:给定k个变量的线性方程,计算它的可能解的总数
  5. 找到醉汉不会从悬崖上掉下来的概率:给定一个线性空间代表距离悬崖的距离,让你知道酒鬼从悬崖起始的距离,以及他向悬崖p前进并远离悬崖1-p的倾向,计算出他的生存概率

9.结论

动态编程是一种工具,可以节省大量的计算时间,以换取更大的空间复杂性,这在很大程度上取决于您正在处理的系统类型,如果CPU时间很宝贵,您选择耗费内存的解决方案,另一方面,如果您的内存有限,则选择更耗时的解决方案。

作者: Vladimir Batoćanin

译者:lee

Java动态规划的更多相关文章

  1. java动态规划取硬币问题

    最近一直在研究动态规划的问题.今天遇到了取硬币问题. 其实动态规划还是,我从底部向顶部,依次求出每个状态的最小值,然后就可以标记上. 这道题目就是,假如有1,5,7,10这四种币值的硬币,我取14元, ...

  2. java动态规划问题

    这里是简单的动态规划问题.其实,如果我们学过数据结构,应该就接触过动态规划问题,当时一直没有反应过来.我们求最小生成树用的是贪婪算法.而求最短路径就是动态规划.从一个点出发,到另外每个点的最短距离.在 ...

  3. java 动态规划解决最大连续子数列和

    很多动态规划算法非常像数学中的递推.我们如果能找到一个合适的递推公式,就能很容易的解决问题.我们用dp[n]表示以第n个数结尾的最大连续子序列的和,这里第n个数必须在子序列中.于是存在以下递推公式: ...

  4. java动态规划导弹问题

    这是一道动态规划题,和昨天的取硬币还有最长公共字串有点类似. 1.题目描述:                        某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一 ...

  5. Java动态规划实现最短路径问题

    问题描述 给定一个加权连通图(无向的或有向的),要求找出从每个定点到其他所有定点之间的最短路径以及最短路径的长度. 2.1 动态规划法原理简介 动态规划算法通常用于求解具有某种最优性质的问题.在这类问 ...

  6. 划分问题(Java 动态规划)

    Description 给定一个正整数的集合A={a1,a2,-.,an},是否可以将其分割成两个子集合,使两个子集合的数加起来的和相等.例A = { 1, 3, 8, 4, 10} 可以分割:{1, ...

  7. 算法:矩阵连乘(Java)动态规划

    Description 给你2个矩阵A.B,我们使用标准的矩阵相乘定义C=AB如下: A数组中栏(column)的数目一定要等于B数组中列(row)的数目才可以做此2数组的相乘.若我们以rows(A) ...

  8. java 动态规划解决上楼梯问题

    问题描述: 你正在爬楼梯. 它需要n步才能达到顶峰. 每次你可以爬1或2步. 您可以通过多少不同的方式登顶? 注意:给定n将是一个正整数. Example 1: Input: 2 Output: 2 ...

  9. [leetcode] 45. 跳跃游戏 II(Java)(动态规划)

    45. 跳跃游戏 II 动态规划 此题可以倒着想. 看示例: [2,3,1,1,4] 我们从后往前推,对于第4个数1,跳一次 对于第3个数1,显然只能跳到第4个数上,那么从第3个数开始跳到最后需要两次 ...

随机推荐

  1. Struts2——(4)OGNL与struts标签

    一.OGNL Object Graphic Navigation Language  对象图导航语言 依赖于 ognl.jar包 OGNL不是Struts框架独有的,它是和框架独立的一种技术. 例如: ...

  2. git版本管理与github

    1.下载并安装好git     里面有几个.exe的文件,需要用到的就是git-bash.exe  2.把git和github连接     1.打开github网站,点击最右上角的图标,点击setti ...

  3. 关于js的window.open()

    window.open是javascript函数,该函数的作用是打开一个新窗口或这改变原来的窗口,不过一般用来的是打开新窗口,因为修改原来的网页地址,可以有另一个函数,那就是window.locati ...

  4. Android UI法宝的发展Android Action Bar Style Generator

    ActionBar它是3.0经UI设计规格.同时它是Google设计风格强烈推荐,如何做一个高速设计的眼睛ActionBar之.进一步,我们设置了阶段为一个入眼ActionBar模板吧,然后,Andr ...

  5. WPF的消息机制(一)- 让应用程序动起来

    原文:WPF的消息机制(一)- 让应用程序动起来 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/powertoolsteam/article/det ...

  6. CentOSserverMysql主从复制集群结构

    在配置Mysql数据库主从复制集群时间,以确保: 1.主从server操作系统版本号和位数一致. 2.Mysql版本号一致. 为了保证稳定性,最好server操作系统和Mysql数据库环境一致. Ce ...

  7. XenServer重置存储管理SR至Ext3

    Xenserver默认安装到本地磁盘,它是基于本地磁盘上Linux的LVM所管理,在xenserver创建为LVM Typer的本地SR,xenserver在LVM上不支持Thin Provision ...

  8. 学习git命令

    1.git init @创建仓库 2.git add  filename @添加文件到缓存区 3.git commit -m"注释说明"   @提交修改内容 4.git statu ...

  9. matlab 可变参数与默认参数设置

    1. 基本思路 矩阵矢量化编程,而不是循环和遍历: GPU 并行计算: 使用稀疏矩阵: 2. 实践 可变长输入参数,输出参数,需要解析(使用大括号进行索引): varargin varargout 函 ...

  10. 解决Ubuntu14.04在外接显示器不能指定问题的最佳分辨率

    通常这种情况发生.在System Settings -> display 你会发现多出了一个 unknown display. 这往往是因为你使用质量低劣的视频电缆,例如,几美元VGA线. 解决 ...