mirror of
https://github.com/Estom/notes.git
synced 2026-04-05 11:57:37 +08:00
大整数乘法
This commit is contained in:
@@ -4,9 +4,10 @@
|
||||
- [x] 双指针环的入接点总结
|
||||
- [x] 字符串分割与格式化方法总结
|
||||
- [x] 位运算与补码总结
|
||||
- [ ] 大数运算
|
||||
- [x] 大数运算
|
||||
- [ ] n个骰子的点数
|
||||
- [ ] 约瑟夫问题
|
||||
- [ ] 手写所有排序
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -88,12 +88,12 @@ $$
|
||||
* 多分支递归:当一个问题可以分为多个相同的子问题的时候。在同一层进行多分支递归。每一层**多次调用递归函数**。如树和图的遍历问题,有多个下层子节点。
|
||||
## 3 递归的实现
|
||||
|
||||
### 递归前的预处理
|
||||
### 递归问题的分析
|
||||
1. 提取重复的逻辑,缩小问题规模。
|
||||
2. 只有写出**反向递推关系式**,才知道递归的参数、递归的返回值和递归的执行关系。
|
||||
|
||||
|
||||
### 递归的实现步骤
|
||||
### 递归算法的步骤
|
||||
|
||||
1. 设计**递归的参数**。首先确定每一次递归,上一层需要提供给下一层的内容。相当于上层---->下层的接口。
|
||||
2. 设计**递归的返回值**。设计递归需要返回的内容,即提供给上一层的内容。相当于下层---->上层的接口。
|
||||
|
||||
0
算法/A类:基本算法/3.4.cpp
Normal file
0
算法/A类:基本算法/3.4.cpp
Normal file
@@ -1,38 +1,333 @@
|
||||
# 大整数乘法
|
||||
|
||||
## 1 大整数乘法-分治法
|
||||
> 参考文献
|
||||
> * [大数乘法的问题及其高效的算法](https://blog.csdn.net/u010983881/article/details/77503519)
|
||||
|
||||
### 问题描述
|
||||
|
||||
设u 和v 是两个n 位的整数(二进制),传统的乘法算法需要(n2) 数字相乘来计算u 和v 的乘积。
|
||||
* 题目描述: 输出两个不超过100位的大整数的乘积。
|
||||
* 输入: 输入两个大整数,如1234567 和 123
|
||||
* 输出: 输出乘积,如:151851741
|
||||
* 数字以字符串的形式给出。
|
||||
```
|
||||
求 1234567891011121314151617181920 * 2019181716151413121110987654321 的乘积结果
|
||||
```
|
||||
|
||||
### 问题分析
|
||||
* 所谓大数相乘(Multiplication algorithm),就是指数字比较大,相乘的结果超出了基本类型的表示范围,所以这样的数不能够直接做乘法运算。参考了很多资料,包括维基百科词条Multiplication algorithm,才知道目前大数乘法算法主要有以下几种思路:
|
||||
|
||||
* 模拟小学乘法:最简单的乘法竖式手算的累加型;
|
||||
* 分治乘法:最简单的是Karatsuba乘法,一般化以后有Toom-Cook乘法;
|
||||
* 快速傅里叶变换FFT:(为了避免精度问题,可以改用快速数论变换FNTT),时间复杂度O(N lgN lglgN)。具体可参照Schönhage–Strassen algorithm;
|
||||
* 中国剩余定理:把每个数分解到一些互素的模上,然后每个同余方程对应乘起来就行;
|
||||
* Furer’s algorithm:在渐进意义上FNTT还快的算法。不过好像不太实用,本文就不作介绍了。大家可以参考维基百科Fürer’s algorithm
|
||||
|
||||
|
||||
## 1 大数乘法-模拟小学乘法
|
||||
|
||||
### 算法设计
|
||||
* 模拟乘法手算累加
|
||||
```
|
||||
7 8 9 6 5 2
|
||||
× 3 2 1 1
|
||||
-----------------
|
||||
7 8 9 6 5 2 <---- 第1趟
|
||||
7 8 9 6 5 2 <---- 第2趟
|
||||
.......... <---- 第n趟
|
||||
-----------------
|
||||
? ? ? ? ? ? ? ? <---- 最后的值用另一个数组表示
|
||||
```
|
||||
* 如上所示,乘法运算可以分拆为两步:
|
||||
1. 首先将数位反置。个位数在低位数组,高位数在高位数组。
|
||||
2. 是将乘数与被乘数逐位相乘将逐位相乘得到的结果,对应累计相加起来。r[i+j]+=a[i]*b[j]位置.
|
||||
3. 因为每个位置代表1位数。循环一边处理进位。/10的结果加到下一位。%10的结果保留到本位。
|
||||
|
||||
> 这个方法,简单易于解。只需要几行代码即可。
|
||||
|
||||
### 算法分析
|
||||
|
||||
* 时间复杂度O(n^2)
|
||||
* 空间复杂度O(n)
|
||||
|
||||
### 算法实现
|
||||
|
||||
```C++
|
||||
#include<iostream>
|
||||
#include<vector>
|
||||
#include<string>
|
||||
using namespace std;
|
||||
|
||||
vector<int> mul_mul(vector<int> a,vector<int> b);
|
||||
// 以字符串形式给出。还是得把这个形式改成整数数组比较好
|
||||
int main(){
|
||||
string a="123456789";
|
||||
string b="987654321";
|
||||
// 将字符串转换为整形数组。C的方法可以使用memset分配空间C++直接pushback
|
||||
// 将大数数组反向存储。个位存在最开始的位置。方便数组增长。
|
||||
vector<int> aa;
|
||||
for(int i=a.size()-1;i>=0;i--){
|
||||
aa.push_back((int)(a[i]-'0'));
|
||||
}
|
||||
vector<int> bb;
|
||||
for(int i=b.size()-1;i>=0;i--){
|
||||
bb.push_back((int)(b[i]-'0'));
|
||||
}
|
||||
vector<int> result = mul_mul(aa,bb);
|
||||
|
||||
// 将最终的整数数组转换成字符串。并输出
|
||||
string res;
|
||||
for(int i=result.size()-1;i>=0;i--){
|
||||
res+=to_string(result[i]);
|
||||
}
|
||||
// 去除前边多余的0
|
||||
res = res.substr(res.find_first_not_of('0'));
|
||||
cout<<res<<endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 模拟小学竖式
|
||||
vector<int> mul_mul(vector<int> a,vector<int> b){
|
||||
vector<int> a_b(a.size()+b.size()+1,0);
|
||||
// 小学乘法
|
||||
for(int i=0;i<a.size();i++){
|
||||
for(int j=0;j<b.size();j++){
|
||||
a_b[i+j] += a[i]*b[j];
|
||||
}
|
||||
}
|
||||
// 对a_b中的数值进行处理(进位处理)
|
||||
for(int i=0;i<a_b.size();i++){
|
||||
a_b[i+1]+= a_b[i]/10;
|
||||
a_b[i]%=10;
|
||||
}
|
||||
|
||||
return a_b;
|
||||
}
|
||||
```
|
||||
|
||||
## 2 大整数乘法-分治法Karatsuba算法
|
||||
|
||||
### 问题分析
|
||||
* 以上两种模拟乘法的手算累加型算法,他们都是模拟普通乘法的计算方式,时间复杂度都是$O(n^2)$,而这个Karatsuba算法,时间复杂度仅有 $O(n^{\log _{2}3})$
|
||||
* 下面,我就来介绍一下这个算法。Karatsuba于1960年发明在 $O(n^{\log _{2}3})$步骤内将两个n位数相乘的Karatsuba算法。它反证了安德雷·柯尔莫哥洛夫于1956年认为这个乘法需要 $\Omega (n^{2})$ 步骤的猜想。首先来看看这个算法是怎么进行计算的
|
||||
|
||||

|
||||
|
||||
> `ac*bd`在暴力乘法当中,需要四次乘法运算。`ad,ab,cd,cb`。但是通过分治法。可以使得ad+bc在一次乘法中实现。即`(a+c)*(b+d)-ac-bd=ad+cb`.降低了乘法运算的数量。
|
||||
|
||||
### 算法原理
|
||||
|
||||
* 把每个整数分为两部分,每部分为n/2位,则u 和v 可重写为u = w2n/2 + x 和v = y2n/2 +z
|
||||

|
||||
|
||||
* u 和v 的乘积可以计算为:
|
||||
```
|
||||
uv = (w2n/2 + x)(y2n/2 +z) = wy2n + (wz + xy) 2n/2 + xz
|
||||
```
|
||||
用2^n做乘法运算相当于简单地左移n 位,它需要θ( n) 时间。
|
||||
1. 我们假设要相乘的两个数是x * y。我们可以把x,y写成:
|
||||
$$
|
||||
x = a * 10^{n/2} + b
|
||||
\\
|
||||
y = c * 10^{n/2} + d
|
||||
$$
|
||||
2. 这里的n是数字的位数。如果是偶数,则a和b都是n/2位的。如果n是奇数,则你可以让a是n/2+1位,b是n/2位。(例如a = 12,b = 34;a = 123,b = 45),那么x*y就变成了:
|
||||
$$
|
||||
x*y = (a * 10^{n/2}+b) * (c * 10^{n/2}+d)
|
||||
$$
|
||||
3. 进一步计算,
|
||||
$$
|
||||
x*y = a * c * 10^n + (a * d + b * c) * 10^{n/2} + bd
|
||||
$$
|
||||
|
||||
* 考虑用以下恒等式计算wz + xy。
|
||||
```
|
||||
wz + xy = (w + x) ( y + z) – wy – xz
|
||||
```
|
||||
* 由于wy 和 xz 不需要做二次计算,结合以上二式,仅需3次乘法运算,即
|
||||
```
|
||||
uv = wy2n + (( w + x) ( y + z) – wy –xz) 2n/2 + xz
|
||||
```
|
||||
4. 对比之前的计算过程。结果已经呼之欲出了。这里唯一需要注意的两点就是:
|
||||
|
||||
> `(a * d + b * c)`的计算为了防止两次乘法,应该使用之前的计算这些乘法在算法里应该是递归实现的,数字很大时,先拆分,然后拆分出来的数字还是很大的话,就继续拆分,直到`a * b`已经是一个非常简单的小问题为之。这也是分治的思想。
|
||||
|
||||
### 算法设计
|
||||
|
||||
> 该方法比较复杂。
|
||||
|
||||
1. 首先根据手推的运算规则。发现在计算过程中需要实现四种计算。分别是:乘法计算、加法计算、减法计算、位移计算(乘以10的指数n。表示在末位添加n个零)
|
||||
2. 分别实现。三种计算方式。并对高位的零进行处理。但是应该至少保留1位数(这位数如果是零。则表示该数字为零。)
|
||||
3. 然后实现递归乘法运算。分以下几种情况实现
|
||||
1. 递归的接口:两个大数。
|
||||
2. 递归的返回值:大数乘积的结果。
|
||||
3. 递归的处理:将大数分为两部分。分别实现`a*c,b*d,(a+b)*(c+d)`实现递归。
|
||||
4. 递归的终止条件。相乘的两个数只有1位数字。结果如果为零,应该保留1位零。
|
||||
5. 递归前的处理:数字对齐。在高位添加零。使得两个数字对齐。
|
||||
6. 递归后的处理:将成绩的结果进行加法计算、减法计算、唯一计算。得到最后的结果
|
||||
|
||||
4. 难点在于:进行分治前:高位补零进行对齐。返回结果前去除高位多余的零,如果是0,则至少保留1位。
|
||||
|
||||
### 算法效率
|
||||
* 此方法产生以下递推式
|
||||
* 时间复杂度:$O(n^{log_2 3}) = O(n^{1.59})$
|
||||
* 空间复杂度O(nlog n)
|
||||
|
||||
### 算法实现
|
||||
|
||||
```C++
|
||||
#include<iostream>
|
||||
#include<vector>
|
||||
#include<string>
|
||||
using namespace std;
|
||||
|
||||
vector<int> mul_mul(vector<int>& a, vector<int>& b);
|
||||
// 大数加法
|
||||
vector<int> mul_divided(vector<int> x, vector<int> y);
|
||||
vector<int> mul_sum(vector<int> a, vector<int>b);
|
||||
vector<int> mul_minus(vector<int>a, vector<int>b);
|
||||
vector<int> mul_pow(vector<int>a, int n);
|
||||
// 以字符串形式给出。还是得把这个形式改成整数数组比较好
|
||||
int main() {
|
||||
string a = "99999999";
|
||||
string b = "77777777";
|
||||
// 将字符串转换为整形数组。C的方法可以使用memset分配空间C++直接pushback
|
||||
// 将大数数组反向存储。个位存在最开始的位置。方便数组增长。
|
||||
vector<int> aa;
|
||||
for (int i = a.size() - 1; i >= 0; i--) {
|
||||
aa.push_back((int)(a[i] - '0'));
|
||||
}
|
||||
vector<int> bb;
|
||||
for (int i = b.size() - 1; i >= 0; i--) {
|
||||
bb.push_back((int)(b[i] - '0'));
|
||||
}
|
||||
//vector<int> result = mul_mul(aa,bb);
|
||||
vector<int> result = mul_divided(aa, bb);
|
||||
// 将最终的整数数组转换成字符串。并输出
|
||||
string res;
|
||||
for (int i = result.size() - 1; i >= 0; i--) {
|
||||
res += to_string(result[i]);
|
||||
}
|
||||
// 去除前边多余的0
|
||||
// res = res.substr(res.find_first_not_of('0'));
|
||||
cout << res << endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 模拟小学竖式。反转的字符串。个位在前边。
|
||||
vector<int> mul_mul(vector<int>& a, vector<int>& b) {
|
||||
vector<int> a_b(a.size() + b.size(), 0);
|
||||
// 小学乘法,i位置×j位置。在i+j位置保存结果。最后处理进位。
|
||||
for (int i = 0; i < a.size(); i++) {
|
||||
for (int j = 0; j < b.size(); j++) {
|
||||
a_b[i + j] += a[i] * b[j];
|
||||
}
|
||||
}
|
||||
// 对a_b中的数值进行处理(进位处理)
|
||||
for (int i = 0; i < a_b.size(); i++) {
|
||||
a_b[i + 1] += a_b[i] / 10;
|
||||
a_b[i] %= 10;
|
||||
}
|
||||
//去除高位的0
|
||||
int i = a_b.size() - 1;
|
||||
while (a_b[i] == 0) {
|
||||
a_b.pop_back();
|
||||
i--;
|
||||
}
|
||||
return a_b;
|
||||
}
|
||||
|
||||
// 分治法。
|
||||
//反转的字符串。个位在前边。
|
||||
//假设等长只有等长的数据划分才会方便。
|
||||
// 如果等长情况下的处理
|
||||
//关于是相乘结果是0的情况应该特殊处理。
|
||||
vector<int> mul_divided(vector<int> x, vector<int> y) {
|
||||
//对x,y短的进行补齐.高位补齐0
|
||||
while (x.size() < y.size()) {
|
||||
x.push_back(0);
|
||||
}
|
||||
while (y.size() < x.size()) {
|
||||
y.push_back(0);
|
||||
}
|
||||
// 只在末端做一次乘法
|
||||
vector<int> result(x.size() + y.size());
|
||||
if (x.size() == 1 && y.size() == 1) {
|
||||
result[0] = x[0] * y[0];
|
||||
result[1] = result[0] / 10;
|
||||
result[0] = result[0] % 10;
|
||||
//去除高位的0
|
||||
int i = result.size() - 1;
|
||||
//但是保留唯一的0;用来表示这是一个0
|
||||
while (result.size()>0 && result[i] == 0) {
|
||||
result.pop_back();
|
||||
i--;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int n = x.size() / 2;
|
||||
// 分割成a,b,c,d四个部分
|
||||
vector<int> b(x.begin(), x.begin() + n);
|
||||
vector<int> a(x.begin() + n, x.end());
|
||||
vector<int> d(y.begin(), y.begin() + n);
|
||||
vector<int> c(y.begin() + n, y.end());
|
||||
|
||||
// 进行递归
|
||||
vector<int> mul1 = mul_divided(a, c);
|
||||
vector<int> mul2 = mul_divided(b, d);
|
||||
vector<int> mul_ab_cd = mul_divided(mul_sum(a, b), mul_sum(c, d));
|
||||
|
||||
// 加法。个位在前边。
|
||||
vector<int> sum_ac_bd = mul_sum(mul1, mul2);
|
||||
vector<int> mul3 = mul_minus(mul_ab_cd, sum_ac_bd);
|
||||
|
||||
|
||||
result = mul_sum(mul_sum(mul_pow(mul1, 2 * n), mul2), mul_pow(mul3, n));
|
||||
return result;
|
||||
}
|
||||
|
||||
// 大数加法。
|
||||
vector<int> mul_sum(vector<int> a, vector<int> b) {
|
||||
vector<int> result(max(a.size(), b.size()) + 1, 0);
|
||||
for (int i = 0; i < result.size() - 1; i++) {
|
||||
if (i < a.size()) {
|
||||
result[i] += a[i];
|
||||
}
|
||||
if (i < b.size()) {
|
||||
result[i] += b[i];
|
||||
}
|
||||
// 处理进位
|
||||
result[i + 1] += result[i] / 10;
|
||||
result[i] %= 10;
|
||||
}
|
||||
//去除高位的0
|
||||
int i = result.size() - 1;
|
||||
while (result.size() > 0 && result[i] == 0) {
|
||||
result.pop_back();
|
||||
i--;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// 大数减法(结果不可能是负数)
|
||||
vector<int> mul_minus(vector<int> a, vector<int>b) {
|
||||
vector<int> result(a.size(), 0);
|
||||
// 做减法
|
||||
for (int i = 0; i < a.size(); i++) {
|
||||
if (i < b.size()) {
|
||||
result[i] += a[i] - b[i];
|
||||
}
|
||||
else {
|
||||
result[i] += a[i];
|
||||
}
|
||||
// 处理借位
|
||||
if (result[i] < 0) {
|
||||
result[i] += 10;
|
||||
result[i + 1] -= 1;
|
||||
}
|
||||
}
|
||||
//去除高位的0
|
||||
int i = result.size() - 1;
|
||||
while (result.size() > 0 && result[i] == 0) {
|
||||
result.pop_back();
|
||||
i--;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// 向右移位
|
||||
vector<int> mul_pow(vector<int> a, int n) {
|
||||
vector<int> result(n, 0);
|
||||
result.insert(result.end(), a.begin(), a.end());
|
||||
//去除高位的0
|
||||
int i = result.size() - 1;
|
||||
while (result.size() > 0 && result[i] == 0) {
|
||||
result.pop_back();
|
||||
i--;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
```
|
||||
T (n) = d 若n = 1
|
||||
= 3T(n/2) +bn 若 n>1
|
||||
```
|
||||
* 定理得出
|
||||
$$
|
||||
T(n) =Θ(n^log 3) = O(n^1.59)
|
||||
$$
|
||||
170
算法/A类:基本算法/4.2.cpp
Normal file
170
算法/A类:基本算法/4.2.cpp
Normal file
@@ -0,0 +1,170 @@
|
||||
#include<iostream>
|
||||
#include<vector>
|
||||
#include<string>
|
||||
using namespace std;
|
||||
|
||||
vector<int> mul_mul(vector<int>& a, vector<int>& b);
|
||||
// 大数加法
|
||||
vector<int> mul_divided(vector<int> x, vector<int> y);
|
||||
vector<int> mul_sum(vector<int> a, vector<int>b);
|
||||
vector<int> mul_minus(vector<int>a, vector<int>b);
|
||||
vector<int> mul_pow(vector<int>a, int n);
|
||||
// 以字符串形式给出。还是得把这个形式改成整数数组比较好
|
||||
int main() {
|
||||
string a = "999999999999999999999999999999999999999999";
|
||||
string b = "777777777777777777777777777777777777777777777777";
|
||||
// 将字符串转换为整形数组。C的方法可以使用memset分配空间C++直接pushback
|
||||
// 将大数数组反向存储。个位存在最开始的位置。方便数组增长。
|
||||
vector<int> aa;
|
||||
for (int i = a.size() - 1; i >= 0; i--) {
|
||||
aa.push_back((int)(a[i] - '0'));
|
||||
}
|
||||
vector<int> bb;
|
||||
for (int i = b.size() - 1; i >= 0; i--) {
|
||||
bb.push_back((int)(b[i] - '0'));
|
||||
}
|
||||
// vector<int> result = mul_mul(aa,bb);
|
||||
vector<int> result = mul_divided(aa, bb);
|
||||
// 将最终的整数数组转换成字符串。并输出
|
||||
string res;
|
||||
for (int i = result.size() - 1; i >= 0; i--) {
|
||||
res += to_string(result[i]);
|
||||
}
|
||||
// 去除前边多余的0
|
||||
// res = res.substr(res.find_first_not_of('0'));
|
||||
cout << res << endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 模拟小学竖式。反转的字符串。个位在前边。
|
||||
vector<int> mul_mul(vector<int>& a, vector<int>& b) {
|
||||
vector<int> a_b(a.size() + b.size(), 0);
|
||||
// 小学乘法,i位置×j位置。在i+j位置保存结果。最后处理进位。
|
||||
for (int i = 0; i < a.size(); i++) {
|
||||
for (int j = 0; j < b.size(); j++) {
|
||||
a_b[i + j] += a[i] * b[j];
|
||||
}
|
||||
}
|
||||
// 对a_b中的数值进行处理(进位处理)
|
||||
for (int i = 0; i < a_b.size(); i++) {
|
||||
a_b[i + 1] += a_b[i] / 10;
|
||||
a_b[i] %= 10;
|
||||
}
|
||||
//去除高位的0
|
||||
int i = a_b.size() - 1;
|
||||
while (a_b[i] == 0) {
|
||||
a_b.pop_back();
|
||||
i--;
|
||||
}
|
||||
return a_b;
|
||||
}
|
||||
|
||||
// 分治法。
|
||||
//反转的字符串。个位在前边。
|
||||
//假设等长只有等长的数据划分才会方便。
|
||||
// 如果等长情况下的处理
|
||||
//关于是相乘结果是0的情况应该特殊处理。
|
||||
vector<int> mul_divided(vector<int> x, vector<int> y) {
|
||||
//对x,y短的进行补齐.高位补齐0
|
||||
while (x.size() < y.size()) {
|
||||
x.push_back(0);
|
||||
}
|
||||
while (y.size() < x.size()) {
|
||||
y.push_back(0);
|
||||
}
|
||||
// 只在末端做一次乘法
|
||||
vector<int> result(x.size() + y.size());
|
||||
if (x.size() == 1 && y.size() == 1) {
|
||||
result[0] = x[0] * y[0];
|
||||
result[1] = result[0] / 10;
|
||||
result[0] = result[0] % 10;
|
||||
//去除高位的0
|
||||
int i = result.size() - 1;
|
||||
//但是保留唯一的0;用来表示这是一个0
|
||||
while (result.size()>0 && result[i] == 0) {
|
||||
result.pop_back();
|
||||
i--;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int n = x.size() / 2;
|
||||
// 分割成a,b,c,d四个部分
|
||||
vector<int> b(x.begin(), x.begin() + n);
|
||||
vector<int> a(x.begin() + n, x.end());
|
||||
vector<int> d(y.begin(), y.begin() + n);
|
||||
vector<int> c(y.begin() + n, y.end());
|
||||
|
||||
// 进行递归
|
||||
vector<int> mul1 = mul_divided(a, c);
|
||||
vector<int> mul2 = mul_divided(b, d);
|
||||
vector<int> mul_ab_cd = mul_divided(mul_sum(a, b), mul_sum(c, d));
|
||||
|
||||
// 加法。个位在前边。
|
||||
vector<int> sum_ac_bd = mul_sum(mul1, mul2);
|
||||
vector<int> mul3 = mul_minus(mul_ab_cd, sum_ac_bd);
|
||||
|
||||
|
||||
result = mul_sum(mul_sum(mul_pow(mul1, 2 * n), mul2), mul_pow(mul3, n));
|
||||
return result;
|
||||
}
|
||||
|
||||
// 大数加法。
|
||||
vector<int> mul_sum(vector<int> a, vector<int> b) {
|
||||
vector<int> result(max(a.size(), b.size()) + 1, 0);
|
||||
for (int i = 0; i < result.size() - 1; i++) {
|
||||
if (i < a.size()) {
|
||||
result[i] += a[i];
|
||||
}
|
||||
if (i < b.size()) {
|
||||
result[i] += b[i];
|
||||
}
|
||||
// 处理进位
|
||||
result[i + 1] += result[i] / 10;
|
||||
result[i] %= 10;
|
||||
}
|
||||
//去除高位的0
|
||||
int i = result.size() - 1;
|
||||
while (result.size() > 0 && result[i] == 0) {
|
||||
result.pop_back();
|
||||
i--;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// 大数减法(结果不可能是负数)
|
||||
vector<int> mul_minus(vector<int> a, vector<int>b) {
|
||||
vector<int> result(a.size(), 0);
|
||||
// 做减法
|
||||
for (int i = 0; i < a.size(); i++) {
|
||||
if (i < b.size()) {
|
||||
result[i] += a[i] - b[i];
|
||||
}
|
||||
else {
|
||||
result[i] += a[i];
|
||||
}
|
||||
// 处理借位
|
||||
if (result[i] < 0) {
|
||||
result[i] += 10;
|
||||
result[i + 1] -= 1;
|
||||
}
|
||||
}
|
||||
//去除高位的0
|
||||
int i = result.size() - 1;
|
||||
while (result.size() > 0 && result[i] == 0) {
|
||||
result.pop_back();
|
||||
i--;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// 向右移位
|
||||
vector<int> mul_pow(vector<int> a, int n) {
|
||||
vector<int> result(n, 0);
|
||||
result.insert(result.end(), a.begin(), a.end());
|
||||
//去除高位的0
|
||||
int i = result.size() - 1;
|
||||
while (result.size() > 0 && result[i] == 0) {
|
||||
result.pop_back();
|
||||
i--;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -17,17 +17,8 @@
|
||||
2. **无后效性**:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
|
||||
3. **有重叠子问题**:即子问题之间是**不独立**的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)
|
||||
|
||||
### 基本步骤
|
||||
1. 找出最优解的性质,刻画其结构特征。
|
||||
2. 递推地定义最优值。需要给出状态转移变量
|
||||
$$
|
||||
f(n)=f(n-1)+f(n-2)
|
||||
$$
|
||||
3. 以自底向上的方式计算出最优解。
|
||||
4. 根据计算最优值时的到的信息,构造最有解。
|
||||
|
||||
### 要素
|
||||
* 最有子结构
|
||||
* 最优子结构
|
||||
* 重叠子问题
|
||||
* 备忘录方法(矩阵表格)
|
||||
|
||||
@@ -44,15 +35,25 @@ $$
|
||||
|
||||
* 顺序解法和逆序解法只表示行进方向的不同或始端的颠倒。但用动态规划方法求最优解时,都是在行进方向规定后,均要逆着这个规定的行进方向,从最后一段向前逆推计算,逐段找出最优途径
|
||||
|
||||
### 构建动态规划模型
|
||||
## 2 构建动态规划模型
|
||||
|
||||
1. 正确选择状态变量xk, 使它既能描述过程的状态,又要满足无后效性。动态规划中的状态与一般所说的状态概念是不同的,它必须具有三个特性:
|
||||
### 动态规划问题的分析
|
||||
1. 找出**最优解的性质**,刻画其结构特征。
|
||||
2. 递推地定义最优值。需要给出**状态转移变量**
|
||||
$$
|
||||
f(n)=f(n-1)+f(n-2)
|
||||
$$
|
||||
3. 以自底向上的方式**计算出最优解**。
|
||||
4. 根据计算最优值时的到的信息,**构造最有解**。
|
||||
|
||||
### 动态规划算法的步骤
|
||||
1. 正确选择**状态变量**xk, 使它既能描述过程的状态,又要满足无后效性。动态规划中的状态与一般所说的状态概念是不同的,它必须具有三个特性:
|
||||
* 要能够用来描述受控过程的演变特征。
|
||||
* 要满足无后效性。 所谓无后效性是指:如果某段状态给定,则在这段以后过程的发展不受前面各阶段状态的影响。
|
||||
* 可知性。即是规定的各段状态变量的值,由直接或间接都是可以知道的。
|
||||
2. 确定决策变量uk及每段的允许决策集合Dk(xk)={uk}
|
||||
3. 写出状态转移方程如果给定第k段状态变量xk的值,则该段的决策变量uk一经确定,第k+1段状态变量xk+1的值也就完全确定。
|
||||
4. 列出指标函数Vk,n 关系,并要满足递推性。
|
||||
2. 确定**决策变量**uk及每段的允许决策集合Dk(xk)={uk}
|
||||
3. 写出**状态转移方程**如果给定第k段状态变量xk的值,则该段的决策变量uk一经确定,第k+1段状态变量xk+1的值也就完全确定。
|
||||
4. 列出**指标函数**Vk,n 关系,并要满足递推性。
|
||||
|
||||
### 动态规划最关键的部分
|
||||
* 确定规模的增长方向。一般在动态规划问题中。规模可变的并不只有一个。比如在正则表达式与字符串的匹配问题中。字符串的规模可以变化,正则表达式的规模也可以变化。
|
||||
|
||||
0
算法/A类:基本算法/5.12 n个骰子的点数.md
Normal file
0
算法/A类:基本算法/5.12 n个骰子的点数.md
Normal file
BIN
算法/A类:基本算法/image/2021-03-28-03-17-52.png
Normal file
BIN
算法/A类:基本算法/image/2021-03-28-03-17-52.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 116 KiB |
BIN
算法/A类:基本算法/image/2021-03-28-03-20-48.png
Normal file
BIN
算法/A类:基本算法/image/2021-03-28-03-20-48.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
Reference in New Issue
Block a user