1算法思想
分治
1.1含义
将规模为n的问题,分解为k个规模较小的子问题。每个子问题独立且与原问题相同,递归求解子问题,将各个子问题的解合并得到原问题的解。
1.2特点
分治三步骤: 划分,递归,合并
1.3适用
缩小,最优,合并,独立
1) 规模缩小到一定程度就容易解决
2) 问题最优子结构性质(即问题的最优解包含子问题的最优解),问题可以分解为规模较小的子问题
3) 子问题的解可以合并处理后得到问题的解
4) 子问题独立
1.4通用解法
分治算法:
划分
递归
合并
1.5经典例题讲解
最大最小问题
企业老板有一袋金块,要从中挑选最重的一块给他的
优秀员工,挑选最轻的一块给他的一位一般员工。
代码:
pair<int,int> maxMin(int* pArr,int iLow,int iHigh) { int iMax,iMin; if(iHigh - iLow <= 1)//递归出口,如果只剩一个元素 { if(pArr[iLow] < pArr[iHigh])//区分谁是最大和谁是最小 { return pair<int,int>(pArr[iLow],pArr[iHigh]); } else { return pair<int,int>(pArr[iHigh],pArr[iLow]); } } else//如果存在多个元素进行划分 {
int iMid = iLow + (iHigh - iLow)/2; //分治第一步,划分 pair<int,int> pairRes1 = maxMin(pArr,iLow,iMid); //分治第二步,递归求解 pair<int,int> pairRes2 = maxMin(pArr,iMid+1,iHigh);
iMin = min(pairRes1.first,pairRes2.first); //分治第三步,合并 iMax = max(pairRes1.second,pairRes2.second);
return pair<int,int>(iMin,iMax); } } |
1.6 分治与递归的区别
分治中使用了递归,与递归不同的是,分治中是对划分后的多个子问题递归求解后,并对各个子问题的解做了归并的操作。
即分治比递归多了归并子问题解的操作。
2 分治系列
类别-编号 |
题目 |
遁去的1 |
1 |
最大最小问题: 企业老板有一袋金块,要从中挑选最重的一块给他的优秀员工,挑选最轻的一块给他的一位一般员工
输入: 9 8 3 6 2 1 9 4 5 7 输出: 1 9 |
算法设计与分析 https://blog.youkuaiyun.com/qingyuanluofeng/article/details/47189209 关键: 1 采用分治法:划分为两部分,比较两部分钟的最小值和最大值,即可,注意由于每次返回的是最大和最小,因此用pair做 2 if(iHigh - iLow <= 1)//递归出口,如果只剩一个元素 { if(pArr[iLow] < pArr[iHigh])//区分谁是最大和谁是最小 { return make_pair<int,int>(pArr[iLow],pArr[iHigh]); }
代码:
pair<int,int> maxMin(int* pArr,int iLow,int iHigh) { int iMax,iMin; if(iHigh - iLow <= 1)//递归出口,如果只剩一个元素 { if(pArr[iLow] < pArr[iHigh])//区分谁是最大和谁是最小 { return pair<int,int>(pArr[iLow],pArr[iHigh]); } else { return pair<int,int>(pArr[iHigh],pArr[iLow]); } } else//如果存在多个元素进行划分 { //分治第一步,划分 int iMid = iLow + (iHigh - iLow)/2; pair<int,int> pairRes1 = maxMin(pArr,iLow,iMid); pair<int,int> pairRes2 = maxMin(pArr,iMid+1,iHigh);
//分治第二步,递归求解 iMin = min(pairRes1.first,pairRes2.first); iMax = max(pairRes1.second,pairRes2.second);
//分治第三步,合并 return pair<int,int>(iMin,iMax); } } |
2 |
正整数划分 将正整数n表示成一系列正整数之和: n = n1 + n2 + ... +nk 其中n1>=n2>=...>=nk>=1,k>=1 正整数的这种表示称为正整数n的划分。求正整数n的不同划分个数
输入: 6 输出: 11 |
算法设计与分析 https://blog.youkuaiyun.com/qingyuanluofeng/article/details/47189223 分析:设p(n)表示正整数n的划分树,难以找到递归关系,考虑增加变量 p(n) = q(n,n) 设q(n,m)表示:对于数字n,将最大加数n1不大于m的划分个数 q(n,m)={1,n = m = 1 {q(n,n),n<m,正整数n的划分由n1=n的划分和n1<=n-1的划分组成 {1+q(n,m-1),n=m,包含n只有1个划分,不包含n,划分值比n小 {q(n,m-1) + q(n-m,m),n>m>1,正整数n的最大加数n1不大于m的划分由n1=m的划分和n1<=m-1的划分组成 包含m时,剩余的数为n-m,又因为还有可能包含m,因此是q(n-m,m) 不包含m的划分,划分中所有值比m小,所以是q(n,m-1)
代码: long long g_q[MAXSIZE][MAXSIZE]; long long divideNum(int n,int m) { if(g_q[n][m] != -1) { return g_q[n][m]; } if(n == 1 || m == 1) { return g_q[n][m]= 1; } if(n < m) { return divideNum(n,n); } if(n == m) { return g_q[n][m] = 1 + divideNum(n,m-1); } if(n > m && m > 1) { return g_q[n][m] = divideNum(n,m-1) + divideNum(n-m,m); } } |
3 |
多项式乘积的分治方法: 计算两个n阶多项式的乘法: p(x) = a0 + a1*x + a2*x^2 + a3*x^3 + ... + an*x^n q(x) = b0 + b1*x + b2*x^2 + b3*x^3 + ... + bn*x^n |
算法设计与分析 https://blog.youkuaiyun.com/qingyuanluofeng/article/details/47189251 为减少乘法运算次数,考虑把一个多项式划分成两个多现实 p(x) = p0(x) + p1(x)*x^n/2 q(x) = q0(x) + q1(x)*x^n/2 p(x)*q(x) = p0(x)*q0(x) + (p0(x)*q1(x) + p1(x)*q0(x))x^n/2 + p1(x)q1(x)*x^n//四个多项式乘法 = p0(x)*q0(x) + ((p0(x) - p1(x))*(q1(x) - q0(x)) + p1(x)*q1(x) + p0(x)*q0(x))x^n/2 + p1(x)q1(x)*x^n//三个多项式乘法 |
4 |
Strassen矩阵乘法 A和B的乘积矩阵C中的元素C[i,j]定义为:C[i][j] = k从1到n 累加A[i][k]*B[k][j] 若依此定义来计算A和B的乘积矩阵C,则每计算C的一个元素C[i][j],需要做n次乘法和n-1次加法。因此,算出矩阵C的 个元素所需的计算时间为O(n3) |
算法设计与分析 https://blog.youkuaiyun.com/qingyuanluofeng/article/details/47189261 分治法: 将矩阵A,B和C中的每一矩阵都分成4个大小相等的子矩阵。可以将方程C=AB重写为: [C11 C12] = [A11 A12] [B11 B12] [C21 C22] [A21 A22] [B21 B22] 可以得到 C11 = A11*B11 + A12*B21 C12 = A11*B12 + A12*B22 C21 = A21*B11 + A22*B21 C22 = A21*B12 + A22*B22 为了降低时间复杂度,必须减少乘法的次数: 从8次乘法降为7次,用了7次对于n/2矩阵乘的递归调用和18次n.2矩阵的加减法计算 M1 = A11(B12 - B22) M2 = (A11+A12)*B22 以下代码是转载的,有空的时候再研究 |
5 |
棋盘覆盖 在一个2k×2k 个方格组成的棋盘中,恰有一个方格 与其它方格不同,称该方格为一特殊方格,且称该棋 盘为一特殊棋盘。在棋盘覆盖问题中,要用图示的4种 不同形态的L型骨牌覆盖给定的特殊棋盘上除特殊方格 以外的所有方格,且任何2个L型骨牌不得重叠覆盖。 当k>0时,将2k×2k棋盘分割为4个2k-1×2k-1 子棋盘 (a)所示。特殊方格必位于4个较小子棋盘之一中,其 余3个子棋盘中无特殊方格。为了将这3个无特殊方格 的子棋盘转化为特殊棋盘,可以用一个L型骨牌覆盖这 3个较小棋盘的会合处,如 (b)所示,从而将原问题转 化为4个较小规模的棋盘覆盖问题。递归地使用这种分 割,直至棋盘简化为棋盘1×1。
输入: 行 列 k(2^k为棋盘长度) 0 1 2(左上角测试) 1 3 2(右上角测试) 3 0 2(左下角测试) 2 2 2 (右下角测试) 输出: 2 0 3 3 2 2 1 3 4 1 1 5 4 4 5 5 2 2 3 3 2 1 3 0 4 1 1 5 4 4 5 5 2 2 3 3 2 1 1 3 4 4 1 5 0 4 5 5 2 2 3 3 2 1 1 3 4 1 0 5 4 4 5 5 |
算法设计与分析 https://blog.youkuaiyun.com/qingyuanluofeng/article/details/47189269 关键: 1将2k×2k棋盘分割为4个2k-1×2k-1 子棋盘 (a)所示。特殊方格必位于4个较小子棋盘之一中,其 余3个子棋盘中无特殊方格。为了将这3个无特殊方格 的子棋盘转化为特殊棋盘,可以用一个L型骨牌覆盖这 3个较小棋盘的会合处, 2如果不在棋盘左上,就设置右下角为指定颜色 右上, 左下 左下 右上 右下 左上 3 //分治第二步:递归处理 //判断特殊标记是否在左上角 if(iColorRow < iBegRow + iHalfSize && iColorCol < iBegCol + iHalfSize) { //如果特殊标记在左上角,那么左上角区域递归处理 chessCover(iBegRow,iBegCol,iColorRow,iColorCol,iHalfSize,pChessBoard); } else { //如果不在左上角,那么设置左上角区域中的最右下角位置为特殊标记。区域重新设置,起始行列:要随区域的不同而改变,左上不需改变,右上的列需要改为原列+棋盘半长 pChessBoard[iBegRow + iHalfSize - 1][iBegCol + iHalfSize - 1] = iMarkNum; &nb |