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

学习笔记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

结果

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

 

  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值