回溯算法例题分析

什么是回溯算法

回溯算法的概念:回溯算法是一种类似枚举但优于枚举的算法,它可以通过“剪枝”的方式去掉那些不可能的递归情况,从而得到实现解题目标的所有可行方案。回溯算法一般通过递归来实现,每个次递归前都要判断当前这种方案是不是有继续递归的必要,如果可以,就继续往深入递归,最终实现目标,如果在递归中发现不符合递归条件了,那么就“减掉”这个分支,程序回溯到之前的另一种可能性重新开始,继续往下递归,能递归到最后一步的方案一般称为可行解,我们可以统计可行解的数目,从而最终得到方案总数。
回溯算法的优点:回溯算法可以讨论所有可能的方案,不遗漏地进行判断,同时它也会放弃那些不可能的递归方案,节省程序的运行时间。
回溯算法的解题思路:首先分析题目,如果最终目标由一系列步骤组成,并且让你求最终目标的方案数目,那么可以尝试使用回溯算法。代码一般分为两部分,递归部分和下一步判断部分,递归部分以循环的形式遍历所有可能的情况,判断部分的布尔值反馈结果决定是否下一步递归或者“剪枝”,即写判断条件。最终在递归达到一定深度的时候判断方案可行,进行计数累加或保存方案。

回溯算法的例题1:N皇后问题

题目:大家常见的是八皇后问题,是N皇后问题的一种特殊情况。N皇后问题是指,在一个N*N的棋盘上,放置N个皇后,他们彼此不能处在相同行、列、对角线,求有多少种摆放方案。
分析:我们可以对N个皇后逐一放置,首先我们可以确定一行摆放一个皇后,每个皇后有N个可以摆放的位置,那么我们先假设第一个皇后放在第一行第一个位置,再判断第二个皇后的摆放,如果放在第二行第一个位置在同一列不符合条件,放在第二个位置是对角线也不符合条件,第三个位置就可以,所以放下这个皇后,接下来看第三个皇后的摆放,以此类推,每次判断不可摆放时,即剪去了这个分支。如果某次递归行数大于N,即棋盘摆满,方案可行,计数加一。
算法实现

	int[] x ; //先申明一个数组,等待皇后数量输入
	int sum =0; //满足条件的情况的累计
	int num ; // 棋盘的宽高
	/**
	 * 回溯算法1:皇后问题入口方法
	 */
	public void bt1()
	{
		num = 8;
		x= new int[num+1];
		BT1(1); //开始第一次递归
		System.out.println(sum); 
	}	
	/**
	 * 回溯算法1:递归方法
	 * @param num
	 * @return
	 */
	public void BT1(int t) //摆第t行
	{	
		if(t > num)  //棋盘摆完,计数加一
			sum++;
        else
        {
			for(int i=1;i<=num;i++)  //从第一个位置开始试探直到最后一个
			{
				x[t]=i;  //放在第i个位置
				if(place(t)==true) //可以放置就开始放下一个
				{
					BT1(t+1);//下一行
				}				
			}
		}
	}
	/**
	 * 回溯算法1:判断位置是否可放	
	 */
	public boolean place(int t)
	{
	       for(int i=1;i<t;i++)
	       {
	    	   if(x[i]==x[t]||Math.abs(i-t)==Math.abs(x[i]-x[t]))//判断对角线及行列
	    	   {
	    		   return false;
	    	   }
	       }		
			return true;
	}

回溯算法2:图着色问题

问题:给定一个拼图,颜色刚开始都是空白的,我们有四种颜色,要求为图形上色且相邻色块颜色不能相同。
分析:类似N皇后问题,递归是对于上色顺序进行递归,试探所有上色可能性,判断是判断是否和周围颜色相同。相邻关系我们可以用一个二维数组来保存(0表示不相邻,1表示相邻)。
算法实现

	int[][] p =     //图的相邻关系矩阵,相邻为1,矩阵长宽和分区数量有关
		{
	                {0,1,0,0,0,0,1},    //图1和1~7的相邻关系
			{1,0,1,1,1,1,1},
			{0,1,0,1,0,0,0},
			{0,1,1,0,1,0,0},
			{0,1,0,1,0,1,1},
			{0,1,0,0,1,0,1},
			{1,1,0,0,1,1,0}		//图7和1~7的相邻关系
		};
	int sum2 = 0; //可行的情况
	int[] y = new int[8]; ;  //上色情况 1和2和3和4表示不同颜色
	int num2=7;
	/**
	 * 回溯算法2:图着色问题
	 */
	public void bt2()
	{
		BT2(1);
		System.out.println(sum2);
	}
	/**
	 * 回溯算法2:递归方法
	 * @param t
	 */
	public void BT2(int t)
	{
		if(t>num2)
		{
			sum2++;
		}
		else
		{
			for(int i=1;i<=4;i++) //四种颜色进行尝试
			{
				y[t]=i;				
				if(place2(t)==true)
				{
					BT2(t+1);
				}
			}
		}		
	}
	/**
	 * 回溯算法2:判断
	 * @param t
	 * @return
	 */
	public boolean place2(int t)
	{
		for(int j=1;j<t;j++) //逻辑上的第1个开始
		{
			if(p[t-1][j-1]==1&&y[t]==y[j])
			{
				return false;
			}
		}
		return true;
	}

回溯算法3:火柴棍摆正方形

题目:给定若干不同长度的火柴棍,要求判断这些火柴棍能不能围成正方形。
分析:如果总长不能被4整除,那么就不能摆成正方形,如果可以被四整除,我们再开始使用回溯算法递归判断。我们选择一个数组,表示四个桶,桶的深度是总长的四分之一,我们选择火柴棍放入桶里,如果长度超出则表示这种摆放方案不可选,取出并进行下一个桶的试探,当四个桶中火柴棍总长度相同并且火柴棍摆完,那么这就是一种可行的解。程序递归的部分递归每个火柴棍的摆放,每个火柴棍有四种摆放方案。
算法实现

	int[] z ;//定义数组用来表示放第几个火柴
	int[] w = new int[5]; //定义每个桶的火柴数量
	int add=0; //火柴总数
	int t=1; //当前放的火柴
	int flag=0;//可否摆成正方形
	/**
	 * 回溯算法3:火柴棍摆正方形
	 */
	public void bt3()
	{
		int[] c={1,1,1,1,1,1,2};  //各火柴的长度
		z=new int[c.length+1];  //1:表示的一个桶,2:表示第二个桶,3:表示第三个桶 4:表示第四个桶
		add=0;
		for(int i=0;i<c.length;i++)
		{
			add+=c[i];
		}
		System.out.println("当前总长度:"+add);
		if(add%4!=0)
			System.out.println("不可以组成正方形");
		else
		{
	        BT(t,c);	
			if(flag!=1)
				System.out.println("不可以组成正方形");
			else
				System.out.println("可以组成正方形");
		}		
	}	
	/**
	 * 回溯算法3:递归方法
	 * @param t
	 */
	public void BT(int t,int[] c)
	{
		for(int i=1;i<=4;i++)
		{
			if(t==8)
			{
				flag=1;
				break;
			}
			z[t]=i;
			w[i]+=i; 
			if(place3(t)==true) //true则继续放下一个火柴
			{
				BT(t+1,c); //递归放下一个火柴
			}
	        w[i]-=i;//不可以放就把重量减掉
		}
	}	
	/**
	 * 回溯算法3:判断可不可以放
	 * @param t
	 */
	public boolean place3(int t)
	{
		for(int i=1;i<5;i++)
		{		
			if(w[i]>add/4)
				return false;
		}		
		return true;
	}

回溯算法4:求子集

题目:给定一个数组,求它的所有不重复子集。
分析:回溯算法试探所有摆放的方法,每个数字有放入和不放入两种方案,递归摆放数字(在数组中保存),摆完三个就可以去和HashMap中的数组进行判断,如果已经存在就不放入,不存在就添加进HashMap。执行完所有情况,HashMap中的元素就是所有的子集。
算法实现

	ArrayList<int[]> array = new ArrayList<>();
	int[] c ; //用来表示存放的是第几个数
	int[] temp ; //临时存放数据
	/**
	 * 回溯算法:4:求子集
	 */
	public void bt4()
	{
		int[] a = {1,2,3};
		c=new int[a.length+1];
		temp=new int[a.length];
		DT4(1,a);	
		for(int i=0;i<array.size();i++)
		System.out.println(Arrays.toString(array.get(i)));
	}	
	/**
	 * 回溯算法4:递归方法
	 */
	public void DT4(int t,int[] a)
	{
		if(t==a.length+1)
		{
			place4(a);
		}
			for(int i=0;i<=1;i++)  //0:不放   1:放
			{
				if(t==a.length+1)
					break;
				c[t]=i;									
				DT4(t+1,a);				
			}	
	}	
	/**
	 * 回溯算法4:判断
	 * @return
	 */
	public void place4(int[] a)
	{
		int count=0;
		for(int j=1;j<a.length+1;j++)    
		{
			if(c[j]==1)  //表示选择了这个数
			{
				temp[count]=a[j-1];
				count++;     //统计要放入的数的数量
			}
		}
		int[] r = new int[count];  //定义一个长度和所选数字数量一致的数组
		for(int j=0;j<count;j++) //存值
			r[j]=temp[j];
        if(!array.contains(r)&&r.length!=0) //判断是否存在
        	array.add(r);  //添加
	}

猜你喜欢

转载自blog.csdn.net/mayifan_blog/article/details/85221239