回溯算法
思想
回溯算法具有解题的通用性,但是效率有待提高。
解空间的树是逻辑上的树。不是真实存在的数据结构。
题目1 打印多少次123?
运行结果是:
为什么会打印出8次123?
这个数组有3个元素,下标分别是0,1,2,
我们看下图:
我们把i的值可以理解成是数组的下标。
我们之前学的二叉树的前序遍历,中序遍历,后序遍历,都是自己调自己2次。只是当时它是调用,是二叉树的,所以起始条件是根节点。其实就像代码中的i=0一样,逐渐从根节点往叶子节点遍历,当指针到达叶子节点的时候,叶子节点遍历完了,递归就结束了。
i=0相当于在逻辑上是一个根节点,i+1相当于下一层,以此类推。相当于在二叉树中,由父节点走向孩子节点。当i=length的时候,相当于从根节点沿着某一个路径遍历到叶子节点上。
当i=0的时候,我们看作二叉树的根节点,0不等于3,就执行A这一句了,i=1,1不等于3,又进来执行A了,i=2,2不等于3,又进来执行A了,i=3。相当于深度遍历。3等于3了,达到if的条件了,递归结束了,打印1,2,3,然后不进入else语句了,然后这个函数执行完了。
递归完还要一个一个回溯。
回溯到i=2的节点上。这个节点刚才只是执行了A这一句,现在要执行B这一句,i=3了!达到if条件,打印1,2,3,然后不进入else语句了,然后这个函数执行完了 。
递归结束,回溯到上面那个节点,它把A,B这两句都执行完了,当前这个函数执行完了,然后就回溯到A:func i=1这个节点上,执行B这一句的调用,i=2,2不等于3,然后递归,又执行A这一句,i=3,打印1,2,3,然后又回溯到父节点,执行B这一句,i=3,再次打印1,2,3。
以此类推下去。
所以,最后打印8次1,2,3。
第0层,节点个数 2^0=1
第1层,节点个数 2^1=2
第2层,节点个数2^2=4
第3层,节点个数2^3=8
自己递归调用自己2次,走出来的是二叉树。
自己递归调用自己3次,走出来的是三叉树。
以此类推。
打印出来3^3=27个123。
问题2(打印原始序列的子集)
我们现在想打印的是原始序列123的所有的子集。
看下图
我们记录根节点往左走,记录为1,根节点往右走,记录为0
也就是说,往左走是执行A这句,往右走是执行B这句。
我们在最后打印的时候,得判断,把往左走的节点打印出来,把往右走的节点不打印出来。
这样就可以把子集打印出来了!
即叶子节点的个数:2^n个
同一层的节点代表同一个下标,同一个下标对应的是数组的同一个元素,往左走表示选择当前这个元素,往右走表示不选择当前这个元素 。
子集树代码实现
/*
子集树代码
*/
void func(int arr[], int i, int length, int x[])
{
if (i == length)//递归结束的条件
{
for (int j = 0; j < length; ++j)
{
if (x[j] == 1)
{
cout << arr[j] << " "; // 1 2 3
}
}
cout << endl;
}
else
{
x[i] = 1;//选择i节点
func(arr, i + 1, length, x);//遍历i的左孩子
x[i] = 0;//不选择i节点
func(arr, i + 1, length, x);//遍历i的右孩子
}
}
int main()
{
int arr[] = { 1,2,3 };// 1 2 3 12 13 23 ...
int length = sizeof(arr) / sizeof(arr[0]);
int x[3] = { 0 };
func(arr, 0, length, x);
return 0;
}
时间复杂度:叶子节点的个数
子集树求解的模板
length是数组长度,也就是最后一层叶子节点的层数
也可以下面这么写