动态规划.md

This commit is contained in:
yinkanglong_lab
2021-03-31 22:34:12 +08:00
parent 334ad877a4
commit 083fa1ae5f
31 changed files with 562 additions and 146 deletions

View File

@@ -61,4 +61,11 @@
* How it works 为什么
* What's more 还有什么
* 我觉得以后做关于技术或者只是类的笔记都可以这么做。
* 究极哲学问题:是什么、怎么做、为什么。
* 究极哲学问题:是什么、怎么做、为什么。
### 说明
* 不能过度使用列表。
* 普通的论述直接分段。或者使用>表示引用。
* 而不是一直过度使用列表。用来缩进。
* 换一个更好的md渲染引擎。

View File

@@ -2,7 +2,7 @@
> 每日计划包括三方面的内容:数据结构与算法、基础知识与项目经历、联邦学习与恶意软件
- [ ] 数据结构与算法——分治法整理完成
- [x] 数据结构与算法——分治法整理完成
- [x] 基础知识与项目经历——操作系统整理完成part2
- [ ] 联邦学习与恶意软件——TensorFlow学习开始part1

View File

@@ -2,8 +2,8 @@
> 每日计划包括三方面的内容:数据结构与算法、基础知识与项目经历、联邦学习与恶意软件
- [ ] 数据结构与算法——动态规划和贪心整理完成
- [ ] 基础知识与项目经历——数据库整理完成
- [x] 数据结构与算法——动态规划和贪心整理完成
- [x] 基础知识与项目经历——数据库整理完成
- [ ] 联邦学习与恶意软件——TensorFlow学习完成part2
## 收获

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

View File

@@ -4,11 +4,11 @@
记 A-\>B 表示 A 函数决定 B,也可以说 B 函数依赖于 A。
如果 {A1,A2,... ,An} 是关系的一个或多个属性的集合,该集合函数决定了关系的其它所有属性并且是最小的,那么该集合就称为键码。
如果 {A1,A2,... ,An} 是关系的一个或多个属性的集合,该集合函数决定了关系的其它所有属性并且是最小的,那么该集合就称为**键码**。
对于 A-\>B,如果能找到 A 的真子集 A',使得 A'-\> B,那么 A-\>B 就是部分函数依赖,否则就是完全函数依赖。
对于 A-\>B,如果能找到 A 的真子集 A',使得 A'-\> B,那么 A-\>B 就是**部分函数依赖**,否则就是**完全函数依赖**。
对于 A-\>B,B-\>C,则 A-\>C 是一个传递函数依赖。
对于 A-\>B,B-\>C,则 A-\>C 是一个**传递函数依赖**。
## 2 异常
@@ -21,6 +21,8 @@
| 2 | 学生-2 | 学院-2 | 院长-2 | 课程-1 | 100 |
| 3 | 学生-3 | 学院-2 | 院长-2 | 课程-2 | 95 |
![](image/2021-03-31-15-16-13.png)
不符合范式的关系,会产生很多异常,主要有以下四种异常:
- 冗余数据:例如 `学生-2` 出现了两次。
@@ -38,6 +40,11 @@
属性不可分。
* 下图不符合第一范式
![](image/2021-03-31-15-14-17.png)
* 符合第一范式的属性
![](image/2021-03-31-15-14-44.png)
### 2. 第二范式 (2NF)
每个非主属性完全函数依赖于键码。
@@ -115,3 +122,11 @@ Sname, Sdept 和 Mname 都部分依赖于键码,当一个学生选修了多门
| :---: | :---: |
| 学院-1 | 院长-1 |
| 学院-2 | 院长-2 |
### 总结
* 1NF: 字段是最小的的单元不可再分
* 2NF:满足1NF,表中的字段必须完全依赖于全部主键而非部分主键 (一般我们都会做到)
* 3NF:满足2NF,非主键外的所有字段必须互不依赖
* 4NF:满足3NF,消除表中的多值依赖

View File

@@ -104,6 +104,8 @@ $$
4. 设计**递归的终止条件**。一旦开始递归,就要设计递归的终止条件。
5. **递归前的数据处理**。在递归前需要处理的数据,提供给递归函数。
6. **递归后的数据处理**。在递归后需要处理的数据。提供给上层函数和最终结果。
> 有一种特殊的递归处理方式。在递归前修改标志位。在递归后。将标志位还原。因为递归本质上是深度优先。所以,这个标志位只会影响一条路径上的问题。
### 递归的终止条件
* 终止递归:终止递归有两种思路。

View File

@@ -18,9 +18,9 @@
### 分治法的步骤
一般来说,分治法的求解过程由以下三个阶段组成:
1. 划分:既然是分治,当然需要把规模为n的原问题划分为k个规模较小的子问题,并尽量使这k个子问题的规模大致相同。
2. 求解子问题:各子问题的解法与原问题的解法通常是相同的,可以用递归的方法求解各个子问题,有时递归处理也可以用循环来实现。
3. 合并:把各个子问题的解合并起来,合并的代价因情况不同有很大差异,分治算法的有效性很大程度上依赖于合并的实现。
1. **划分divede**:既然是分治,当然需要把规模为n的原问题划分为k个规模较小的子问题,并尽量使这k个子问题的规模大致相同。
2. **解决conquer**:各子问题的解法与原问题的解法通常是相同的,可以用递归的方法求解各个子问题,有时递归处理也可以用循环来实现。
3. **合并merge**:把各个子问题的解合并起来,合并的代价因情况不同有很大差异,分治算法的有效性很大程度上依赖于合并的实现。
在用分治法设计算法时,最好使子问题的规模大致相同。即将一个问题分成大小相等的k个子问题的处理方法是行之有效的。这种使子问题规模大致相等的做法是出自一种平衡(balancing)子问题的思想,它几乎总是比子问题规模不等的做法要好。
@@ -88,100 +88,40 @@ f(n) = f (n-1) *a 如果n > 1
## 4 分治法应用
### 排列问题
### 整数划分问题
### 1排列问题√
### 9整数划分问题√
### 二分搜索问题√
O(\log n)
* 迭代空间O(1)
* 递归空间O(\log n)
### 大数乘法√
O(n^(log 3))
### 矩阵乘法√
### 2大数乘法√
### 3矩阵乘法√
### 快速排序√
### 合并排序√
O(n\log n)
### 线性时间选择√
* 选择最大最小元素-分治或暴力
O(n)
* 选择第k小元素-快排的一半
O(n)
### 最近点对问题√
### 棋盘覆盖问题
### 8最近点对问题√
### 10棋盘覆盖问题√
### 7数组中的逆序对√
## 5 减治法应用
### 拓扑排序
### 生成子集
* 比特串方法
### 4拓扑排序√
### 11生成子集√
### 假币问题
### 俄式乘法
### 约瑟夫问题
### 5约瑟夫问题√
### gcd欧几里得算法
### 插值查找
### 二叉树查找
### 插值查找√
### 二叉树查找√
## 6 变治法应用
### 元素唯一性-预排序算法
### 6元素唯一性-预排序算法√
### 模式计算-预排序算法
* 问题描述
在给定的数字列表中最经常出现的一个数值称为模式(Mode)。 (例:5,1,5,7,6,5,7,模式是5)
* 暴力法O(n^2)
* 预排序O(n\log n)
### 线性方程组-高斯小区算法
* 问题描述
n个线性方程构成的n 元联立方程组
* 高斯消去O(n^3)
### 线性方程组-高斯消去算法
### 霍纳法则
* 问题描述
针对一个给定的x 的多项式p(x) = anxn + an-1xn-1 + … + a1x + a0 求值
* 思想
它不断地把x作为公因子从降次以后的剩余多项式中提取出来:(x) =(…(anx + an-1)x + ..)x + a0
* 例子
```
p(x) = 2x4 - x3 + 3x2 + x - 5
= x(2x3 - x2 + 3x + 1 )- 5
= x(x(2 x2 - x +3)+1)-5
= x(x(x(2 x-1)+3)+1)-5
```
### lcm 最小公倍数
### AVL树
### 2-3树
### 堆排序
### 二进制幂
### lcm最小公倍数
### AVL树√
### 2-3树√
### 堆排序√
### 二进制幂√

View File

@@ -16,10 +16,14 @@ R是由n个元素构成的序列集合,R={r1, r2, … ,rn},求R的全排列p
perm(R)=(r1)perm(R1)∪(r2)perm(R2)∪(r3)perm(R3)
### 策略选择
* 算法思想:分治法
### 算法设计
分治法
依次将待排列的数组的后n-1个元素与第一个元素交换,则每次递归处理的都是后n-1个元素的全排列。当数组元素仅有一个时为此递归算法的出口。
1. 划分:依次将待排列的数组的后n-1个元素与第一个元素交换。
2. 解决:则每次递归处理的都是后n-1个元素的全排列。
3. 合并:当数组元素仅有一个时为此递归算法的出口。即开始合并。
```
算法 perm(Type list[], int k, int m)
//生成列表list的全排列

View File

@@ -0,0 +1,96 @@
## 棋盘覆盖问题
### 问题描述
棋盘覆盖问题。有一个$2^k∗2^k$的方格棋盘,恰有一个方格是黑色的,其他为白色。你的任务是用包含3个方格的L型牌覆盖所有白色方格。黑色方格不能被覆盖,且任意一个白色方格不能同时被两个或更多牌覆盖。如图所示为L型牌的4种旋转方式。
求解棋盘覆盖的方法。
![](image/2021-03-31-20-57-39.png)
### 问题分析
* 问题分类:
### 策略选择
* 数据结构:线性数据结构
* 算法思想:分治法
### 算法设计
分治三步骤
1. 划分问题:将$2^k∗2^k$的棋盘划分为 $2^{k−1}∗2^{k−1}$这样的子棋盘4块。
2. 递归求解:递归填充各个格子,填充分为四个情况,在下面会有解释,递归出口为 k=0k=0也就是子棋盘方格数为1。
3. 合并问题:不需要合并子问题。
分治方法:将棋盘等分为四块。然后分为以下两种情况。
1. 额外的方格在某一块中,不需要处理该区块。
2. 额外的方格不在某一块中。则添加一个L型牌覆盖三个没有额外方格的块。
重复以上步骤直到能完全覆盖。
![](image/2021-03-31-21-01-21.png)
### 算法分析
### 算法实现
```C++
void chessBoard(int row, int column, int x, int y, int siz) {
// 递归出口
if(siz == 1) {
return;
}
// 对半划分成2^(siz - 1) * 2^(siz - 1)的棋盘
int s = siz / 2;
// L型牌编号自增
int t = ++number;
// 中间点,以此判别(x,y)在哪个子棋盘中
int centerRow = row + s;
int centerColumn = column + s;
// 黑色方格在左上子棋盘
if(x < centerRow && y < centerColumn) {
chessBoard(row, column, x, y, s);
} else {
// 不在则填充左上子棋盘的右下角
chess[centerRow - 1][centerColumn - 1] = t;
// 然后覆盖其他格子,注意这时(x,y)要看做已填充位置
chessBoard(row, column, centerRow - 1, centerColumn - 1, s);
}
// 黑色方格在右上子棋盘
if(x < centerRow && y >= centerColumn) {
chessBoard(row, centerColumn, x, y, s);
} else {
// 不在则填充右上子棋盘的左下角
chess[centerRow - 1][centerColumn] = t;
// 然后覆盖其他格子,注意这时(x,y)要看做已填充位置
chessBoard(row, centerColumn, centerRow - 1, centerColumn, s);
}
// 黑色方格在左下子棋盘
if(x >= centerRow && y < centerColumn) {
chessBoard(centerRow, column, x, y, s);
} else {
// 不在则填充左下子棋盘的右上角
chess[centerRow][centerColumn - 1] = t;
// 然后覆盖其他格子,注意这时(x,y)要看做已填充位置
chessBoard(centerRow, column, centerRow, centerColumn - 1, s);
}
// 黑色方格在右下子棋盘
if(x >= centerRow && y >= centerColumn) {
chessBoard(centerRow, centerColumn, x, y, s);
} else {
// 不在则填充右下子棋盘的左上角
chess[centerRow][centerColumn] = t;
// 然后覆盖其他格子,注意这时(x,y)要看做已填充位置
chessBoard(centerRow, centerColumn, centerRow, centerColumn, s);
}
}
```

View File

@@ -0,0 +1,119 @@
## 生成子集问题——迭代
### 问题描述
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例 1:
```
输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
```
### 问题分析
### 策略选择
* 算法思想:迭代求解
### 算法设计
* 迭代思想,减治思想。每次选择一个元素,参与构建子集。
* 首先对元素去重。记录重复元素的个数。如果多个重复元素分散在其他数组中没有意义。所有多个重复元素在一起的情况下组合到之前的解集中。
* 这也算是一种动态规划的思想?每次都利用之前构建好的集合。构成新的解集集合。
### 算法分析
* 时间复杂度:$O(n×2^n)$
* 空间复杂度:$O(n)$.结果一般不算在空间复杂度当中。
### 算法实现
```C++
class Solution {
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
vector<vector<int>> result;
vector<int> m(30, 0);
vector<int> unique_nums;
for (int i = 0; i < nums.size(); i++) {
if (m[nums[i] + 10] == 0) {
unique_nums.push_back(nums[i]);
}
m[nums[i] + 10]++;
}
result.push_back(vector<int>());
for(int i=0;i<unique_nums.size();i++){
int n = result.size();
for(int j=0;j<n;j++){
vector<int> vec(result[j]);
for(int k=0;k<m[unique_nums[i]+10];k++){
vec.push_back(unique_nums[i]);
result.push_back(vec);
}
}
}
return result;
}
};
```
## 生成子集问题——递归
### 问题分析
### 策略选择
* 算法思想:迭代求解
### 算法设计
### 算法分析
* 时间复杂度:$O(n×2^n)$
* 空间复杂度:$O(n)$.结果一般不算在空间复杂度当中。
### 算法实现
``` C++
void back(vector<int>nums, vector<vector<int>>& r, ctor<int>& m) {
if (nums.size() == 0) {
vector<int> vec1;
r.push_back(vec1);
return;
}
int temp = nums.back();
nums.pop_back();
back(nums, r, m);
int n = r.size();
for (int j = 0; j < n;j++) {
vector<int> temp_v(r[j]);
for (int i = 0; i < m[temp + 10]; i++) {
temp_v.push_back(temp);
r.push_back(temp_v);
}
}
return;
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
vector<vector<int>> result;
vector<int> m(30, 0);
vector<int> unique_nums;
for (int i = 0; i < nums.size(); i++) {
if (m[nums[i] + 10] == 0) {
unique_nums.push_back(nums[i]);
}
m[nums[i] + 10]++;
}
back(unique_nums, result, m);
return result;
}
```

View File

@@ -141,14 +141,14 @@ $$
> 该方法比较复杂。
1. 首先根据手推的运算规则。发现在计算过程中需要实现四种计算。分别是:乘法计算、加法计算、减法计算、位移计算(乘以10的指数n。表示在末位添加n个零)
2. 分别实现。三种计算方式。并对高位的零进行处理。但是应该至少保留1位数(这位数如果是零。则表示该数字为零。)
3. 然后实现递归乘法运算。分以下几种情况实现
1. 划分divide:首先根据手推的运算规则。发现在计算过程中需要实现四种计算。分别是:乘法计算、加法计算、减法计算、位移计算(乘以10的指数n。表示在末位添加n个零)分别实现。三种计算方式。并对高位的零进行处理。但是应该至少保留1位数(这位数如果是零。则表示该数字为零。)
2. 解决conquer:然后实现递归乘法运算。分以下几种情况实现
1. 递归的接口:两个大数。
2. 递归的返回值:大数乘积的结果。
3. 递归的处理:将大数分为两部分。分别实现`a*c,b*d,(a+b)*(c+d)`实现递归。
4. 递归的终止条件。相乘的两个数只有1位数字。结果如果为零,应该保留1位零。
5. 递归前的处理:数字对齐。在高位添加零。使得两个数字对齐。
3. 合并merge:
6. 递归后的处理:将成绩的结果进行加法计算、减法计算、唯一计算。得到最后的结果
4. 难点在于:进行分治前:高位补零进行对齐。返回结果前去除高位多余的零,如果是0,则至少保留1位。

View File

@@ -1,11 +1,26 @@
# 矩阵乘法
## 1 矩阵乘法-STRASSEN算法
## 1 矩阵乘法-蛮力法
### 问题描述
![](image/2021-03-31-21-05-48.png)
### 算法原理
### 问题分析
### 算法设计
问题归结为计算元素C[i,j]。依据定义来计算A和B的乘积矩阵C,则每计算C的一个元素C[i][j],需要做n次乘法和n-1次加法。C的规模为n*n。
### 算法分析
* 时间复杂度O(n^3)
### 算法实现
## 2 矩阵乘法-STRASSEN算法
### 算法设计
算法的基本思想在于以增加加减法的次数来减少乘法次数:
用了7次n/2 x n/2 矩阵乘法和18次n/2 x n/2 矩阵的加法
@@ -31,14 +46,18 @@ d7 = (a12 – a22) ( b21 + b22)
```
* 将分块矩阵乘法进行合并
```
C11 =
C12 =
C21 =
C22 =
```
### 算法效率
* 时间复杂度:T (n) =$O(n^{log7})= O(n^{2.81})$
### 算法实现
```
T (n) = m 若n=1
=7T (n/2) + 18(n/2)2 a 若n≥ 2
```
```
T (n) = mnlog7+6anlog7-6an2
即运行时间为Θ (nlog7)= O(n2.81)
```

View File

@@ -1,14 +1,12 @@
## 拓扑排序-减治法
### 问题描述
考虑五门必修课的一个集合{C1, C2, C3, C4, C5},
一个在职的学生必须在某个阶段修完这几门课程。
可以按照任何次序学习这些课程,只要满足下面这些条件:
C1和C2没有任何先决条件;
修完C1 和C2才能修C3;
修完C3 才能修C4;
修完C3、C4才能修C5;
这个学生每个学期只能修一门课程。
考虑五门必修课的一个集合{C1, C2, C3, C4, C5},一个在职的学生必须在某个阶段修完这几门课程。可以按照任何次序学习这些课程,只要满足下面这些条件:
* C1和C2没有任何先决条件;
* 修完C1 和C2才能修C3;
* 修完C3 才能修C4;
* 修完C3、C4才能修C5;
* 这个学生每个学期只能修一门课程。
若用一个图来建模,它的顶点代表课程,有向边表示先决条件,该问题为:是否可以按照这种次序列出它的顶点,使得对于图中每一条边来说,边的起始顶点总是排在边的结束顶点之前。
@@ -21,8 +19,8 @@ C1和C2没有任何先决条件;
基于减(减一)治技术的一个直接实现:重复以下过程:
在余下的有向图中求出一个源,它是一个没有输入边的顶点;
然后把该源和所有从它出发的边都删除。(如果有多个这样的源,可以任意选择一个;如果这样的源不存在,算法停止,因为该问题是无解的)
顶点被删除的次序就是拓扑排序的一个解。
1. 在余下的有向图中求出一个源,它是一个没有输入边的顶点;
2. 然后把该源和所有从它出发的边都删除。(如果有多个这样的源,可以任意选择一个;如果这样的源不存在,算法停止,因为该问题是无解的)
3. 顶点被删除的次序就是拓扑排序的一个解。
### 拓扑排序-深度搜索

View File

@@ -2,7 +2,7 @@
### 问题描述
一堆人围城环。每次淘汰第m个人。最终剩下谁。
### 问题分析

View File

@@ -0,0 +1,49 @@
# 最近对问题
## 1 最近对问题 暴力求解
### 问题描述
* 给定平面上n个点。找其中的一对点。使得在n个点组成的所有点对中,该点对之间的距离最小。
### 问题分析
* 找出一个包含n个点的集合中距离最近的两个点。
* 题直观的解决方法便是Brute Force(暴力求解)。时间复杂度为O(n^2)
### 选择策略
* 分别计算每一点对之间的距离,然后从中找出距离最小的那一对。为了避免同一点对计算两次,可以只考虑i<j的点对(Pi, Pj)
### 算法设计
* 算法 bruteForceClosesPoints(P)
```
//蛮力法求解平面中距离最近的两点
//输入:一个n(n≥2)个点的列表P,P1=(x1, y1),…,Pn=(xn, yn)
//输出:两个最近点的下标
dmin←∞
for i←0 to n-2 do
for j←i+1 to n-1 do
d←(xi-xj)2+(yi-yj)2
if d<dmin
dmin←d; index1←i; index2←j;
return index1,index2
```
### 正确性证明
### 算法分析
$O(n^2)$
### 程序设计
## 2 最近对问题——分治法
> 参考文献
> * [https://blog.csdn.net/sinat_35678407/article/details/82874216](https://blog.csdn.net/sinat_35678407/article/details/82874216)
> * [https://zhuanlan.zhihu.com/p/74905629](https://zhuanlan.zhihu.com/p/74905629)
> * [http://www.360doc.com/content/19/0303/18/32937624_818850914.shtml](http://www.360doc.com/content/19/0303/18/32937624_818850914.shtml)
### 算法设计
### 正确性证明
### 算法分析
### 算法实现

View File

@@ -0,0 +1,28 @@
## 整数划分
### 问题描述
将给定正整数n表示成一系列正整数之和n=n1+n2+…+nk,其中n1≥n2≥…≥nk≥1,k≥1。求最大加数不大于m的划分个数p(n,m)。
### 算法设计
![](image/2021-03-31-16-39-06.png)
### 算法实现
```C++
#include<iostream>
using namespace std;
int q(int n,int m)
{
if((n<1)||(m<1))
return 0;
if((n == 1)||(m == 1))
return 1;
if(n < m)
return q(n,n);
if(n == m)
return q(n,m-1)+1;
return q(n,m-1)+q(n-m,m);
```

View File

@@ -84,6 +84,10 @@ $$
### 11最优二叉搜索树问题
### 12 n个骰子的点数
### 13 正则表达式的匹配
### 12 序列匹配问题
----

View File

@@ -8,7 +8,8 @@
### 问题分析
* 可以使用动态规划。将字符串的规模增长的方向,作为动态变化的方向。状态变量表示当前的正则表达式,能够与之匹配。当规模发生变化时,保证字符串-1和正则表达式-1能够匹配。其实在这里感觉规模增长的方向,确实是有两个。但是状态变量的设置比较精巧。
* 可以使用动态规划。将字符串的规模增长的方向和pattern规模增长的方向,作为动态变化的方向。状态变量表示当前的正则表达式,能够与之匹配。当规模发生变化时,保证字符串-1和正则表达式-1能够匹配。其实在这里感觉规模增长的方向,确实是有两个。但是状态变量的设置比较精巧。
* 这与最长公共子序列,完全是同一类问题。都是两个方向的动态规划规模增长。包括str和pattern两个方向。
### 问题分类
@@ -20,7 +21,7 @@
* 动态规划
### 算法设计
![](image/2021-03-31-22-04-55.png)
* 设s的长度为n ,p 的长度为 m ;将 s 的第 i 个字符记为$s_i$,p 的第 j个字符记为 $p_j$ ,将 s 的前 i 个字符组成的子字符串记为s[:i] , 同理将 p 的前 j 个字符组成的子字符串记为 p[:j]p[:j] 。
* 因此,本题可转化为求s[:n] 是否能和p[:m] 匹配。

View File

@@ -0,0 +1,158 @@
## 最长公共子序列
### 问题描述
求数组A和B的最长公共子序列
### 问题分析
1. 子序列(subsequence): 一个特定序列的子序列就是将给定序列中零个或多个元素去掉后得到的结果(不改变元素间相对次序)。例如序列<A,B,C,B,D,A,B>的子序列有:<A,B>、<B,C,A>、<A,B,C,D,A>等。
2. 公共子序列(common subsequence): 给定序列X和Y,序列Z是X的子序列,也是Y的子序列,则Z是X和Y的公共子序列。例如X=[A,B,C,B,D,A,B],Y=[B,D,C,A,B,A[,那么序列Z=[B,C,A]为X和Y的公共子序列,其长度为3。但Z不是X和Y的最长公共子序列,而序列[B,C,B,A]和[B,D,A,B]也均为X和Y的最长公共子序列,长度为4,而X和Y不存在长度大于等于5的公共子序列。对于序列[A,B,C]和序列[E,F,G]的公共子序列只有空序列[]。
3. 最长公共子序列:给定序列X和Y,从它们的所有公共子序列中选出长度最长的那一个或几个。
4. 子串: 将一个序列从最前或最后或同时删掉零个或几个字符构成的新系列。区别与子序列,子序列是可以从中间抠掉字符的。cnblogs这个字符串中子序列有多少个呢?很显然有27个,比如其中的cb,cgs等等都是其子序列
![](image/2021-03-31-21-26-52.png)
### 策略选择
* 数据结构:线性数组
* 算法思想:动态规划
### 算法设计
设X=x1,x2,x3,x4...,xm,Y=y1,y2,y3,y4...,yn为两个序列,Z=z1,z2,z3,z4...,zk是他们的任意公共子序列.所以如果用一个二维数组C表示字符串X和Y中对应的前i,前j个字符的LCS的长度话。
* 确定**状态转移变量**Zk-1。它表示X的子串Xn-1和Y的子串Ym-1的最长公共子序列。
* 确定状态转移方程:
1. 如果xm = yn,则zk = xm = yn 且 Zk-1是Xm-1和Yn-1的一个LCS
2. 如果xm != yn 且 zk != xm,则Z是Xm-1和Y的一个LCS
3. 如果xm != yn 且 zk != yn,则Z是X和Yn-1的一个LCS
![](image/2021-03-31-22-16-12.png)
* 状态转移过程
![](image/2021-03-31-22-19-07.png)
### 算法分析
* 时间复杂度为O(m*n)
### 算法实现
```C++
#include <iostream>
#include <string>
#include <stack>
using namespace std;
void LCS(string s1,string s2)
{
int m=s1.length()+1;
int n=s2.length()+1;
int **c;
int **b;
c=new int* [m];
b=new int* [m];
for(int i=0;i<m;i++)
{
c[i]=new int [n];
b[i]=new int [n];
for(int j=0;j<n;j++)
b[i][j]=0;
}
for(int i=0;i<m;i++)
c[i][0]=0;
for(int i=0;i<n;i++)
c[0][i]=0;
for(int i=0;i<m-1;i++)
{
for(int j=0;j<n-1;j++)
{
if(s1[i]==s2[j])
{
c[i+1][j+1]=c[i][j]+1;
b[i+1][j+1]=1; //1表示箭头为 左上
}
else if(c[i][j+1]>=c[i+1][j])
{
c[i+1][j+1]=c[i][j+1];
b[i+1][j+1]=2; //2表示箭头向 上
}
else
{
c[i+1][j+1]=c[i+1][j];
b[i+1][j+1]=3; //3表示箭头向 左
}
}
}
for(int i=0;i<m;i++) //输出c数组
{
for(int j=0;j<n;j++)
{
cout<<c[i][j]<<' ';
}
cout<<endl;
}
stack<char> same; //存LCS字符
stack<int> same1,same2; //存LCS字符在字符串1和字符串2中对应的下标,方便显示出来
for(int i = m-1,j = n-1;i >= 0 && j >= 0; )
{
if(b[i][j] == 1)
{
i--;
j--;
same.push(s1[i]);
same1.push(i);
same2.push(j);
}
else if(b[i][j] == 2)
i--;
else
j--;
}
cout<<s1<<endl; //输出字符串1
for(int i=0;i<m && !same1.empty();i++) //输出字符串1的标记
{
if(i==same1.top())
{
cout<<1;
same1.pop();
}
else
cout<<' ';
}
cout<<endl<<s2<<endl; //输出字符串2
for(int i=0;i<n && !same2.empty();i++) //输出字符串2的标记
{
if(i==same2.top())
{
cout<<1;
same2.pop();
}
else
cout<<' ';
}
cout<<endl<<"最长公共子序列为:";
while(!same.empty())
{
cout<<same.top();
same.pop();
}
cout<<endl<<"长度为:"<<c[m-1][n-1]<<endl;
for (int i = 0; i<m; i++)
{
delete [] c[i];
delete [] b[i];
}
delete []c;
delete []b;
}
int main()
{
string s1="ABCPDSFJGODIHJOFDIUSHGD";
string s2="OSDIHGKODGHBLKSJBHKAGHI";
LCS(s1,s2);
return 0;
}
```

View File

@@ -6,13 +6,13 @@
### 算法设计
* 状态定义: 设动态规划列表 dpdp ,dp[i]dp[i] 代表以元素 nums[i]nums[i] 为结尾的连续子数组最大和。
* **状态定义**: 设动态规划列表 dpdp ,dp[i]dp[i] 代表以元素 nums[i]nums[i] 为结尾的连续子数组最大和。
* 为何定义最大和 dp[i]dp[i] 中必须包含元素 nums[i]nums[i] :保证 dp[i]dp[i] 递推到 dp[i+1]dp[i+1] 的正确性;如果不包含 nums[i]nums[i] ,递推时则不满足题目的 连续子数组 要求。
* 转移方程: 若 dp[i-1] \leq 0dp[i−1]≤0 ,说明 dp[i - 1]dp[i−1] 对 dp[i]dp[i] 产生负贡献,即 dp[i-1] + nums[i]dp[i−1]+nums[i] 还不如 nums[i]nums[i] 本身大。
* **转移方程**: 若 dp[i-1] \leq 0dp[i−1]≤0 ,说明 dp[i - 1]dp[i−1] 对 dp[i]dp[i] 产生负贡献,即 dp[i-1] + nums[i]dp[i−1]+nums[i] 还不如 nums[i]nums[i] 本身大。
* 当 dp[i - 1] > 0dp[i−1]>0 时:执行 dp[i] = dp[i-1] + nums[i]dp[i]=dp[i−1]+nums[i]
* 当 dp[i - 1] \leq 0dp[i−1]≤0 时:执行 dp[i] = nums[i]dp[i]=nums[i] ;
* 初始状态: dp[0] = nums[0]dp[0]=nums[0],即以 nums[0]nums[0] 结尾的连续子数组最大和为 nums[0]nums[0] 。
* 返回值: 返回 dpdp 列表中的最大值,代表全局最大值。
* **初始状态**: dp[0] = nums[0]dp[0]=nums[0],即以 nums[0]nums[0] 结尾的连续子数组最大和为 nums[0]nums[0] 。
* **返回值**: 返回 dpdp 列表中的最大值,代表全局最大值。
![](image/2021-03-29-09-50-50.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -1,24 +0,0 @@
## 最近对问题
### 理解问题
* 找出一个包含n个点的集合中距离最近的两个点。
### 选择策略
* 分别计算每一点对之间的距离,然后从中找出距离最小的那一对。为了避免同一点对计算两次,可以只考虑i<j的点对(Pi, Pj)
### 算法设计
* 算法 bruteForceClosesPoints(P)
```
//蛮力法求解平面中距离最近的两点
//输入:一个n(n≥2)个点的列表P,P1=(x1, y1),…,Pn=(xn, yn)
//输出:两个最近点的下标
dmin←∞
for i←0 to n-2 do
for j←i+1 to n-1 do
d←(xi-xj)2+(yi-yj)2
if d<dmin
dmin←d; index1←i; index2←j;
return index1,index2
```
### 正确性证明
### 算法分析
$O(n^2)$
### 程序设计