回溯法——两类问题的递归方法解析

最近在学习回溯法,有些心得,记录下来。

之前学习了分治法,动态规划,和回溯法拿在一起考虑,发现其利用递归的思想很巧妙,我自己总结的认为递归的核心思想就是考虑整体中所有个体都有的一般规律,将其描述出来;然后进行递归,到下一个个体;当到达分解的尾部时,则返回。

回溯法中的两类问题:

子集树:求整体的所有子集;

排列树:求整体的所有不同排列。

(我只列了两类的最基本的两个问题)

其核心便是如何进行个体的组合(子集树中哪个个体不加入当前子集,哪个加入;排列树中,当前个体与哪个个体交换)。对于个体组合可以考虑到递归,因为递归把整体分解成个体来解决问题,所以可以很方便的得到相应的解答。

子集树

对于这个问题来说,如果用递归思想来考虑,其实就是如何组合整体的个体,而对每一个个体而言,只有加入组合和不加入组合两种情况,所以,我们只需对两种不同情况分别进行递归即可。而输出的时候,每次输出都要在整体的所有个体全都遍历一遍后才能输出(对每个个体都决定一下是否输出),这要即可输出全部个体。

对个体的两种情况可以用0,1来代替,0代表不输出,1代表输出。判断是否输出可以用当前的数组下标是否是字符串的最后字符下标,如果是,则输出,不是,则继续进行个体判断和递归。

(代码的注释说明了整个回溯的流程)

//输出结果的代码为:
if(t >= n)
    show(a);
//其中,t为当前的数组下标,n为字符串长度。a数组保存每个个体对应的情况。
//个体判断并进入下层递归的代码:
else
{							
	for (int i = 0; i <= 1; i++)	//两种情况,首先用第一种,然后用第二种
    {
	a[t] = i;				
    /*
    *第一次输出是每个个体都决定为0,即不输出,所以为空串,最后一次输出是倒数第二次输
    *出从尾端不断回退,回到头部,然后头部进行其for循环的第二次,决定为1,后面的在之
    *前也都决定为1了,所以最后一次输出为整个字符串。
    */		
        backtracking(t+1);		
    }
}
//show函数如下:
void show(int a[])
{
	for (int i = 0; i < n; i++) 
        {
	    if(a[i] == 1)		//如果当前个体对应的是输出
   		cout<<s[i];	//则输出此个体
	}
	cout<<endl;	
}


排列树

同理子集树思想,排序树其实就是两个个体看作一个小整体,只用考虑两者是否交换,也是两种情况,第一种交换,第二种不交换。但是与子集树不同的是,排列树里面为了方便,会直接对目标字符串进行修改,所以在交换后,我们还要记得再换回来,保证所有的情况都考虑到。

(代码的注释说明了整个回溯的流程)

由于输出与子集树的判断相同,这里就不再列出

//决定是否交换和进行下一次递归的代码:
/*
*这里与子集树不同,因为所有不同的个体间都可以两两交换,如第一个可以和最后一个交
*换,所以要把所有情况考虑到,t即为当前进行到的个体,而i为要与t进行交换的个体位
*置
*/
for (int i = t; i < n; i++)		
{
/*
*第一次执行时是自身与自身交换,即第一次输出为不交换的目标字符串自身,而之后当前
*个体会与其后面的每一个依次进行交换
*/
	swap(i, t, s);		//s即为输入的字符串	
//不断向下一个元素迈进,从最后一个个体开始进行交换后的恢复
	backtracking(t+1);	
	swap(i, t, s);		//恢复交换过的两个字符,为了保证所有情况都可以计算在内
}

要时刻记得递归的核心思想是考虑整体中所有个体都有的一般规律,将这个规律描述出来;然后向下一个个体迈进;到达分解的结尾则返回。

文章为博主自学的心得体会,如有错误,欢迎指正。

参考文章

回溯法的解题步骤与例子解析 

感谢 若明天不见 博主的文章

 

    原文作者:回溯法
    原文地址: https://blog.csdn.net/a844761860/article/details/81940894
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞