以下内容主要参考了严蔚敏版的数据结构教材。
回溯法是求解某些问题的全部或部分解的通用算法,特别是带有限制条件的问题。它通过不断的产生问题的完整解的片段并不断增长完整解的片段来获取该问题的完整解。当发现某个完整解的片段不能生成最后的完整解时,就会丢弃所有以该片段解为基础的完整解。
回溯法会首先列出完整解的片段的一个集合。这个集合原则上可以通过不同的方式最后给出给定的问题的所有解。这是通过不断地对该完整解的片段的集合中的片段解不断扩展而达到的。
给出一个简单的例子。假设现在需要求集合
A
=
{
1
,
2
,
3
}
A=\{1,2,3\}
A={1,2,3}的幂集。集合A的幂集是由集合A的所有子集组成的集合。因此集合A的幂集中的元素是一个集合,它或者是空集,或者含有集合A中的一个元素或者含有集合A中的l两个元素或者含有集合A中的三个元素。从集合A中的每一个元素来看,它要么属于幂集中每一个集合或者不属于。求幂集中的每一个集合可以看成是依次对集合A中的每一个元素“取”或者“舍”的过程。如图1所示。在图1中除叶子节点之外的所有节点都可以看成是该问题完整解的片段,通过不断取舍集合A中的元素来扩展该问题完整解的片段,当取舍完集合A中的所有元素时即得到了一个该问题的完整解。
由此可看出求解问题的过程形成了一颗二叉树,求解过程即为从根节点递归的先根遍历该二叉树。回溯法也是设计递归过程的一种重要方法,它的求解过程实质上是一个先根遍历一颗“状态树”的过程,只不过这棵树不是遍历前预先建立的,而是隐含在遍历过程中。
void printSet(vector<int> set)
{
for (int i = 0; i < set.size(); i++)
{
cout << set[i] << " ";
}
cout << endl;
}
void getPowerSet(int seTAElementIndex,vector<int> seTA, vector<int> &seTB)
{
int currentElement;
if (seTAElementIndex > seTA.size())
{
printSet(seTB);
}
else
{
currentElement = seTA[seTAElementIndex - 1];
seTB.push_back(currentElement);
getPowerSet(seTAElementIndex + 1, seTA, seTB);
seTB.pop_back();
getPowerSet(seTAElementIndex + 1, seTA, seTB);
}
}
//测试程序
int main()
{
int seTAElementIndex = 1;
vector<int> seTA;
seTA.push_back(1);
seTA.push_back(2);
seTA.push_back(3);
vector<int> seTB;
getPowerSet(seTAElementIndex, seTA, seTB);
}
上面的例子是一个特例。有些其他问题比如八皇后问题的求解过程的状态树不是一颗满的多叉树。当求解过程中的完整解的片段和问题所求解产生矛盾时(即该完整解的片段不能生成最后的完整解),不再继续试探下去,这时出现的叶子节点不是该问题的完整解。这类问题的求解过程可以看成是在约束条件下进行先根遍历,并在遍历过程中删去那些不满足条件的分支。图2是4皇后问题的状态树的部分。图中打红叉的是被删去的分支。