学习笔记 | 算法设计 | 回溯法剪枝函数解决子集和问题(C语言和MATLAB)

    科技2026-02-28  9

    学习笔记2 | 算法设计 | 回溯法解决子集和问题

    前言:关于本节的笔记,大多数是代码呈现,目前网上都是递归解决子集和问题,没有将剪枝的功能体现出来。希望本节笔记能帮助你了解回溯法的代码实现,子集和问题是我最近才遇见的,主要是代码的实现,很头大,特别是MATLAB,简直编的快炸了,也许是我太菜了-_-!

    1.子集和问题

    这是我算法课程学习时遇到的问题,简单来说,就是一个集合比如{2,2,4,6,5,5,7},我现在给定一个目标值C,找出满足C的所有子集。

    s=[2,2,4,6,5,5,7]

    现在找出组合:

    C = 10 = 2 + 2 + 6 =  4 + 6  =  5 + 5

    而我们就是要通过回溯法找到如{2,2,4}和{5,5}这样的子集解。而这就是子集和问题。

    2.回溯法

    那么什么是回溯法,这个很好解释:

    回溯法按深度优先策略搜索问题的解空间树。首先从根节点出发搜索解空间树,当算法搜索至解空间树的某一节点时,先利用剪枝函数判断该节点是否可行(即能得到问题的解)。如果不可行,则跳过对该节点为根的子树的搜索,逐层向其祖先节点回溯;否则,进入该子树,继续按深度优先策略搜索。

    具体的概述本节不细讲,主要是代码实现:

    3.C语言实现

    代码里有详细解释,可以对照来看,基本原理就是一直递归并且进行剪枝,但是一定要先排序。

    注意:回溯法求解的结果时全部解,只有分支限界法求的是一个可行解或者一个最优解。

    #include <stdio.h> int sum=0; //sum是子集和问题的值和 int *s, *x, n,c;//c是目标值,n是子集的数值个数 注意:这里的指针有s和x,指代全局变量数组s和x int count = 0;//由count计数,count>0则表示有解 void bt(int i,int ts,int rs,int x[]) //当搜索到第i( 1≤i≤n)的某个结点时,用ts表示选取的整数和,rs表示余下的整数和,即rs=s[i]+…+s[n](其中包含s[i]) { if(i==n) { if(ts==c) //如果等于目标值 { printf("---结果:解%d---\n",count+1); for(i=0;i<n;i++) if(x[i]) printf("%3d",s[i]); printf("\n"); count++; } } else { //左剪枝:通过检查当前整数s[i]加入后子集和是否超过s,若是则剪枝,即仅仅扩展满足ts+s[i]≤c的左孩子结点。 //右剪枝:如果一个结点满足ts+rs-s[i]<c,也就是说即便选择剩余所有整数,也不可能找到一个解,即仅仅扩展满足ts+rs-s[i]≥c的右孩子结点。 if(ts+s[i]<=c)//左剪枝 { x[i]=1;//选择元素 printf("到达左!\n"); for(int d=0;d<n;d++) { if(x[d]) printf("%3d",s[d]); } printf("\n这时的rs的值:%d",rs); printf("\n"); bt(i+1,ts+s[i],rs-s[i],x); } if(ts+rs-s[i]>=c)//右剪枝 { x[i]=0;//不选择元素 printf("到达右!\n"); for(int d=0;d<n;d++) { if(x[d]) printf("%3d",s[d]); } printf("\n这时的rs的值:%d",rs); printf("\n"); bt(i+1,ts,rs-s[i],x); } } } int main() { int i; int exNum=0; printf("请输入子集个数n和目标值c:\n"); scanf("%d%d",&n,&c); //输入子集个数n,目标值c s=new int[n]; x=new int[n]; int l=0; //按照n的个数,循环录入到数组s和x里 for(i=0;i<n;i++){ l=i+1; printf("输入第%d个值:",l); scanf("%d",&s[i]); x[i]=0; } /* //测试用的数据 n=7; c=10; s=new int [n]; x=new int [n]; s[0]=2;s[1]=2;s[2]=4;s[3]=6;s[4]=5;s[5]=5;s[6]=7; i=n;*/ int rs=0; for(int f=0;f<n;f++) { x[f]=0; rs=rs+s[f]; } printf("排列前的子集:\n"); for(int q=0;q<i;q++) { printf("%d ",s[q]); } //排序:从小到大 for(int m=0;m<i;m++) { for(int n=m+1;n<i;n++) { if(s[m]>s[n]) { exNum=s[m]; s[m]=s[n]; s[n]=exNum; } } } printf("\n"); printf("排列后的子集:\n"); for(int w=0;w<i;w++) { printf("%d ",s[w]); } printf("\n"); //进入回溯函数 bt(0,sum,rs,x); if(count) printf("该子集有解!\n"); else printf("该子集无解!\n"); return 0; }

    4.MATLAB实现

    这个是目前我比较头大的,谁叫我才开始学习MATLAB-_-..........

    4.1回溯递归剪枝函数m文件

    这里面基本跟C语言的实现差别不大,可以对应C语言来看:

    function count=bt(i,ts,rs,x,count,n,c,s) if(i==n) if(ts==c) count=count+1; str=['结果:解',num2str(count)]; disp(str); for i=1:n if(x(i)==1) disp(s(i)); end end end else if(ts+s(i)<=c) %左剪枝 x(i)=1; count=bt(i+1,ts+s(i),rs-s(i),x,count,n,c,s); end if(ts+rs-s(i)>=c) %右剪枝 x(i)=0; count=bt(i+1,ts,rs-s(i),x,count ,n,c,s); end end end

    4.2主要m文件

    %%子集和问题-回溯法 %比如 2 2 5 4 6 的目标值为10,结果就是 2 2 6 clear; clc; s=input('请输入集合的数值S(比如以矩阵形式输入[m,n,j,p...]):'); n=size(s,2); disp('该集合的大小N为:'); disp(n); c=input('请输入集合的目标值C:'); x=zeros(1,n); %生产一行为0大小为n的矩阵,储存值 sumn=0; %集合和的值,初值为0 rs=0; for f=1:n rs=rs+s(f); end s=sort(s); count=bt(1,sumn,rs,x,0,n,c,s); if(count>0) disp('该子集有解!'); else disp('该子集无解!'); end

    结果

    运行结果还是要有的,不然就是瞎说了:

     

    Processed: 0.012, SQL: 9