0x08算法设计与分析复习(二):算法设计策略-回溯法2
参考书籍:算法设计与分析——C++语言描述(第二版)
算法设计策略-回溯法
子集和数
问题描述
已知n个不同的正数
回溯法求解
对于子集和数问题问题,可采用两种不的解结构形式:可变长度元组和固定长度元组。
采用可变长度元组表示解
当采用可变长度元组表示解时,代表一个可行解的元组长度可以不同,成为一个k-元组
(x0,x1,⋯,xk−1),0≤k≤n 。元组的每个分量的取值可以是元素值,也可以是选入子集的正数的下标。通常的做法是列出入选子集的元素下标,如果采用这种形式的解,其显式约束可描述为:xi∈{j|j是整数且0≤j<n} 且xi<xi+1(0≤i<n−1) 。加入条件xi<xi+1 可以避免产生重复子集现象。隐式约束如下:∑k−1i=0wxi=M 。采用固定长度元组表示解
固定长度n-元组
(x0,x1,⋯,xn−1),xi∈{0,1},0≤i<n 。xi=0 表示wi 未选入子集;xi=1 表示wi 入选子集。采用这种形式的解,其显示约束可描述为:xi∈{0,1}(0≤i<n−1) 。问题的隐式约束是选入子集的正数之和等于M ,即∑n−1i=0wixi=M 。
一般称从n个元素的集合中找出满足某些性质的子集的状态空间树为子集树。子集树有
最后讨论子集和数的约束函数
- 因为如果上式得不到满足,那么意味着当剩余的正数全部加入子集后,子集中的正数之和仍小于
M ,自然表明部分向量(x0,x1,⋯,xk) 不可能导致一个答案状态。
如果假定n个正数
- 因为如果子集中加入下一个待选的正数
wk (它是剩余正数中最小的),使整个子集之和已大于M ,这显然意味着从剩余正数中选取任意一个正数加入到现有子集后都不可能使子集和数等于M 。
根据上面两式,可以得到约束函数:
子集和数算法
函数定义:
void SumOfSub(float s, int k, float r, int *x, float m, float *w)
- 前置条件:
wi≤wi+1(0≤i<n−1),s+r≥m&&s+wk≤m(s=∑k−1i=0wixi,r=∑n−1i=kwi) - 后置条件:在以
(x0,x1,⋯,xk) 为根的子树上搜索答案状态
函数SumOfSub
的初始条件是
//子集和数的回溯算法 void SumOfSub(float s, int k, float r, int *x, float m, float *w) { x[k]=1; if(s+w[k]==m){ for(int j = 0;j<k;j++){ //得到一个可行解 cout << x[j]<<" "; } cout << endl; } else if(s+w[k]+w[k+1]<=m){ //搜索左子树 SumOfSub(s+w[k],k+1,r-w[k],x,m,w); } if((s+r-w[k]>=m)&&(s+w[k+1]<=m)){ x[k]=0; //搜索右子树 SumOfSub(s,k+1,r-w[k],x,m,w); } } void SumOfSub(int *x,int x, float m,float *w) { float r=0; for(int i = 0;i<n;i++){ r=r+w[i]; //计算r=\sum_{i=k}^{n-1}w[i]; } if(r>=m && w[0]<=m) SumOfSub(0,0,r,x,m,w); }
图的着色
问题描述
已知无向图
回溯法求解
设无向图
下面采用n-元组
约束函数的设计:约束函数从隐式约束产生,对所有i和j
图着色算法
//图的m-着色算法 template<class T> void MGraph<T>::NextValue(int k,int m,int *x) { //本函数在[1,m]中为x[k]确定一个值最小,且不与其邻接点冲突的颜色 //x[k]=0表示没有可用颜色,颜色从1开始编号 do{ x[k]=(x[k]+1)%(m+1);//尝试下一种颜色 if(!x[k]) return;//没有可用颜色 for(int j=0;j<k;j++){ if(a[k][j] && x[k]==x[j]){ //若(i,j)是图的边,且相邻结点k和j颜色相同 break;//发生冲突,选择下一种颜色 } } if(j==k) return;//成功选择一种颜色返回 }while(1); } template<class T> void MGraph<T>::mColoring(int k,int m,int *x) { do{ NextValue(k,m,x);//为x[k]分配颜色 if(!x[k]) break;//x[k]=0表示没有适当的颜色 if(k==n-1){ //得到图G的一种m-着色方案 for(int i=0;i<n;i++){ cout << x[i] <<" "; } cout << endl; } else { //已经对前k个结点分配了颜色,尝试其余结点 mColoring(k+1,m,x); } }while(1); } template<class T> void MGraph<T>::mColoring(int m,int *x) { mColoring(0,m,x); }
函数mColoring
对一个给定的无向图G和m,列出图中结点的所有可能的m-着色方案。算法以深度优先方式生成状态空间树中的结点,寻找所有答案结点,即m-着色方案。搜索中使用约束函数剪去不可能包含答案结点的分枝。
时间分析
算法的计算时间上界可以由状态空间树的结点树NextValue
的执行时间为
哈密顿环
问题描述
已知图
哈密顿环算法
对于n个结点的图
//哈密顿环算法 //,函数NextValue的功能是约束函数。 template<class T> void MGraph<T>::NextValue(int k,int *x) { //函数NextValue的功能是约束函数 do{ x[k]=(x[k]+1)%n;//下一个结点编号 if(!x[k]) return; if(a[x[k-1]][x[k]]){ //检查(x[k-1],x[k])是否是图中一条边 for(int j=0;j<k;j++){ //检查与前k个结点是否相同 if(x[j]==x[k]) break; } if(j==k){ //x[k]是当前可取的结点编号 if((k<n-1)||((k==n-1)&&a[x[n-1]][x[0]])) return; } } }while(1); } template<class T> void ExtMGraph<T>::Hamiltonian(int k,int *x) { //递归函数Hamiltonian实际计算哈密顿环 do{ NextValue(k,x);//产生x[k]的下一个值 if(!x[k]) return;//x[k]=0表示x[k]已经没有可取的值 if(k==n-1){ //输出一个哈密顿环 for(int i=0;i<n;i++){ cout << x[i]<<" "; } cout << "0" << endl; } else { //深度优先进入下一层 Hamiltonian(k+1,x); } }while(1); } template<class T> void ExtMGraph<T>::Hamiltonian(int *x) { Hamiltonian(1,x);//x[0]=0为约定的起始结点 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)