chore: add 4.3.4 文本优化
This commit is contained in:
@@ -265,7 +265,7 @@ solution = backtrack(dict())
|
|||||||
print(solution)
|
print(solution)
|
||||||
```
|
```
|
||||||
|
|
||||||
使用命令`pip install python-constraint`安装constraint库
|
使用命令`pip install python-constraint`安装 constraint 库
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from constraint import *
|
from constraint import *
|
||||||
@@ -308,14 +308,15 @@ for solution in problem.getSolutions():
|
|||||||
2. Steepest-ascent hill-climbing,每次都从相同的初始状态开始
|
2. Steepest-ascent hill-climbing,每次都从相同的初始状态开始
|
||||||
3. Stochastic hill-climbing,每次从不同的初始状态开始
|
3. Stochastic hill-climbing,每次从不同的初始状态开始
|
||||||
4. Stochastic hill-climbing,每次都从相同的初始状态开始
|
4. Stochastic hill-climbing,每次都从相同的初始状态开始
|
||||||
5. 无论是steepest-ascent还是stochastic hill climbing,只要你总是从同一个初始状态开始
|
5. 无论是 steepest-ascent 还是 stochastic hill climbing,只要你总是从同一个初始状态开始
|
||||||
6. 无论是steepest-ascent还是stochastic hill climbing,只要每次都从不同的初始状态开始
|
6. 无论是 steepest-ascent 还是 stochastic hill climbing,只要每次都从不同的初始状态开始
|
||||||
7. 没有任何版本的爬山算法能保证每次都能得到相同的解决方案
|
7. 没有任何版本的爬山算法能保证每次都能得到相同的解决方案
|
||||||
|
|
||||||
2. 下面两个问题都会问你关于下面描述的优化问题。
|
2. 下面两个问题都会问你关于下面描述的优化问题。
|
||||||
一位农民正在尝试种植两种作物,作物1和作物2,并希望实现利润最大化。农民将从种植的每英亩作物1中获得500美元的利润,从种植的每英亩作物2中获得400美元的利润。然而,农民今天需要在早上7点到晚上7点之间的12个小时内完成所有的种植。种植一英亩的作物1需要3个小时,种植一英亩作物2需要2个小时。农民在供应方面也很有限:他有足够的供应种植10英亩的作物1,有足够的资源种植4英亩的作物2。假设变量C1表示要种植的作物1的英亩数,变量C2表示要种植作物2的英亩数。
|
一位农民正在尝试种植两种作物,`作物 1` 和`作物 2`,并希望实现利润最大化。农民将从种植的每英亩`作物 1` 中获得 500 美元的利润,从种植的每英亩`作物 2` 中获得 400 美元的利润。然而,农民今天需要在早上 7 点到晚上 7 点之间的 12 个小时内完成所有的种植。种植一英亩的`作物 1` 需要 3 个小时,种植一英亩`作物 2` 需要 2 个小时。农民在供应方面也很有限:他有足够的供应种植 10 英亩的`作物 1`,有足够的资源种植 4 英亩的`作物 2`。假设变量 C1 表示要种植的`作物 1` 的英亩数,变量 C2 表示要种植`作物 2` 的英亩数。
|
||||||
|
|
||||||
对于这个问题,什么是有效的目标函数?
|
对于这个问题,什么是有效的目标函数?
|
||||||
|
|
||||||
1. 10 \* C1 + 4 \* C2
|
1. 10 \* C1 + 4 \* C2
|
||||||
2. -3 \* C1 - 2 \* C2
|
2. -3 \* C1 - 2 \* C2
|
||||||
3. 500 \* 10 \* C1 + 400 \* 4 \* C2
|
3. 500 \* 10 \* C1 + 400 \* 4 \* C2
|
||||||
@@ -330,10 +331,11 @@ for solution in problem.getSolutions():
|
|||||||
|
|
||||||
4. 下面的问题将问你以下考试安排约束满足图,其中每个节点代表一个课程。每门课程都与可能的考试日的初始域相关联(大多数课程可能在周一、周二或周三;少数课程已经被限制在一天内)。两个节点之间的边意味着这两个课程必须在不同的日子进行考试。
|
4. 下面的问题将问你以下考试安排约束满足图,其中每个节点代表一个课程。每门课程都与可能的考试日的初始域相关联(大多数课程可能在周一、周二或周三;少数课程已经被限制在一天内)。两个节点之间的边意味着这两个课程必须在不同的日子进行考试。
|
||||||
|
|
||||||
在对整个问题满足弧一致性之后,变量C、D和E的结果域是什么?
|
在对整个问题满足弧一致性之后,变量 C、D 和 E 的结果域是什么?
|
||||||
1. C的域是\{Mon,Tue\},D的域是\{Wed\},E的域是\{Mon\}
|
|
||||||
2. C的域是\{Mon\},D的域是\{Wed\},E的域为\{Tue\}
|
1. C 的域是\{Mon,Tue\},D 的域是\{Wed\},E 的域是\{Mon\}
|
||||||
3. C的域是\{Mon\},D的域是\{Tue\},E的域为\{Wed\}
|
2. C 的域是\{Mon\},D 的域是\{Wed\},E 的域为\{Tue\}
|
||||||
4. C的域是\{Mon\},D的域是\{Mon,Wed\},E的域是\{Tue,Wed\}
|
3. C 的域是\{Mon\},D 的域是\{Tue\},E 的域为\{Wed\}
|
||||||
5. C的域是\{Mon,Tue,Wed\},D的域是\{Mon,Wed\},E的域是\{Mon,Tue,Wed\}
|
4. C 的域是\{Mon\},D 的域是\{Mon,Wed\},E 的域是\{Tue,Wed\}
|
||||||
6. C的域是\{Mon\},D的域是\{Mon,Wed\},E的域是\{Mon,Tue,Wed\}
|
5. C 的域是\{Mon,Tue,Wed\},D 的域是\{Mon,Wed\},E 的域是\{Mon,Tue,Wed\}
|
||||||
|
6. C 的域是\{Mon\},D 的域是\{Mon,Wed\},E 的域是\{Mon,Tue,Wed\}
|
||||||
|
|||||||
@@ -12,63 +12,65 @@
|
|||||||
|
|
||||||
编写一个人工智能来完成填词游戏。
|
编写一个人工智能来完成填词游戏。
|
||||||
|
|
||||||
```txt
|
能够实现将文字转换为图片。
|
||||||
|
|
||||||
|
```shell
|
||||||
$ python generate.py data/structure1.txt data/words1.txt output.png
|
$ python generate.py data/structure1.txt data/words1.txt output.png
|
||||||
██████████████
|
|█|█|█|█|█|█|█|█|█|█|█|█|█|█|
|
||||||
███████M████R█
|
|█|█|█|█|█|█|█|M|█|█|█|█|R|█|
|
||||||
█INTELLIGENCE█
|
|█|I|N|T|E|L|L|I|G|E|N|C|E|█|
|
||||||
█N█████N████S█
|
|█|N|█|█|█|█|█|N|█|█|█|█|S|█|
|
||||||
█F██LOGIC███O█
|
|█|F|█|█|L|O|G|I|C|█|█|█|O|█|
|
||||||
█E█████M████L█
|
|█|E|█|█|█|█|█|M|█|█|█|█|L|█|
|
||||||
█R███SEARCH█V█
|
|█|R|█|█|█|S|E|A|R|C|H|█|V|█|
|
||||||
███████X████E█
|
|█|█|█|█|█|█|█|X|█|█|█|█|E|█|
|
||||||
██████████████
|
|█|█|█|█|█|█|█|█|█|█|█|█|█|█|
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 背景
|
## 背景
|
||||||
|
|
||||||
- 你如何生成一个填字游戏?考虑到填字游戏的结构(即网格中哪些方格需要填入字母),以及要使用的单词列表,问题就变成了选择哪些单词应该填入每个垂直或水平的方格序列。我们可以将这种问题建模为一个约束满足问题。每一个方格序列都是一个变量,我们需要决定它的值(在可能的单词域中哪个单词将被填入该序列)。考虑一下下面的字谜结构。
|
- 你如何生成一个填字游戏?考虑到填字游戏的结构 (即网格中哪些方格需要填入字母),以及要使用的单词列表,问题就变成了选择哪些单词应该填入每个垂直或水平的方格序列。我们可以将这种问题建模为一个约束满足问题。每一个方格序列都是一个变量,我们需要决定它的值 (在可能的单词域中哪个单词将被填入该序列)。考虑一下下面的字谜结构。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- 在这个结构中,我们有四个变量,代表了我们需要填入这个字谜的四个单词(在上图中每个单词都用数字表示)。每个变量由四个值定义:它开始的行(`i`值),它开始的列(`j`值),单词的方向(纵向或横向down or across),以及单词的长度。例如,`变量1`将是一个由第1行(假设从顶部计数的0索引)、第1列(假设从左边计数的0索引)、方向为`across`和`4`的长度表示的变量。
|
- 在这个结构中,我们有四个变量,代表了我们需要填入这个字谜的四个单词 (在上图中每个单词都用数字表示)。每个变量由四个值定义:它开始的行 (`i`值),它开始的列 (`j`值),单词的方向 (纵向或横向 down or across),以及单词的长度。例如,`变量1`将是一个由第 1 行 (假设从顶部计数的 0 索引)、第 1 列 (假设从左边计数的 0 索引)、方向为`across`和`4`的长度表示的变量。
|
||||||
|
|
||||||
- 与许多约束满足问题一样,这些变量有一元和二元约束。变量的一元约束是由其长度决定的。例如,对于`变量1`来说,数值`BYTE`可以满足一元约束,但是数值`BIT`则不能(它有错误的字母数量)。因此,任何不满足变量的一元约束的值都可以立即从变量的域中删除。
|
- 与许多约束满足问题一样,这些变量有一元和二元约束。变量的一元约束是由其长度决定的。例如,对于`变量1`来说,数值`BYTE`可以满足一元约束,但是数值`BIT`则不能 (它有错误的字母数量)。因此,任何不满足变量的一元约束的值都可以立即从变量的域中删除。
|
||||||
|
|
||||||
- 变量的二元约束是由其与相邻变量的重合度给出的。`变量1`有一个邻居:`变量2`。`变量2`有两个邻居:`变量1`和`变量3`。对于每一对相邻的变量,这些变量都有一个重叠部分:一个它们共同的单方块。我们可以将这种重叠表示为每个变量有用相同字符位置的索引对。例如,`变量1`和`变量2`之间的重叠可以表示为一对`(1, 0)`,这意味着`变量1`在索引1处的字符必然与`变量2`在索引0处的字符相同(再次假设索引从0开始)。因此,`变量2`和`变量3`之间的重叠将被表示为一对`(3, 1)`,`变量2`的值的字符`3`必须与`变量3`的值的字符`1`相同。
|
- 变量的二元约束是由其与相邻变量的重合度给出的。`变量1`有一个邻居:`变量2`。`变量2`有两个邻居:`变量1`和`变量3`。对于每一对相邻的变量,这些变量都有一个重叠部分:一个它们共同的单方块。我们可以将这种重叠表示为每个变量有用相同字符位置的索引对。例如,`变量1`和`变量2`之间的重叠可以表示为一对`(1, 0)`,这意味着`变量1`在索引 1 处的字符必然与`变量2`在索引 0 处的字符相同 (再次假设索引从 0 开始)。因此,`变量2`和`变量3`之间的重叠将被表示为一对`(3, 1)`,`变量2`的值的字符`3`必须与`变量3`的值的字符`1`相同。
|
||||||
|
|
||||||
- 对于这个问题,我们将增加一个额外的约束条件,即所有的单词必须是不同的:同一个单词不应该在谜题中重复出现多次。
|
- 对于这个问题,我们将增加一个额外的约束条件,即所有的单词必须是不同的:同一个单词不应该在谜题中重复出现多次。
|
||||||
|
|
||||||
- 那么,接下来的挑战是写一个程序来找到一个满意的赋值:为每个变量提供一个不同的词(来自给定的词汇表),从而满足所有的一元和二元约束。
|
- 那么,接下来的挑战是写一个程序来找到一个满意的赋值:为每个变量提供一个不同的词 (来自给定的词汇表),从而满足所有的一元和二元约束。
|
||||||
理解
|
理解
|
||||||
|
|
||||||
- 这个项目中有两个Python文件:`crossword.py`和`generate.py`。第一个文件已经完全为你写好了,第二个文件有一些函数留给你去实现。
|
- 这个项目中有两个 Python 文件:`crossword.py`和`generate.py`。第一个文件已经完全为你写好了,第二个文件有一些函数留给你去实现。
|
||||||
|
|
||||||
- 首先,让我们看一下`crossword.py`。这个文件定义了两个类,`Variable`(代表填字游戏中的变量)和`Crossword`(代表填字游戏本身)。
|
- 首先,让我们看一下`crossword.py`。这个文件定义了两个类,`Variable`(代表填字游戏中的变量) 和`Crossword`(代表填字游戏本身)。
|
||||||
|
|
||||||
- 注意,要创建一个变量,我们必须指定四个值:它的第`i`行,第`j`列,它的方向(常数`Variable.ACROSS`或常数`Variable.DOWN``),以及它的长度(`length``)。
|
- 注意,要创建一个变量,我们必须指定四个值:它的第`i`行,第`j`列,它的方向 (常数`Variable.ACROSS`或常数`Variable.DOWN``),以及它的长度(`length``)。
|
||||||
|
|
||||||
- 字谜类需要两个值来创建一个新的字谜:一个定义了字谜结构的`structure_file`(`_`用来代表空白单元格,任何其他字符都代表不会被填入的单元格)和一个定义了字词列表(每行一个)的`word_file`,用来作为游戏的词汇表。这些文件的三个例子可以在项目的数据目录中找到,也欢迎你自己创建。
|
- 字谜类需要两个值来创建一个新的字谜:一个定义了字谜结构的`structure_file`(`_`用来代表空白单元格,任何其他字符都代表不会被填入的单元格) 和一个定义了字词列表 (每行一个) 的`word_file`,用来作为游戏的词汇表。这些文件的三个例子可以在项目的数据目录中找到,也欢迎你自己创建。
|
||||||
|
|
||||||
- 特别要注意的是,对于任何一个字谜对象的字谜,我们都会存储以下的数值:
|
- 特别要注意的是,对于任何一个字谜对象的字谜,我们都会存储以下的数值:
|
||||||
- `crossword.height`是一个整数,代表填字游戏的高度。
|
- `crossword.height`是一个整数,代表填字游戏的高度。
|
||||||
- `crossword.width`是一个整数,代表填字游戏的宽度。
|
- `crossword.width`是一个整数,代表填字游戏的宽度。
|
||||||
- `crossword.structure`是一个二维列表,代表字谜的结构。对于任何有效的第i行和第j列,如果该单元格是空白的,`crossword.structure[i][j]`将为真(必须在该单元格中填入一个字符),否则将为假(该单元格中没有字符要填)。
|
- `crossword.structure`是一个二维列表,代表字谜的结构。对于任何有效的第 i 行和第 j 列,如果该单元格是空白的,`crossword.structure[i][j]`将为真 (必须在该单元格中填入一个字符),否则将为假 (该单元格中没有字符要填)。
|
||||||
- `crossword.words`是一个包含所有单词的集合,在构建填字游戏的时候,可以从这些单词中提取。
|
- `crossword.words`是一个包含所有单词的集合,在构建填字游戏的时候,可以从这些单词中提取。
|
||||||
- `crossword.variables`是谜题中所有变量的集合(每个变量都是一个Variable对象)。
|
- `crossword.variables`是谜题中所有变量的集合 (每个变量都是一个 Variable 对象)。
|
||||||
- `crossword.overlaps`是一个字典,它将一对变量映射到它们的重合处。对于任何两个不同的变量v1和v2,如果这两个变量没有重叠,`crossword.overlaps[v1, v2]`将是`None`,如果这两个变量有重叠,则是一对整数`(i, j)`。这对`(i, j)`应被解释为:`v1`的第`i`个字符的值必须与`v2`的第`j`个字符的值相同。
|
- `crossword.overlaps`是一个字典,它将一对变量映射到它们的重合处。对于任何两个不同的变量 v1 和 v2,如果这两个变量没有重叠,`crossword.overlaps[v1, v2]`将是`None`,如果这两个变量有重叠,则是一对整数`(i, j)`。这对`(i, j)`应被解释为:`v1`的第`i`个字符的值必须与`v2`的第`j`个字符的值相同。
|
||||||
|
|
||||||
- `Crossword`对象还支持一个方法`neighbors`,它可以返回与给定变量重叠的所有变量。也就是说,`crossword.neighbors(v1)`将返回一个与变量`v1`相邻的所有变量的集合。
|
- `Crossword`对象还支持一个方法`neighbors`,它可以返回与给定变量重叠的所有变量。也就是说,`crossword.neighbors(v1)`将返回一个与变量`v1`相邻的所有变量的集合。
|
||||||
|
|
||||||
- 接下来,看一下`generate.py`。在这里,我们定义了一个`CrosswordCreator`类,我们将用它来解决填字游戏。当一个`CrosswordCreator`对象被创建时,它得到一个填字游戏的属性,它应该是一个`Crossword`对象(因此具有上面描述的所有属性)。每个`CrosswordCreator`对象还得到一个域属性:一个字典,它将变量映射到该变量可能作为一个值的一组词。最初,这组词是我们词汇表中的所有词,但我们很快就会写函数来限制这些域。
|
- 接下来,看一下`generate.py`。在这里,我们定义了一个`CrosswordCreator`类,我们将用它来解决填字游戏。当一个`CrosswordCreator`对象被创建时,它得到一个填字游戏的属性,它应该是一个`Crossword`对象 (因此具有上面描述的所有属性)。每个`CrosswordCreator`对象还得到一个域属性:一个字典,它将变量映射到该变量可能作为一个值的一组词。最初,这组词是我们词汇表中的所有词,但我们很快就会写函数来限制这些域。
|
||||||
|
|
||||||
- 我们还为你定义了一些函数,以帮助你测试你的代码:`print`将向终端打印你的填字游戏的一个给定的赋值(每个赋值,在这个函数和其他地方,是一个字典,将变量映射到它们相应的词)。同时,`save`将生成一个与给定作业相对应的图像文件(如果你无法使用这个函数,你需要`pip3 install Pillow`)。 `letter_grid`是一个被`print`和`save`使用的辅助函数,它为给定的赋值生成一个所有字符在其适当位置的2D列表:你可能不需要自己调用这个函数,但如果你想的话,欢迎你这样做。
|
- 我们还为你定义了一些函数,以帮助你测试你的代码:`print`将向终端打印你的填字游戏的一个给定的赋值 (每个赋值,在这个函数和其他地方,是一个字典,将变量映射到它们相应的词)。同时,`save`将生成一个与给定作业相对应的图像文件 (如果你无法使用这个函数,你需要`pip3 install Pillow`)。 `letter_grid`是一个被`print`和`save`使用的辅助函数,它为给定的赋值生成一个所有字符在其适当位置的 2D 列表:你可能不需要自己调用这个函数,但如果你想的话,欢迎你这样做。
|
||||||
|
|
||||||
- 最后,注意`solve`函数。这个函数做了三件事:首先,它调用`enforce_node_consistency`来强制执行填字游戏的节点一致性,确保变量域中的每个值都满足一元约束。接下来,该函数调用`ac3`来强制执行弧一致性,确保二元约束得到满足。最后,该函数在最初的空赋值(空字典dict())上调用`backtrack`,试图计算出问题的解决方案。
|
- 最后,注意`solve`函数。这个函数做了三件事:首先,它调用`enforce_node_consistency`来强制执行填字游戏的节点一致性,确保变量域中的每个值都满足一元约束。接下来,该函数调用`ac3`来强制执行弧一致性,确保二元约束得到满足。最后,该函数在最初的空赋值 (空字典 dict()) 上调用`backtrack`,试图计算出问题的解决方案。
|
||||||
|
|
||||||
- 不过,`enforce_node_consistency`、`ac3`和`backtrack`等函数还没有实现(以及其他函数)。这就是你的任务。
|
- 不过,`enforce_node_consistency`、`ac3`和`backtrack`等函数还没有实现 (以及其他函数)。这就是你的任务。
|
||||||
|
|
||||||
## 明确
|
## 明确
|
||||||
|
|
||||||
@@ -78,7 +80,7 @@ $ python generate.py data/structure1.txt data/words1.txt output.png
|
|||||||
- 回顾一下,当对每个变量来说,其域中的每个值都与该变量的一元约束一致时,就实现了节点一致性。在填字游戏的情况下,这意味着要确保变量域中的每个值的字母数与变量的长度相同。
|
- 回顾一下,当对每个变量来说,其域中的每个值都与该变量的一元约束一致时,就实现了节点一致性。在填字游戏的情况下,这意味着要确保变量域中的每个值的字母数与变量的长度相同。
|
||||||
- 要从一个变量`v`的域中移除一个值`x`,因为`self.domains`是一个将变量映射到数值集的字典,你可以调用`self.domains[v].remove(x)`。
|
- 要从一个变量`v`的域中移除一个值`x`,因为`self.domains`是一个将变量映射到数值集的字典,你可以调用`self.domains[v].remove(x)`。
|
||||||
- 这个函数不需要返回值。
|
- 这个函数不需要返回值。
|
||||||
- `revise`函数应该使变量x与变量y保持弧一致性。
|
- `revise`函数应该使变量 x 与变量 y 保持弧一致性。
|
||||||
- `x`和`y`都是`Variable`对象,代表谜题中的变量。
|
- `x`和`y`都是`Variable`对象,代表谜题中的变量。
|
||||||
- 回顾一下,当`x`的域中的每一个值在`y`的域中都有一个不引起冲突的可能值时,`x`就与`y`保持弧一致性。(在填字游戏的背景下,冲突是指一个方格,两个变量对它的字符值意见不一)。
|
- 回顾一下,当`x`的域中的每一个值在`y`的域中都有一个不引起冲突的可能值时,`x`就与`y`保持弧一致性。(在填字游戏的背景下,冲突是指一个方格,两个变量对它的字符值意见不一)。
|
||||||
- 为了使`x`与`y`保持一致,你要从`x`的域中删除任何在`y`的域中没有相应可能值的值。
|
- 为了使`x`与`y`保持一致,你要从`x`的域中删除任何在`y`的域中没有相应可能值的值。
|
||||||
@@ -87,15 +89,15 @@ $ python generate.py data/structure1.txt data/words1.txt output.png
|
|||||||
- 如果对`x`的域进行了修改,该函数应返回`True`;如果没有修改,则应返回`False`。
|
- 如果对`x`的域进行了修改,该函数应返回`True`;如果没有修改,则应返回`False`。
|
||||||
|
|
||||||
- `ac3`函数应该使用`AC3`算法,对问题实施弧一致性。回顾一下,当每个变量域中的所有值都满足该变量的二进制约束时,就实现了弧一致性。
|
- `ac3`函数应该使用`AC3`算法,对问题实施弧一致性。回顾一下,当每个变量域中的所有值都满足该变量的二进制约束时,就实现了弧一致性。
|
||||||
- 回顾一下,`AC3`算法保持着一个要处理的弧的队列。这个函数需要一个叫做`arcs`的可选参数,代表要处理的弧的初始列表。如果`arcs`是`None`,你的函数应该从问题中的所有弧的初始队列开始。否则,你的算法应该从一个初始队列开始,该队列中只有在列表`arcs`中的弧(其中每个弧是一个变量`x`和另一个变量`y`的元组`(x,y)`)。
|
- 回顾一下,`AC3`算法保持着一个要处理的弧的队列。这个函数需要一个叫做`arcs`的可选参数,代表要处理的弧的初始列表。如果`arcs`是`None`,你的函数应该从问题中的所有弧的初始队列开始。否则,你的算法应该从一个初始队列开始,该队列中只有在列表`arcs`中的弧 (其中每个弧是一个变量`x`和另一个变量`y`的元组`(x,y)`)。
|
||||||
- 回顾一下,为了实现`AC3`,你要一次一次地修改队列中的每个弧。不过,任何时候你对一个域做了改变,你可能需要在队列中增加额外的弧,以确保其他弧保持一致。
|
- 回顾一下,为了实现`AC3`,你要一次一次地修改队列中的每个弧。不过,任何时候你对一个域做了改变,你可能需要在队列中增加额外的弧,以确保其他弧保持一致。
|
||||||
- 你可能会发现在`ac3`的实现中调用`revise`函数是很有帮助的。
|
- 你可能会发现在`ac3`的实现中调用`revise`函数是很有帮助的。
|
||||||
- 如果在执行弧一致性的过程中,你从一个域中删除了所有剩余的值,则返回`False`(这意味着问题无解,因为这个变量已经没有可能的值了)。否则,返回`True`。
|
- 如果在执行弧一致性的过程中,你从一个域中删除了所有剩余的值,则返回`False`(这意味着问题无解,因为这个变量已经没有可能的值了)。否则,返回`True`。
|
||||||
- 你不需要担心在这个函数中强制执行词的唯一性(你将在`consistent`函数中实现这个检查。)
|
- 你不需要担心在这个函数中强制执行词的唯一性 (你将在`consistent`函数中实现这个检查。)
|
||||||
|
|
||||||
- `assignment_complete`函数应该(如其名所示)检查一个给定的赋值是否完成。
|
- `assignment_complete`函数应该 (如其名所示) 检查一个给定的赋值是否完成。
|
||||||
- `assignment`是一个字典,其中键是`Variable`对象,值是代表这些变量将采取的单词的字符串。
|
- `assignment`是一个字典,其中键是`Variable`对象,值是代表这些变量将采取的单词的字符串。
|
||||||
- 如果每个字谜变量都被分配到一个值(不管这个值是什么),那么这个赋值就是完整的。
|
- 如果每个字谜变量都被分配到一个值 (不管这个值是什么),那么这个赋值就是完整的。
|
||||||
- 如果赋值完成,该函数应该返回`True`,否则返回`False`。
|
- 如果赋值完成,该函数应该返回`True`,否则返回`False`。
|
||||||
|
|
||||||
- `consistent`函数应该检查一个给定的`assignment`是否一致。
|
- `consistent`函数应该检查一个给定的`assignment`是否一致。
|
||||||
@@ -109,21 +111,21 @@ $ python generate.py data/structure1.txt data/words1.txt output.png
|
|||||||
- 请注意,在`assignment`中出现的任何变量都已经有了一个值,因此在计算相邻未赋值变量被排除的值的数量时不应该被计算在内。
|
- 请注意,在`assignment`中出现的任何变量都已经有了一个值,因此在计算相邻未赋值变量被排除的值的数量时不应该被计算在内。
|
||||||
- 对于排除相邻变量相同数量可能选择的域值,任何排序都是可以接受的。
|
- 对于排除相邻变量相同数量可能选择的域值,任何排序都是可以接受的。
|
||||||
- 回顾一下,你可以访问`self.crossword.overlaps`来获得两个变量之间的重叠,如果有的话。
|
- 回顾一下,你可以访问`self.crossword.overlaps`来获得两个变量之间的重叠,如果有的话。
|
||||||
- 首先通过返回一个任意顺序的数值列表来实现这个函数可能会有帮助(这仍然会产生正确的填字游戏)。一旦你的算法开始工作,你就可以回去确保这些值是以正确的顺序返回的。
|
- 首先通过返回一个任意顺序的数值列表来实现这个函数可能会有帮助 (这仍然会产生正确的填字游戏)。一旦你的算法开始工作,你就可以回去确保这些值是以正确的顺序返回的。
|
||||||
- 你可能会发现根据一个特定的key来对一个[列表](https://docs.python.org/3/howto/sorting.html)进行排序是很有帮助的: Python 包含一些有用的函数来实现这一点。
|
- 你可能会发现根据一个特定的 key 来对一个[列表](https://docs.python.org/3/howto/sorting.html)进行排序是很有帮助的:Python 包含一些有用的函数来实现这一点。
|
||||||
|
|
||||||
- `select_unassigned_variable`函数应该根据最小剩余值启发式,然后是度启发式,返回字谜中尚未被赋值的单个变量。
|
- `select_unassigned_variable`函数应该根据最小剩余值启发式,然后是度启发式,返回字谜中尚未被赋值的单个变量。
|
||||||
- `assignment`是一个字典,其中键是`Variable`对象,值是代表这些变量将承担的单词的字符串。你可以假设赋值不会是完整的:不是所有的变量都会出现在`assignment`中。
|
- `assignment`是一个字典,其中键是`Variable`对象,值是代表这些变量将承担的单词的字符串。你可以假设赋值不会是完整的:不是所有的变量都会出现在`assignment`中。
|
||||||
- 你的函数应该返回一个`Variable`对象。你应该返回在其域中剩余数值最少的变量。如果变量之间存在平局,你应该在这些变量中选择度最大的变量(拥有最多的邻居)。如果在这两种情况下都相同,你可以在相同的变量中任意选择。
|
- 你的函数应该返回一个`Variable`对象。你应该返回在其域中剩余数值最少的变量。如果变量之间存在平局,你应该在这些变量中选择度最大的变量 (拥有最多的邻居)。如果在这两种情况下都相同,你可以在相同的变量中任意选择。
|
||||||
- 首先通过返回任意未分配的变量来实现这个函数可能是有帮助的(这应该仍然会产生正确的填字游戏)。一旦你的算法开始工作,你就可以回去修改这个函数确保你是根据启发式方法返回一个变量。
|
- 首先通过返回任意未分配的变量来实现这个函数可能是有帮助的 (这应该仍然会产生正确的填字游戏)。一旦你的算法开始工作,你就可以回去修改这个函数确保你是根据启发式方法返回一个变量。
|
||||||
- 你可能会发现根据一个特定的key来对一个列表进行[排序](https://docs.python.org/3/howto/sorting.html)是很有帮助的:Python 包含一些有用的函数来实现这一点。
|
- 你可能会发现根据一个特定的 key 来对一个列表进行[排序](https://docs.python.org/3/howto/sorting.html)是很有帮助的:Python 包含一些有用的函数来实现这一点。
|
||||||
|
|
||||||
- `backtrack`函数应该接受一个部分赋值`assignment`作为输入,并且使用回溯搜索,如果有可能的话,返回一个完整的令人满意的变量赋值。
|
- `backtrack`函数应该接受一个部分赋值`assignment`作为输入,并且使用回溯搜索,如果有可能的话,返回一个完整的令人满意的变量赋值。
|
||||||
- `assignment`是一个字典,其中键是`Variable`对象,值是代表这些变量将承担的单词的字符串。你可以假设赋值不会是完整的:不是所有的变量都会出现在`assignment`中。
|
- `assignment`是一个字典,其中键是`Variable`对象,值是代表这些变量将承担的单词的字符串。你可以假设赋值不会是完整的:不是所有的变量都会出现在`assignment`中。
|
||||||
- 如果有可能生成一个令人满意的字谜,你的函数应该返回完整的赋值:一个字典,其中每个变量是一个键,值是该变量应该承担的单词。如果不可能产生令人满意的赋值,该函数应该返回`None`。
|
- 如果有可能生成一个令人满意的字谜,你的函数应该返回完整的赋值:一个字典,其中每个变量是一个键,值是该变量应该承担的单词。如果不可能产生令人满意的赋值,该函数应该返回`None`。
|
||||||
- 如果你愿意,你可能会发现,如果你把搜索和推理交织在一起,你的算法会更有效率(比如每次做新的赋值时都要保持弧一致性)。我们不要求你这样做,但允许你这样做,只要你的函数仍然产生正确的结果。(正是由于这个原因,`ac3`函数允许一个`arcs`的参数,以防你想从不同的弧队列开始)。
|
- 如果你愿意,你可能会发现,如果你把搜索和推理交织在一起,你的算法会更有效率 (比如每次做新的赋值时都要保持弧一致性)。我们不要求你这样做,但允许你这样做,只要你的函数仍然产生正确的结果。(正是由于这个原因,`ac3`函数允许一个`arcs`的参数,以防你想从不同的弧队列开始)。
|
||||||
|
|
||||||
- 除了要求你实现的函数外,你不应该修改`generate.py`中的任何其他东西,尽管你可以编写额外的函数和/或导入其他Python标准库模块。如果你熟悉`numpy`或`pandas`,你也可以导入它们,但是你不应该使用任何其他的第三方Python模块。你不应该修改`crossword.py`中的任何东西。
|
- 除了要求你实现的函数外,你不应该修改`generate.py`中的任何其他东西,尽管你可以编写额外的函数和/或导入其他 Python 标准库模块。如果你熟悉`numpy`或`pandas`,你也可以导入它们,但是你不应该使用任何其他的第三方 Python 模块。你不应该修改`crossword.py`中的任何东西。
|
||||||
|
|
||||||
## 提示
|
## 提示
|
||||||
|
|
||||||
@@ -131,4 +133,4 @@ $ python generate.py data/structure1.txt data/words1.txt output.png
|
|||||||
|
|
||||||
- 要运行你的程序,你可以运行类似`python generate.py data/structure1.txt data/words1.txt`的命令,指定一个结构文件和一个单词文件。如果可以进行赋值,你应该看到打印出来的赋值。你也可以为图像文件添加一个额外的命令行参数,如运行`python generate.py data/structure1.txt data/words1.txt output.png`,可以为生成的填字游戏生成一个图像表示。
|
- 要运行你的程序,你可以运行类似`python generate.py data/structure1.txt data/words1.txt`的命令,指定一个结构文件和一个单词文件。如果可以进行赋值,你应该看到打印出来的赋值。你也可以为图像文件添加一个额外的命令行参数,如运行`python generate.py data/structure1.txt data/words1.txt output.png`,可以为生成的填字游戏生成一个图像表示。
|
||||||
|
|
||||||
- `Crossword`类有一个`neighbors`函数,可以用来访问某个特定变量的所有邻居(即重叠的变量)。在你需要确定某个特定变量的邻居时,请随时使用这个函数。
|
- `Crossword`类有一个`neighbors`函数,可以用来访问某个特定变量的所有邻居 (即重叠的变量)。在你需要确定某个特定变量的邻居时,请随时使用这个函数。
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
- 最优化是指从一组可能的选项中选择最佳选项。我们已经遇到过试图找到最佳选项的问题,比如在极大极小算法中,今天我们将学习一些工具,可以用来解决更广泛的问题。
|
- 最优化是指从一组可能的选项中选择最佳选项。我们已经遇到过试图找到最佳选项的问题,比如在极大极小算法中,今天我们将学习一些工具,可以用来解决更广泛的问题。
|
||||||
|
|
||||||
## 局部搜索(Local Search)
|
## 局部搜索 (Local Search)
|
||||||
|
|
||||||
- 局部搜索是一种保持单一节点并通过移动到邻近的节点进行搜索的搜索算法。这种类型的算法与我们之前看到的搜索类型不同。例如,在解决迷宫的过程中,我们想找到通往目标的最快捷的方法,而局部搜索则对寻找问题的最佳答案感兴趣。通常情况下,局部搜索会带来一个不是最佳但 "足够好 "的答案,以节省计算能力。考虑一下下面这个局部搜索问题的例子:我们有四所房子在设定的位置。我们想建两所医院,使每所房子到医院的距离最小。这个问题可以形象地描述如下:
|
- 局部搜索是一种保持单一节点并通过移动到邻近的节点进行搜索的搜索算法。这种类型的算法与我们之前看到的搜索类型不同。例如,在解决迷宫的过程中,我们想找到通往目标的最快捷的方法,而局部搜索则对寻找问题的最佳答案感兴趣。通常情况下,局部搜索会带来一个不是最佳但 "足够好 "的答案,以节省计算能力。考虑一下下面这个局部搜索问题的例子:我们有四所房子在设定的位置。我们想建两所医院,使每所房子到医院的距离最小。这个问题可以形象地描述如下:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- 在这幅图中,我们看到的是房屋和医院的可能配置。它们之间的距离是用曼哈顿距离(向上、向下和向两侧移动的次数;在[搜索](4.3.1搜索.md) 中详细讨论)来衡量的,从每个房子到最近的医院的距离之和是17。我们称其为成本 __(cost)__,因为我们试图使这个距离最小化。在这种情况下,一个状态将是房屋和医院的任何一个配置。
|
- 在这幅图中,我们看到的是房屋和医院的可能配置。它们之间的距离是用曼哈顿距离 (向上、向下和向两侧移动的次数;在[搜索](4.3.1搜索.md) 中详细讨论) 来衡量的,从每个房子到最近的医院的距离之和是 17。我们称其为成本 __(cost)__,因为我们试图使这个距离最小化。在这种情况下,一个状态将是房屋和医院的任何一个配置。
|
||||||
|
|
||||||
- 把这个概念抽象化,我们可以把每一种房屋和医院的配置表现为下面的状态空间图。图中的每一条都代表一个状态的值,在我们的例子中,它是房屋和医院配置的成本。
|
- 把这个概念抽象化,我们可以把每一种房屋和医院的配置表现为下面的状态空间图。图中的每一条都代表一个状态的值,在我们的例子中,它是房屋和医院配置的成本。
|
||||||
|
|
||||||
@@ -16,15 +16,15 @@
|
|||||||
|
|
||||||
- 从这个可视化的角度来看,我们可以为我们接下来的讨论定义几个重要的术语:
|
- 从这个可视化的角度来看,我们可以为我们接下来的讨论定义几个重要的术语:
|
||||||
- 目标函数 __(Objective Function)__ 是一个函数,我们用它来最大化解决方案的值。
|
- 目标函数 __(Objective Function)__ 是一个函数,我们用它来最大化解决方案的值。
|
||||||
- 成本函数 __(Cost Function)__ 是一个我们用来最小化解决方案成本的函数(这就是我们在房屋和医院的例子中要使用的函数。我们想要最小化从房屋到医院的距离)。
|
- 成本函数 __(Cost Function)__ 是一个我们用来最小化解决方案成本的函数 (这就是我们在房屋和医院的例子中要使用的函数。我们想要最小化从房屋到医院的距离)。
|
||||||
- 当前状态 __(Current State)__ 是指函数目前正在考虑的状态。
|
- 当前状态 __(Current State)__ 是指函数目前正在考虑的状态。
|
||||||
- 邻居状态 __(Neighbor State)__ 是当前状态可以过渡到的一个状态。在上面的一维状态空间图中,邻居状态是指当前状态两侧的状态。在我们的例子中,邻居状态可以是将其中一家医院向任何方向移动一步所产生的状态。邻居状态通常与当前状态相似,因此,其值与当前状态的值接近。
|
- 邻居状态 __(Neighbor State)__ 是当前状态可以过渡到的一个状态。在上面的一维状态空间图中,邻居状态是指当前状态两侧的状态。在我们的例子中,邻居状态可以是将其中一家医院向任何方向移动一步所产生的状态。邻居状态通常与当前状态相似,因此,其值与当前状态的值接近。
|
||||||
|
|
||||||
- 请注意,局部搜索算法的工作方式是考虑当前状态下的一个节点,然后将该节点移动到当前状态的一个邻节点处。这与极大极小算法不同,例如,在极大极小算法中,状态空间中的每一个状态都被递归地考虑。
|
- 请注意,局部搜索算法的工作方式是考虑当前状态下的一个节点,然后将该节点移动到当前状态的一个邻节点处。这与极大极小算法不同,例如,在极大极小算法中,状态空间中的每一个状态都被递归地考虑。
|
||||||
|
|
||||||
## 爬山算法(Hill Climbing)
|
## 爬山算法 (Hill Climbing)
|
||||||
|
|
||||||
- 爬山算法是局部搜索算法的一种类型。在这个算法中,邻居的状态与当前的状态进行比较,如果其中任何一个状态更好,我们就把当前的节点从当-的状态改为该邻居的状态。“好状态”的定义是由目标函数决定的,倾向于一个较高的值,或一个递减函数,倾向于一个较低的值。
|
- 爬山算法是局部搜索算法的一种类型。在这个算法中,邻居的状态与当前的状态进行比较,如果其中任何一个状态更好,我们就把当前的节点从当 - 的状态改为该邻居的状态。“好状态”的定义是由目标函数决定的,倾向于一个较高的值,或一个递减函数,倾向于一个较低的值。
|
||||||
|
|
||||||
- 一个爬山算法在伪代码中会有以下样子:
|
- 一个爬山算法在伪代码中会有以下样子:
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ function Hill-Climb(problem):
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
- 在这个状态下,成本是11,比初始状态的成本17有所提高。然而,这还不是最佳状态。例如,将左边的医院移到左上角的房子下面,会使成本达到9,比11好。然而,这个版本的爬山算法无法达到这个目标,因为所有的邻居状态都至少和当前状态的成本一样高。从这个意义上说,爬坡算法是短视的,它经常满足于比其他一些解决方案更好的解决方案,但不一定是所有可能的解决方案中最好的。
|
- 在这个状态下,成本是 11,比初始状态的成本 17 有所提高。然而,这还不是最佳状态。例如,将左边的医院移到左上角的房子下面,会使成本达到 9,比 11 好。然而,这个版本的爬山算法无法达到这个目标,因为所有的邻居状态都至少和当前状态的成本一样高。从这个意义上说,爬坡算法是短视的,它经常满足于比其他一些解决方案更好的解决方案,但不一定是所有可能的解决方案中最好的。
|
||||||
|
|
||||||
### 局部和全局最小值和最大值
|
### 局部和全局最小值和最大值
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ function Hill-Climb(problem):
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
- 爬山算法的问题是,它们可能会在局部最小和最大中结束。一旦算法到达一个点,其邻居状态就目标函数而言,比当前状态更糟糕,算法就会停止。特殊类型的局部最大值和最小值包括平坦的局部最大值/最小值 __(flat local maximum/minimum)__,即多个数值相同的状态相邻,形成一个plateau,其邻居状态的数值更差;以及 __shoulder__ ,邻居状态既可以更好,也可以更差。从plateau的中间开始,算法将无法向任何方向推进。
|
- 爬山算法的问题是,它们可能会在局部最小和最大中结束。一旦算法到达一个点,其邻居状态就目标函数而言,比当前状态更糟糕,算法就会停止。特殊类型的局部最大值和最小值包括平坦的局部最大值/最小值 __(flat local maximum/minimum)__,即多个数值相同的状态相邻,形成一个plateau,其邻居状态的数值更差;以及 __shoulder__,邻居状态既可以更好,也可以更差。从 plateau 的中间开始,算法将无法向任何方向推进。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -67,11 +67,11 @@ function Hill-Climb(problem):
|
|||||||
- Stochastic: 从值较高的邻居状态中随机选择。这样做,我们选择去任何比我们的值更高的方向。
|
- Stochastic: 从值较高的邻居状态中随机选择。这样做,我们选择去任何比我们的值更高的方向。
|
||||||
- __First-choice__: 选择第一个值较高的邻居状态。
|
- __First-choice__: 选择第一个值较高的邻居状态。
|
||||||
- __Random-restart__: 进行多次爬山。每次都从一个随机状态开始。比较每次试验的最大值,并在这些最大值中选择一个。
|
- __Random-restart__: 进行多次爬山。每次都从一个随机状态开始。比较每次试验的最大值,并在这些最大值中选择一个。
|
||||||
- __Local Beam Search__: 选择值最高的k个邻居状态。这与大多数本地搜索算法不同,它使用多个节点进行搜索,而不是只有一个节点。
|
- __Local Beam Search__: 选择值最高的 k 个邻居状态。这与大多数本地搜索算法不同,它使用多个节点进行搜索,而不是只有一个节点。
|
||||||
|
|
||||||
- 虽然局部搜索算法并不总是给出最好的解决方案,但在考虑所有可能的状态在计算上不可行的情况下,它们往往能给出足够好的解决方案。
|
- 虽然局部搜索算法并不总是给出最好的解决方案,但在考虑所有可能的状态在计算上不可行的情况下,它们往往能给出足够好的解决方案。
|
||||||
|
|
||||||
## 模拟退火算法(Simulated Annealing)
|
## 模拟退火算法 (Simulated Annealing)
|
||||||
|
|
||||||
- 尽管我们已经看到了可以改进爬山算法的变种,但它们都有一个共同的错误:一旦算法达到局部最大值,它就会停止运行。模拟退火算法允许算法在卡在局部最大值时"摆脱"自己。
|
- 尽管我们已经看到了可以改进爬山算法的变种,但它们都有一个共同的错误:一旦算法达到局部最大值,它就会停止运行。模拟退火算法允许算法在卡在局部最大值时"摆脱"自己。
|
||||||
|
|
||||||
@@ -90,9 +90,9 @@ function Simulated-Annealing(problem, max):
|
|||||||
return current
|
return current
|
||||||
```
|
```
|
||||||
|
|
||||||
- 该算法将一个`problem`和`max`作为输入,`max`是它应该重复的次数。对于每个迭代,`T`是用一个`Temperature`函数来设置的。这个函数在早期迭代中返回一个较高的值(当`t`较低时),在后期迭代中返回一个较低的值(当`t`较高时)。然后,随机选择一个邻居状态,并计算`ΔE`,使其量化邻居状态比当前状态好的程度。如果邻居状态比当前状态好(`ΔE>0`),像以前一样,我们将当前状态设置为邻居状态。然而,当邻居状态较差时(`ΔE<0`),我们仍然可能将我们的当前状态设置为该邻居状态,并且我们以$e^{ΔE/t}$的概率这样做。这里的意思是,更小的`ΔE`将导致邻居状态被选择的概率降低,而温度`t`越高,邻居状态被选择的概率越高。这意味着邻居状态越差,被选择的可能性就越小,而算法在其过程中越早,就越有可能将一个较差的邻居状态设置为当前状态。这背后的数学原理如下:`e`是一个常数(大约2.72),`ΔE`是负数(因为这个邻居比当前状态更糟糕)。温度`t`越高,ΔE/`t`就越接近于0,使概率更接近于1。
|
- 该算法将一个`problem`和`max`作为输入,`max`是它应该重复的次数。对于每个迭代,`T`是用一个`Temperature`函数来设置的。这个函数在早期迭代中返回一个较高的值 (当`t`较低时),在后期迭代中返回一个较低的值 (当`t`较高时)。然后,随机选择一个邻居状态,并计算`ΔE`,使其量化邻居状态比当前状态好的程度。如果邻居状态比当前状态好 (`ΔE>0`),像以前一样,我们将当前状态设置为邻居状态。然而,当邻居状态较差时 (`ΔE<0`),我们仍然可能将我们的当前状态设置为该邻居状态,并且我们以$e^{ΔE/t}$的概率这样做。这里的意思是,更小的`ΔE`将导致邻居状态被选择的概率降低,而温度`t`越高,邻居状态被选择的概率越高。这意味着邻居状态越差,被选择的可能性就越小,而算法在其过程中越早,就越有可能将一个较差的邻居状态设置为当前状态。这背后的数学原理如下:`e`是一个常数 (大约 2.72),`ΔE`是负数 (因为这个邻居比当前状态更糟糕)。温度`t`越高,ΔE/`t`就越接近于 0,使概率更接近于 1。
|
||||||
|
|
||||||
### 旅行商问题(Traveling Salesman Problem)
|
### 旅行商问题 (Traveling Salesman Problem)
|
||||||
|
|
||||||
- 在旅行商问题中,任务是连接所有的点,同时选择最短的距离。例如,这就是快递公司需要做的事情:找到从商店到所有客户家的最短路线,然后再返回。
|
- 在旅行商问题中,任务是连接所有的点,同时选择最短的距离。例如,这就是快递公司需要做的事情:找到从商店到所有客户家的最短路线,然后再返回。
|
||||||
|
|
||||||
@@ -100,25 +100,25 @@ function Simulated-Annealing(problem, max):
|
|||||||
| ------------------------------ | ------------------------------ |
|
| ------------------------------ | ------------------------------ |
|
||||||
|  |  |
|
|  |  |
|
||||||
|
|
||||||
- 在这种情况下,邻居状态可以被看作是两个箭头互换位置的状态。计算每一个可能的组合使得这个问题在计算上要求很高(10个点给了我们10!或者说3,628,800条可能的路线)。通过使用模拟退火算法,可以以较低的计算成本找到一个好的解决方案。
|
- 在这种情况下,邻居状态可以被看作是两个箭头互换位置的状态。计算每一个可能的组合使得这个问题在计算上要求很高 (10 个点给了我们 10! 或者说 3,628,800 条可能的路线)。通过使用模拟退火算法,可以以较低的计算成本找到一个好的解决方案。
|
||||||
|
|
||||||
## 线性规划(Linear Programming)
|
## 线性规划 (Linear Programming)
|
||||||
|
|
||||||
- 线性规划是一个优化线性方程(y=ax₁+bx₂+...形式的方程)的问题系列。
|
- 线性规划是一个优化线性方程 (y=ax₁+bx₂+...形式的方程) 的问题系列。
|
||||||
|
|
||||||
- 线性规划有以下内容:
|
- 线性规划有以下内容:
|
||||||
- 一个我们想要最小化的成本函数:c₁x₁ + c₂x₂ + ... + cₙxₙ。这里,每个x是一个变量,它与一些成本c相关联。
|
- 一个我们想要最小化的成本函数:c₁x₁ + c₂x₂ + ... + cₙxₙ。这里,每个 x 是一个变量,它与一些成本 c 相关联。
|
||||||
- 一个约束条件,它表示为一个变量的总和,它要么小于或等于一个值(a₁x₁+a₂x₂+...+aₙxₙ≤b),要么正好等于这个值(a₁x₁+a₂x₂+...+aₙxₙ=b)。在这种情况下,x是一个变量,a是与之相关的一些资源,而b是我们可以为这个问题投入多少资源。
|
- 一个约束条件,它表示为一个变量的总和,它要么小于或等于一个值 (a₁x₁+a₂x₂+...+aₙxₙ≤b),要么正好等于这个值 (a₁x₁+a₂x₂+...+aₙxₙ=b)。在这种情况下,x 是一个变量,a 是与之相关的一些资源,而 b 是我们可以为这个问题投入多少资源。
|
||||||
- 变量的域(例如,一个变量不能是负数),形式为lᵢ≤xᵢ≤uᵢ。
|
- 变量的域 (例如,一个变量不能是负数),形式为 lᵢ≤xᵢ≤uᵢ。
|
||||||
|
|
||||||
- 请考虑以下例子:
|
- 请考虑以下例子:
|
||||||
- 两台机器,X₁和X₂。X₁的运行成本为50美元/小时,X₂的运行成本为80美元/小时。我们的目标是使成本最小化。这可以被表述为一个成本函数:50x₁+80x₂。
|
- 两台机器,X₁和 X₂。X₁的运行成本为 50 美元/小时,X₂的运行成本为 80 美元/小时。我们的目标是使成本最小化。这可以被表述为一个成本函数:50x₁+80x₂。
|
||||||
- X₁每小时需要5个单位的劳动力。X₂每小时需要2个单位的劳动力。总共需要花费20个单位的劳动力。这可以被形式化为一个约束条件:5x₁ + 2x₂ ≤ 20。
|
- X₁每小时需要 5 个单位的劳动力。X₂每小时需要 2 个单位的劳动力。总共需要花费 20 个单位的劳动力。这可以被形式化为一个约束条件:5x₁ + 2x₂ ≤ 20。
|
||||||
- X₁每小时生产10个单位的产品。X₂每小时生产12个单位的产品。公司需要90个单位的产出。这是另一个约束条件。从字面上看,它可以被改写为10x₁+12x₂≥90。然而,约束条件必须是(a₁x₁+a₂x₂+...+aₙxₙ≤b)或(a₁x₁+a₂x₂+...+aₙxₙ=b)。因此,我们乘以(-1),得到一个所需形式的等价方程:(-10x₁)+(-12x₂)≤-90。
|
- X₁每小时生产 10 个单位的产品。X₂每小时生产 12 个单位的产品。公司需要 90 个单位的产出。这是另一个约束条件。从字面上看,它可以被改写为 10x₁+12x₂≥90。然而,约束条件必须是 (a₁x₁+a₂x₂+...+aₙxₙ≤b) 或 (a₁x₁+a₂x₂+...+aₙxₙ=b)。因此,我们乘以 (-1),得到一个所需形式的等价方程:(-10x₁)+(-12x₂)≤-90。
|
||||||
|
|
||||||
- 线性规划的优化算法需要几何学和线性代数的背景知识,而我们并不想假设这些知识。相反,我们可以使用已经存在的算法,如Simplex和Interior-Point。
|
- 线性规划的优化算法需要几何学和线性代数的背景知识,而我们并不想假设这些知识。相反,我们可以使用已经存在的算法,如 Simplex 和 Interior-Point。
|
||||||
|
|
||||||
- 下面是一个使用Python中scipy库的线性规划例子:
|
- 下面是一个使用 Python 中 scipy 库的线性规划例子:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import scipy.optimize
|
import scipy.optimize
|
||||||
@@ -137,19 +137,19 @@ else:
|
|||||||
print("No solution")
|
print("No solution")
|
||||||
```
|
```
|
||||||
|
|
||||||
## 约束满足(Constraint Satisfaction)
|
## 约束满足 (Constraint Satisfaction)
|
||||||
|
|
||||||
- 约束满足问题是一类需要在满足某些条件下为变量赋值的问题。
|
- 约束满足问题是一类需要在满足某些条件下为变量赋值的问题。
|
||||||
|
|
||||||
- 约束条件满足问题具有以下特性:
|
- 约束条件满足问题具有以下特性:
|
||||||
- 变量集合{x₁,x₂,...,xₙ}。
|
- 变量集合{x₁,x₂,...,xₙ}。
|
||||||
- 每个变量域的集合{D₁, D₂, ..., Dₙ}。
|
- 每个变量域的集合{D₁, D₂, ..., Dₙ}。
|
||||||
- 一组约束条件C
|
- 一组约束条件 C
|
||||||
|
|
||||||
- 数独可以表示为一个约束满足问题,每个空方块是一个变量,域是数字1-9,而约束是不能彼此相等的方块。
|
- 数独可以表示为一个约束满足问题,每个空方块是一个变量,域是数字 1-9,而约束是不能彼此相等的方块。
|
||||||

|

|
||||||
|
|
||||||
- 再考虑一个例子。每个学生1-4都在选修A、B、...、G中的三门课程。每门课程都需要有考试,可能的考试日是星期一、星期二和星期三。但是,同一个学生不能在同一天有两次考试。在这种情况下,变量是课程,域是天数,约束条件是哪些课程不能在同一天安排考试,因为是同一个学生在考试。这可以直观地显示如下:
|
- 再考虑一个例子。每个学生 1-4 都在选修 A、B、...、G 中的三门课程。每门课程都需要有考试,可能的考试日是星期一、星期二和星期三。但是,同一个学生不能在同一天有两次考试。在这种情况下,变量是课程,域是天数,约束条件是哪些课程不能在同一天安排考试,因为是同一个学生在考试。这可以直观地显示如下:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -158,22 +158,22 @@ else:
|
|||||||

|

|
||||||
|
|
||||||
- 关于约束满足问题,还有几个值得了解的术语:
|
- 关于约束满足问题,还有几个值得了解的术语:
|
||||||
- 硬约束(Hard Constraint)是指在一个正确的解决方案中必须满足的约束。
|
- 硬约束 (Hard Constraint) 是指在一个正确的解决方案中必须满足的约束。
|
||||||
- 软约束(Soft Constraint)是一种约束,表示哪种解决方案比其他解决方案更受欢迎。
|
- 软约束 (Soft Constraint) 是一种约束,表示哪种解决方案比其他解决方案更受欢迎。
|
||||||
- 一元约束(Unary Constraint)是指只涉及一个变量的约束。在我们的例子中,一元约束是指课程A在周一不能有考试{A≠周一}。
|
- 一元约束 (Unary Constraint) 是指只涉及一个变量的约束。在我们的例子中,一元约束是指课程 A 在周一不能有考试{A≠周一}。
|
||||||
- 二元约束(Binary Constraint)是一种涉及两个变量的约束。这就是我们在上面的例子中使用的约束类型,表示两个课程不能有相同的值{A ≠ B}。
|
- 二元约束 (Binary Constraint) 是一种涉及两个变量的约束。这就是我们在上面的例子中使用的约束类型,表示两个课程不能有相同的值{A ≠ B}。
|
||||||
|
|
||||||
### 节点一致性(Node Consistency)
|
### 节点一致性 (Node Consistency)
|
||||||
|
|
||||||
- 节点一致性是指一个变量域中的所有值都满足该变量的一元约束。
|
- 节点一致性是指一个变量域中的所有值都满足该变量的一元约束。
|
||||||
|
|
||||||
- 例如,让我们拿两门课程,A和B。每门课程的域是{Monday, Tuesday, Wednesday},约束条件是{A≠Mon,B≠Tue,B≠Mon,A≠B}。现在,A和B都不是一致的,因为现有的约束条件使它们不能取其域中的每一个值。然而,如果我们从A的域中移除Monday,那么它就会有节点一致性。为了实现B的节点一致性,我们必须从它的域中删除Monday和Tuesday。
|
- 例如,让我们拿两门课程,A 和 B。每门课程的域是{Monday, Tuesday, Wednesday},约束条件是{A≠Mon,B≠Tue,B≠Mon,A≠B}。现在,A 和 B 都不是一致的,因为现有的约束条件使它们不能取其域中的每一个值。然而,如果我们从 A 的域中移除 Monday,那么它就会有节点一致性。为了实现 B 的节点一致性,我们必须从它的域中删除 Monday 和 Tuesday。
|
||||||
|
|
||||||
### 弧一致性(Arc Consistency)
|
### 弧一致性 (Arc Consistency)
|
||||||
|
|
||||||
- 弧一致性是指一个变量域中的所有值都满足该变量的二元约束(注意,我们现在用"弧"来指代我们以前所说的 "边")。换句话说,要使X对Y具有弧一致性,就要从X的域中移除元素,直到X的每个选择都有Y的可能选择。
|
- 弧一致性是指一个变量域中的所有值都满足该变量的二元约束 (注意,我们现在用"弧"来指代我们以前所说的 "边")。换句话说,要使 X 对 Y 具有弧一致性,就要从 X 的域中移除元素,直到 X 的每个选择都有 Y 的可能选择。
|
||||||
|
|
||||||
- 考虑到我们之前的例子,修改后的域:A:{Tuesday, Wednesday}和B:{Wednesday}。如果A与B是弧一致的,那么无论A的考试被安排在哪一天(从它的域来看),B仍然能够安排考试。A与B是弧一致的吗?如果A取值为Tuesday,那么B可以取值为Wednesday。然而,如果A取值为Wednesday,那么就没有B可以取的值(记住,其中一个约束是A≠B)。因此,A与B不是弧一致的。为了改变这种情况,我们可以从A的域中删除Wednesday。然后,A的任何取值(Tuesday是唯一的选择)都会给B留下一个取值(Wednesday)。现在,A与B是弧一致的。让我们看看一个伪代码的算法,使一个变量相对于其他变量是弧一致的(注意,csp代表 "约束满足问题")。
|
- 考虑到我们之前的例子,修改后的域:A:{Tuesday, Wednesday}和 B:{Wednesday}。如果 A 与 B 是弧一致的,那么无论 A 的考试被安排在哪一天 (从它的域来看),B 仍然能够安排考试。A 与 B 是弧一致的吗?如果 A 取值为 Tuesday,那么 B 可以取值为 Wednesday。然而,如果 A 取值为 Wednesday,那么就没有 B 可以取的值 (记住,其中一个约束是 A≠B)。因此,A 与 B 不是弧一致的。为了改变这种情况,我们可以从 A 的域中删除 Wednesday。然后,A 的任何取值 (Tuesday 是唯一的选择) 都会给 B 留下一个取值 (Wednesday)。现在,A 与 B 是弧一致的。让我们看看一个伪代码的算法,使一个变量相对于其他变量是弧一致的 (注意,csp 代表 "约束满足问题")。
|
||||||
|
|
||||||
```python
|
```python
|
||||||
function Revise(csp, X, Y):
|
function Revise(csp, X, Y):
|
||||||
@@ -185,9 +185,9 @@ function Revise(csp, X, Y):
|
|||||||
return revised
|
return revised
|
||||||
```
|
```
|
||||||
|
|
||||||
- 这个算法从跟踪X的域是否有任何变化开始,使用变量revised,这在我们研究的下一个算法中会很有用。然后,代码对X的域中的每一个值进行重复,看看Y是否有一个满足约束条件的值。如果是,则什么都不做,如果不是,则从X的域中删除这个值。
|
- 这个算法从跟踪 X 的域是否有任何变化开始,使用变量 revised,这在我们研究的下一个算法中会很有用。然后,代码对 X 的域中的每一个值进行重复,看看 Y 是否有一个满足约束条件的值。如果是,则什么都不做,如果不是,则从 X 的域中删除这个值。
|
||||||
|
|
||||||
- 通常情况下,我们感兴趣的是使整个问题的弧一致,而不仅仅是一个变量相对于另一个变量的一致性。在这种情况下,我们将使用一种叫做AC-3的算法,该算法使用Revise:
|
- 通常情况下,我们感兴趣的是使整个问题的弧一致,而不仅仅是一个变量相对于另一个变量的一致性。在这种情况下,我们将使用一种叫做 AC-3 的算法,该算法使用 Revise:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
function AC-3(csp):
|
function AC-3(csp):
|
||||||
@@ -202,20 +202,20 @@ function AC-3(csp):
|
|||||||
return true
|
return true
|
||||||
```
|
```
|
||||||
|
|
||||||
- 该算法将问题中的所有弧添加到一个队列中。每当它考虑一个弧时,它就把它从队列中删除。然后,它运行Revise算法,看这个弧是否一致。如果做了修改使其一致,则需要进一步的行动。如果得到的X的域是空的,这意味着这个约束满足问题是无法解决的(因为没有任何X可以取的值会让Y在约束条件下取任何值)。如果问题在上一步中没有被认为是不可解决的,那么,由于X的域被改变了,我们需要看看与X相关的所有弧是否仍然一致。也就是说,我们把除了Y以外的所有X的邻居,把他们和X之间的弧添加到队列中。然而,如果Revise算法返回false,意味着域没有被改变,我们只需继续考虑其他弧。
|
- 该算法将问题中的所有弧添加到一个队列中。每当它考虑一个弧时,它就把它从队列中删除。然后,它运行 Revise 算法,看这个弧是否一致。如果做了修改使其一致,则需要进一步的行动。如果得到的 X 的域是空的,这意味着这个约束满足问题是无法解决的 (因为没有任何 X 可以取的值会让 Y 在约束条件下取任何值)。如果问题在上一步中没有被认为是不可解决的,那么,由于 X 的域被改变了,我们需要看看与 X 相关的所有弧是否仍然一致。也就是说,我们把除了 Y 以外的所有 X 的邻居,把他们和 X 之间的弧添加到队列中。然而,如果 Revise 算法返回 false,意味着域没有被改变,我们只需继续考虑其他弧。
|
||||||
|
|
||||||
- 虽然弧一致性的算法可以简化问题,但不一定能解决问题,因为它只考虑了二元约束,而没有考虑多个节点可能的相互连接方式。我们之前的例子中,4个学生中的每个人都在选修3门课程,对其运行AC-3后,仍然没有变化。
|
- 虽然弧一致性的算法可以简化问题,但不一定能解决问题,因为它只考虑了二元约束,而没有考虑多个节点可能的相互连接方式。我们之前的例子中,4 个学生中的每个人都在选修 3 门课程,对其运行 AC-3 后,仍然没有变化。
|
||||||
|
|
||||||
- 我们讲过[搜索](4.3.1搜索.md)问题。一个约束满足问题可以被看作是一个搜索问题:
|
- 我们讲过[搜索](4.3.1搜索.md)问题。一个约束满足问题可以被看作是一个搜索问题:
|
||||||
- 初始状态(Initial state):空赋值(所有变量都没有分配任何数值)。
|
- 初始状态 (Initial state):空赋值 (所有变量都没有分配任何数值)。
|
||||||
- 动作(Action):在赋值中加入一个{变量=值};也就是说,给某个变量一个值。
|
- 动作 (Action):在赋值中加入一个{变量=值};也就是说,给某个变量一个值。
|
||||||
- 过渡模型(Transition model):显示添加赋值如何改变变量。这没有什么深度:过渡模型返回包括最新动作后的赋值的状态。
|
- 过渡模型 (Transition model):显示添加赋值如何改变变量。这没有什么深度:过渡模型返回包括最新动作后的赋值的状态。
|
||||||
- 目标测试(Goal test):检查所有变量是否被赋值,所有约束条件是否得到满足。
|
- 目标测试 (Goal test):检查所有变量是否被赋值,所有约束条件是否得到满足。
|
||||||
- 路径成本函数(Path cost function):所有路径的成本都是一样的。正如我们前面提到的,与典型的搜索问题相比,优化问题关心的是解决方案,而不是通往解决方案的路线。
|
- 路径成本函数 (Path cost function):所有路径的成本都是一样的。正如我们前面提到的,与典型的搜索问题相比,优化问题关心的是解决方案,而不是通往解决方案的路线。
|
||||||
|
|
||||||
- 然而,把约束满足问题作为一个普通的搜索问题来处理,是非常低效的。相反,我们可以利用约束满足问题的结构来更有效地解决它。
|
- 然而,把约束满足问题作为一个普通的搜索问题来处理,是非常低效的。相反,我们可以利用约束满足问题的结构来更有效地解决它。
|
||||||
|
|
||||||
### 回溯搜索(Backtracking Search)
|
### 回溯搜索 (Backtracking Search)
|
||||||
|
|
||||||
- 回溯搜索是一种考虑约束满足搜索问题结构的搜索算法。一般来说,它是一个递归函数,只要值满足约束,它就会尝试继续赋值。如果违反了约束,它将尝试不同的赋值。让我们看看它的伪代码:
|
- 回溯搜索是一种考虑约束满足搜索问题结构的搜索算法。一般来说,它是一个递归函数,只要值满足约束,它就会尝试继续赋值。如果违反了约束,它将尝试不同的赋值。让我们看看它的伪代码:
|
||||||
|
|
||||||
@@ -240,13 +240,13 @@ function Backtrack(assignment, csp):
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
- 我们从空赋值开始。然后,我们选择变量A,并给它赋值`Mon`。然后,使用这个赋值,我们再次运行算法。既然A已经有了一个赋值,算法将考虑B,并将`Mon`赋值给它。此赋值返回`false`,因此算法将尝试在`Tue`为B赋值,而不是在给定上一个赋值的情况下为C赋值。这个新的赋值满足约束条件,在给定这个赋值的情况下,下一步将考虑一个新的变量。例如,如果将`Tue`或`Wed`也赋值给B会导致失败,那么算法将回溯并返回到考虑A,为其分配另一个值,即`Tue`。如果`Tue`和`Wed`也失败了,那么这意味着我们已经尝试了所有可能的赋值,该问题是无法解决的。
|
- 我们从空赋值开始。然后,我们选择变量 A,并给它赋值`Mon`。然后,使用这个赋值,我们再次运行算法。既然 A 已经有了一个赋值,算法将考虑 B,并将`Mon`赋值给它。此赋值返回`false`,因此算法将尝试在`Tue`为 B 赋值,而不是在给定上一个赋值的情况下为 C 赋值。这个新的赋值满足约束条件,在给定这个赋值的情况下,下一步将考虑一个新的变量。例如,如果将`Tue`或`Wed`也赋值给 B 会导致失败,那么算法将回溯并返回到考虑 A,为其分配另一个值,即`Tue`。如果`Tue`和`Wed`也失败了,那么这意味着我们已经尝试了所有可能的赋值,该问题是无法解决的。
|
||||||
|
|
||||||
- 在源代码部分,您可以从头开始实现的回溯算法。然而,这种算法被广泛使用,因此,多个库已经包含了它的实现。
|
- 在源代码部分,您可以从头开始实现的回溯算法。然而,这种算法被广泛使用,因此,多个库已经包含了它的实现。
|
||||||
|
|
||||||
## 推理(Inference)
|
## 推理 (Inference)
|
||||||
|
|
||||||
- 尽管回溯搜索比简单搜索更有效,但它仍然需要大量的算力。另一方面,满足弧一致性需要的算力较低。通过将回溯搜索与推理交织在一起(满足弧一致性),我们可以得到一种更有效的算法。该算法被称为“保持弧一致性” __(Maintaining Arc-Consistency)__ 算法。该算法将在每次新的回溯搜索分配之后满足弧一致性。具体来说,在我们对X进行新的赋值后,我们将调用`AC-3`算法,并从所有弧(Y,X)的队列开始,其中Y是X的邻居(而不是问题中所有弧的队列)。以下是一个经过修订的Backtrack算法,该算法保持了弧的一致性。
|
- 尽管回溯搜索比简单搜索更有效,但它仍然需要大量的算力。另一方面,满足弧一致性需要的算力较低。通过将回溯搜索与推理交织在一起(满足弧一致性),我们可以得到一种更有效的算法。该算法被称为“保持弧一致性”__(Maintaining Arc-Consistency)__ 算法。该算法将在每次新的回溯搜索分配之后满足弧一致性。具体来说,在我们对 X 进行新的赋值后,我们将调用`AC-3`算法,并从所有弧 (Y,X) 的队列开始,其中 Y 是 X 的邻居 (而不是问题中所有弧的队列)。以下是一个经过修订的 Backtrack 算法,该算法保持了弧的一致性。
|
||||||
|
|
||||||
```python
|
```python
|
||||||
function Backtrack(assignment, csp):
|
function Backtrack(assignment, csp):
|
||||||
@@ -266,28 +266,28 @@ function Backtrack(assignment, csp):
|
|||||||
return failure
|
return failure
|
||||||
```
|
```
|
||||||
|
|
||||||
- Inference函数运行AC-3算法,如前所述。它的输出是通过满足弧一致性可以做出的所有推断。从字面上看,这些是可以从以前的赋值和约束满足问题的结构中推导出来的新赋值。
|
- Inference 函数运行 AC-3 算法,如前所述。它的输出是通过满足弧一致性可以做出的所有推断。从字面上看,这些是可以从以前的赋值和约束满足问题的结构中推导出来的新赋值。
|
||||||
|
|
||||||
- 还有其他方法可以提高算法的效率。到目前为止,我们随机选择了一个未分配的变量。然而,有些选择比其他选择更有可能更快地找到解决方案。这需要使用启发式方法。启发式是一条经验法则,这意味着,通常情况下,它会比遵循随机的方法带来更好的结果,但不能保证总是更优。
|
- 还有其他方法可以提高算法的效率。到目前为止,我们随机选择了一个未分配的变量。然而,有些选择比其他选择更有可能更快地找到解决方案。这需要使用启发式方法。启发式是一条经验法则,这意味着,通常情况下,它会比遵循随机的方法带来更好的结果,但不能保证总是更优。
|
||||||
|
|
||||||
- __最小剩余值(Minimum Remaining Values(MRV))__ 就是这样一种启发式方法。这里的想法是,如果一个变量的域被推理限制了,现在它只剩下一个值(甚至是两个值),那么通过进行这种赋值,我们将减少以后可能需要进行的回溯次数。也就是说,我们迟早要做这个赋值,因为它是从满足弧一致性中推断出来的。如果这项任务失败了,最好尽快发现,避免以后的回溯。
|
- __最小剩余值 (Minimum Remaining Values(MRV))__ 就是这样一种启发式方法。这里的想法是,如果一个变量的域被推理限制了,现在它只剩下一个值 (甚至是两个值),那么通过进行这种赋值,我们将减少以后可能需要进行的回溯次数。也就是说,我们迟早要做这个赋值,因为它是从满足弧一致性中推断出来的。如果这项任务失败了,最好尽快发现,避免以后的回溯。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- 例如,在给定当前赋值的情况下缩小变量的域后,使用MRV启发式,我们接下来将选择变量C,并以Wed为其赋值。
|
- 例如,在给定当前赋值的情况下缩小变量的域后,使用 MRV 启发式,我们接下来将选择变量 C,并以 Wed 为其赋值。
|
||||||
|
|
||||||
- __度(Degree)__ 启发式依赖于变量的度,其中度是指将一个变量连接到其他变量的弧数。通过一次赋值选择度最高的变量,我们约束了多个其他变量,从而加快了算法的进程。
|
- __度 (Degree)__ 启发式依赖于变量的度,其中度是指将一个变量连接到其他变量的弧数。通过一次赋值选择度最高的变量,我们约束了多个其他变量,从而加快了算法的进程。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- 例如,上面所有的变量都有相同大小的域。因此,我们应该选择一个度最高的域,它将是变量E。
|
- 例如,上面所有的变量都有相同大小的域。因此,我们应该选择一个度最高的域,它将是变量 E。
|
||||||
|
|
||||||
- 这两种启发式方法并不总是适用的。例如,当多个变量在其域中具有相同的最小值时,或者当多个变数具有相同的最高度时。
|
- 这两种启发式方法并不总是适用的。例如,当多个变量在其域中具有相同的最小值时,或者当多个变数具有相同的最高度时。
|
||||||
|
|
||||||
- 另一种提高算法效率的方法是,当我们从变量的域中选择一个值时,使用另一种启发式方法。在这里,我们使用 __最小约束值(Least Constraining Values)__ 启发式,在这里我们选择将约束最少其他变量的值。这里的想法是,在度启发式中,我们希望使用更可能约束其他变量的变量,而在这里,我们希望这个变量对其他变量的约束最少。也就是说,我们希望找到可能是最大潜在麻烦源的变量(度最高的变量),然后使其尽可能不麻烦(为其赋值约束其他变量最少的值)。
|
- 另一种提高算法效率的方法是,当我们从变量的域中选择一个值时,使用另一种启发式方法。在这里,我们使用 __最小约束值 (Least Constraining Values)__ 启发式,在这里我们选择将约束最少其他变量的值。这里的想法是,在度启发式中,我们希望使用更可能约束其他变量的变量,而在这里,我们希望这个变量对其他变量的约束最少。也就是说,我们希望找到可能是最大潜在麻烦源的变量 (度最高的变量),然后使其尽可能不麻烦 (为其赋值约束其他变量最少的值)。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- 例如,让我们考虑变量C。如果我们将`Tue`分配给它,我们将对所有B、E和F施加约束。然而,如果我们选择`Wed`,我们将只对B和E施加约束。因此,选择`Tue`可能更好。
|
- 例如,让我们考虑变量 C。如果我们将`Tue`分配给它,我们将对所有 B、E 和 F 施加约束。然而,如果我们选择`Wed`,我们将只对 B 和 E 施加约束。因此,选择`Tue`可能更好。
|
||||||
|
|
||||||
- 总之,优化问题可以用多种方式来表述。在这,我们考虑了局部搜索、线性规划和约束满足。
|
- 总之,优化问题可以用多种方式来表述。在这,我们考虑了局部搜索、线性规划和约束满足。
|
||||||
|
|||||||
Reference in New Issue
Block a user