数据结构6.7_回溯法和树的遍历

参考链接:

回溯法(八皇后问题):http://data.biancheng.net/view/34.html

详细讲解回溯算法:https://blog.csdn.net/gardenpalace/article/details/84625537

回溯算法超通俗易懂详尽分析:https://blog.csdn.net/sinat_27908213/article/details/80599460

 

 

回溯算法(BackTracking)

 

在程序设计中,由相当一类求一组解、或求全部解或求最优解的问题,不是根据某种确定的计算法则,而是利用试探回溯的搜索技术求解。 

 

回溯法时设计递归过程中的一种方法。

 

这个求解过程实质上是一个先序遍历一棵“状态树”的过程。只是这棵树不是遍历前预先建立的,而是隐含在遍历过程中。

回溯算法实际上一个类似枚举的深度优先搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回(也就是递归返回),尝试别的路径。

许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。

回溯法说白了就是穷举法。回溯法一般用递归来解决。

 

回溯法的求解过程实际上是一个先序遍历一棵“状态树”的过程,只是这棵树不是预先建立的,而是隐含在遍历过程中。

一个复杂问题的解决方案是由若干个小的决策步骤组成的决策序列,所以一个问题的解可以表示成解向量X=(x1,x2,.....xn),

其中分量xi对应第i步的选择,X中个分量xi所有取值的组合构成问题的解向量空间,简称解空间或者解空间树(因为解空间一般用树形式来组织),

由于一个解向量往往对应问题的某个状态,所以解空间又称为问题的状态空间树

 

按照DFS算法的策略,从跟结点出发搜索解空间树。首先根结点成为活结点(指自身已生成但其孩子结点没有全部生成的结点),同时也成为当前的扩展结点(指正在产生孩子结点的结点,也称为E结点)。

在当前的扩展结点处,搜索向纵深方向移至一个新结点。这个新结点就成为新的活结点,并成为当前扩展结点

如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成了死结点(指其所有子结点均已产生的结点)。

此时应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。

回溯法以这种方式递归地在解空间中搜索,直到找到所要求的解或解空间中已无活结点为止。

所以回溯法体现出走不通就退回再走的思路。

 

练习题1:求含n个元素的集合的幂集

参考链接:https://blog.csdn.net/summer_dew/article/details/83921730

 

A={1,2,3}
ρ(A) = { {1,2,3}, {1,2}, {1,3}, {1}, {2,3}, {2}, {3}, ∅ }

集合A的幂集是由集合A的所有子集所组成的集合。

幂集中的每个元素是一个集合。

从幂集中的每个元素的角度来看:或是空集,或者包含集合A中一个元素,或者包含集合A中两个元素,或者包含集合A中三个元素,或者等于集合A。

从集合A的角度来看:它其中的元素只有两种状态。要么属于幂集的元素集 ,要么不属于幂集元素集。

求幂集ρ(A) 的元素的过程可以看成是依次对集合A中元素进行“取”或“舍”的过程,并且可以用下图所示的二叉树表示:

 1 void PowerSet(int i,int n) {
 2     // 求含n个元素的集合A的幂集ρ(A)。进入函数时已对A中前i-1个元素坐了取舍处理
 3     // 现从第i个元素起进行取舍处理。若i>n,则求得幂集的一个元素,并输出之
 4     // 初始调用:PowerSet(1,n);
 5     if (i>n) 输出幂集的一个元素
 6     else {
 7         取第i个元素;PowerSet(i+1, n);
 8         舍第i个元素;PowerSet(i+1, n);
 9     }
10 }

 

接下来假设以线性表来表示集合A,则算法的具体表示如下:

 1 void GetPowerSet(int i, List A, List &B)
 2 {
 3     //线性表A表示集合A,线性表B表示幂集的一个元素;
 4     if(i>ListLength(A)) Output(B);
 5     else{
 6         GetElem(A,i,x);
 7         k = ListLength(B);  //k表示进入函数时,ListB的长度,第一次调用本函数时,k=1
 8         ListInsert(B,k+1,x);
 9         GetPowerSet(i+1,A,B);
10         ListDelete(B,k+1,x);
11         GetPowerSet(i+1,A,B);
12     }
13 }

 

图中状态变化树是一棵满二叉树:树中每个叶子结点的状态都是求解过程中可能出现的状态(即问题的解)。

【然而】很多问题用回溯和试探求解时,描述求解过程的状态树不是一棵满的多叉树

【非满多叉树】不是满的多叉树:当试探过程中出现的状态和问题所求解产生矛盾时,不再继续试探下去,这时出现的叶子结点不是问题的解的终结状态

此类问题的求解过程可看成是在约束条件下进行先序(根)遍历并在遍历过程中剪去那些不满足条件的分支

例如,下面的四皇后问题

 

练习题2: 4皇后问题

参考链接:https://segmentfault.com/a/1190000003733325

以4皇后为例,其他的N皇后问题以此类推。所谓4皇后问题就是求解如何在4×4的棋盘上无冲突的摆放4个皇后棋子。在国际象棋中,皇后的移动方式为横竖交叉的,因此在任意一个皇后所在位置的水平、竖直、以及45度斜线上都不能出现皇后的棋子:

其实在解决四皇后问题的时候,并不一定要真的构建出这样的一棵解空间树,它完全可以通过一个递归回溯来模拟。所谓的解空间树只是一个逻辑上的抽象。当然也可以用树结构来真实的创建出一棵解空间树,不过那样会比较浪费空间资源也没有那个必要

 

 1 #include<stdio.h>
 2 
 3 int count = 0;
 4 int isCorrect(int i, int j, int (*Q)[4])  //用于判断当前布局是否合法
 5 {
 6     int s, t;
 7     for(s=i,t=0; t<4; t++)
 8         if(Q[s][t]==1 && t!=j)
 9             return 0;//判断行
10     for(t=j,s=0; s<4; s++)
11         if(Q[s][t]==1 && s!=i)
12             return 0;//判断列
13     for(s=i-1,t=j-1; s>=0&&t>=0; s--,t--)
14         if(Q[s][t]==1)
15             return 0;//判断左上方
16     for(s=i+1,t=j+1; s<4&&t<4;s++,t++)
17         if(Q[s][t]==1)
18             return 0;//判断右下方
19     for(s=i-1,t=j+1; s>=0&&t<4; s--,t++)
20         if(Q[s][t]==1)
21             return 0;//判断右上方
22     for(s=i+1,t=j-1; s<4&&t>=0; s++,t--)
23         if(Q[s][t]==1)
24             return 0;//判断左下方
25 
26     return 1;//否则返回
27 }
28 
29 void Queue(int j, int (*Q)[4])   //递归函数,实现回溯遍历
30 {
31     int i,k;
32     if(j==4){//递归结束条件
33         for(i=0; i<4; i++){
34                 //得到一个解,在屏幕上显示
35             for(k=0; k<4; k++)
36                 printf("%d ", Q[i][k]);
37             printf("\n");
38         }
39         printf("\n");
40         count++;
41         return ;
42     }
43     for(i=0; i<4; i++){
44         if(isCorrect(i, j, Q)){//如果Q[i][j]可以放置皇后
45             Q[i][j]=1;//放置皇后
46             Queue(j+1, Q);//递归深度优先搜索解空间树
47             Q[i][j]=0;//这句代码就是实现回溯到上一层
48         }
49     }
50 }
51 
52 int main()
53 {
54     int Q[4][4];
55     int i, j;
56     for(i=0; i<4; i++)
57         for(j=0; j<4; j++)
58             Q[i][j] = 0;
59     Queue(0, Q);
60     printf("The number of the answers are %d\n", count);
61     return 0;
62 }

 

posted @ 2019-05-07 14:50  Grooovvve  阅读(1312)  评论(0编辑  收藏  举报