Appearance
4.分治策略
递归式: 分治算法的运行时间通常由递归式来描述。
三种求解递归式的方法:
- 代入法(Substitution Method):猜测一个界,然后使用数学归纳法证明这个界是对的。
- 递归树法(Recursion-Tree Method):将递归式转化成一棵树。树中的每个节点代表一个子问题的代价。 计算树中所有节点的代价之和,得到递归式的解。
- 主方法(Master Method):可求解形如下面公式的递归式的界:
可以看作是一个“即插即用”的通用定理。只需将
4.1 最大子数组问题
4.1.1 暴力求解法查找最大子数组
时间复杂度:
步骤:
- 遍历数组的每个元素作为子数组的起点
; - 对于每个起点
,遍历其后所有元素作为终点 ( ); - 计算数组
的和,并更新最大子数组和及其起点和终点。
cpp
void Find_max_subarray::maxSubarrayByBruteForce(const std::vector<int> &prices) {
const size_t n = prices.size();
int max_profit = 0;
int start = 0, end = 0;
for (int i = 0; i < n; i++) {
int current_sum = 0;
for (int j = i; j < n; j++) {
current_sum += prices[j];
if (current_sum > max_profit) {
max_profit = current_sum;
start = i;
end = j;
}
}
}
std::cout << "Maximum subarray: a[" << start << ".." << end << "] = { ";
for (int i = start; i <= end; i++) {
std::cout << prices[i] << " ";
}
std::cout << "}" << std::endl;
std::cout << "Sum: " << max_profit << std::endl;
}4.1.2 分治法查找最大子数组
对于给定数组
- 完全位于左侧:
; - 递归求解
的最大子数组。
- 递归求解
- 完全位于右侧:
; - 递归求解
的最大子数组。
- 递归求解
- 跨越中点:
; - 这个子数组必须包含
和 。
- 这个子数组必须包含
计算方法:
- 左半部分:从
向左遍历到 ,找到包含 的最大和 。 - 右半部分:从
向右遍历到 ,找到包含 的最大和 。 - 跨越中点:跨越中点的最大数组和即为
。
运行时间:
是递归求解左右两部分的最大子数组; 合并处理步骤,是求解跨越中点的最大子数组。
根据 2.3.1 主定理计算可得:分治策略的最大子数组算法的运行时间是
cpp
struct MaxSubarrayResult {
int start;
int end;
int sum;
};
MaxSubarrayResult findMaxCrossingSubarray(const std::vector<int> &prices, int low, int mid, int high) {
//search left
int left_sum = std::numeric_limits<int>::min();
int sum = 0;
int max_left = mid;
for (int i = mid; i >= low; i--) {
sum += prices[i];
if (sum > left_sum) {
left_sum = sum;
max_left = i;
}
}
//search right
int right_sum = std::numeric_limits<int>::min();
sum = 0;
int max_right = mid + 1;
for (int j = mid + 1; j <= high; j++) {
sum += prices[j];
if (sum > right_sum) {
right_sum = sum;
max_right = j;
}
}
// Return merged results
return {max_left, max_right, left_sum + right_sum};
}
MaxSubarrayResult findMaximumSubarray(const std::vector<int> &prices, int low, int high) {
if (low == high) {
return {low, high, prices[low]};
}
const int mid = (low + high) / 2;
const MaxSubarrayResult left = findMaximumSubarray(prices, low, mid);
const MaxSubarrayResult right = findMaximumSubarray(prices, mid + 1, high);
const MaxSubarrayResult cross = findMaxCrossingSubarray(prices, low, mid, high);
//Return max
if (left.sum >= right.sum && left.sum >= cross.sum) {
return left;
}
if (right.sum >= left.sum && right.sum >= cross.sum) {
return right;
}
return cross;
}4.2 矩阵乘法的 Strassen 算法
4.2.1 暴力求解矩阵乘法
- 原理: 对于两个
的矩阵 和 ,其乘积 也是一个 的矩阵。基本规则是:结果矩阵 中的元素 ,等于矩阵 的第 行元素与矩阵 的第 列元素对应相乘后的和。
- 实现:
- 外层循环(
):遍历结果矩阵 C 的行索引( ); - 二层循环(
):遍历结果矩阵 C 的列索引( ); - 内层循环(
):点积操作,计算 的第 行和 的第 列的乘积和( )。
- 外层循环(
- 代码:
cpp
using namespace std;
vector<vector<int>> Matrix_multiplication::bruteForce(const vector<vector<int> > &A,
const vector<vector<int> > &B,
const int n) {
vector C(n, vector(n, 0));
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++) {
C[i][j] += A[i][k] * B[k][j];
}
}
}
return C;时间复杂度:
- 循环次数:
各 次,总计循环 次。 - 乘法操作:对于结果矩阵中的每一个元素
,需要 次乘法, 个元素,所以需要乘 次。 - 加法操作:对于结果矩阵中的每一个元素
,需要 次加法,总计约为 次。
- 循环次数:
因此,暴力算法矩阵乘法的时间复杂度为:
4.2.2 Strassen 算法
Strassen 算法将矩阵乘法的时间复杂度降到
Strassen 算法递归式:
Strassen 算法总共分为4步:
- 将
的输入矩阵 和输出矩阵 分割成 4 个 的子矩阵
cpp
vector<vector<int> > MatrixMultiplication::matrixStrassen(const vector<vector<int> > &A,
const vector<vector<int> > &B,
const int n) {
const int half = n / 2;
vector<vector<int> > A11(half, vector<int>(half)), A12(half, vector<int>(half)),
A21(half, vector<int>(half)), A22(half, vector<int>(half));
vector<vector<int> > B11(half, vector<int>(half)), B12(half, vector<int>(half)),
B21(half, vector<int>(half)), B22(half, vector<int>(half));
matrixSplit(A, A11, A12, A21, A22, n);
matrixSplit(B, B11, B12, B21, B22, n);- 创建 10 个
的矩阵 ,每个矩阵保存步骤 1 中创建的两个子矩阵的和或差。 花费时间 。
cpp
// S1 = B12 - B22
vector<vector<int> > S1 = matrixSub(B12, B22, half);
// S2 = A11 + A12
vector<vector<int> > S2 = matrixAdd(A11, A12, half);
// S3 = A21 + A22
vector<vector<int> > S3 = matrixAdd(A21, A22, half);
// S4 = B21 - B11
vector<vector<int> > S4 = matrixSub(B21, B11, half);
// S5 = A11 + A22
vector<vector<int> > S5 = matrixAdd(A11, A22, half);
// S6 = B11 + B22
vector<vector<int> > S6 = matrixAdd(B11, B22, half);
// S7 = A12 - A22
vector<vector<int> > S7 = matrixSub(A12, A22, half);
// S8 = B21 + B22
vector<vector<int> > S8 = matrixAdd(B21, B22, half);
// S9 = A11 - A21
vector<vector<int> > S9 = matrixSub(A11, A21, half);
// S10 = B11 + B12
vector<vector<int> > S10 = matrixAdd(B11, B12, half);- 使用步骤 1 中创建的子矩阵和步骤 2 创建的 10 个矩阵,递归计算 7 个矩阵积
,每个矩阵 都是 的。
cpp
vector<vector<int> > P1 = matrixStrassen(A11, S1, half);
vector<vector<int> > P2 = matrixStrassen(S2, B22, half);
vector<vector<int> > P3 = matrixStrassen(S3, B11, half);
vector<vector<int> > P4 = matrixStrassen(A22, S4, half);
vector<vector<int> > P5 = matrixStrassen(S5, S6, half);
vector<vector<int> > P6 = matrixStrassen(S7, S8, half);
vector<vector<int> > P7 = matrixStrassen(S9, S10, half);- 通过
矩阵的不同组合进行加减运算,计算出结果矩阵 的子矩阵 ,花费时间 。
cpp
// C11 = P5 + P4 - P2 + P6
vector<vector<int> > C11_temp1 = matrixAdd(P5, P4, half);
vector<vector<int> > C11_temp2 = matrixSub(C11_temp1, P2, half);
vector<vector<int> > C11 = matrixAdd(C11_temp2, P6, half);
// C12 = P1 + P2
vector<vector<int> > C12 = matrixAdd(P1, P2, half);
// C21 = P3 + P4
vector<vector<int> > C21 = matrixAdd(P3, P4, half);
// C22 = P5 + P1 - P3 - P7
vector<vector<int> > C22_temp1 = matrixAdd(P5, P1, half);
vector<vector<int> > C22_temp2 = matrixSub(C22_temp1, P3, half);
vector<vector<int> > C22 = matrixSub(C22_temp2, P7, half);
//5、合并 C_ij 结果得到矩阵 C
vector C(n, vector<int>(n));
matrixJoin(C, C11, C12, C21, C22, n);
return C;
}