前天在《编程之美》中读到一个“一摞烙饼排序”的问题,第一眼不会做,看了答案之后倒是让我明白了一年多之前不真正理解的剪枝搜索的内涵。记录一下,作为分享和温习之地。
剪枝搜索说白了还是搜索,就像动态规划说白了就是使用特殊的方法进行搜索以提高效率。剪枝搜索中涉及到很重要的概念:状态空间和搜索树。我觉得状态空间和搜索树之间是有绝对的关系的。剪枝搜索就是在一棵状态树上搜索想要的节点。然而和以前的树不同的是,这棵树中的节点并不是都在内存中的,而是到达该节点时该节点通过数组表示在内存中而其他节点并没有,通过改变数组内容来达到状态的改变。在程序中通过剪枝条件判断进行中断某条搜索路径转向其他路径,从而不必在无用的路径浪费时间,进而提高效率。在程序中,典型的是在一个循环中存在递归语句和辅助处理程序,在循环之外是判断终点的语句和进行剪枝的语句。判断终点的语句自然是为了停止递归,循环是为了在搜索树上进行横向扩展即遍历每一个状态节点的所有直接子节点,而递归则是为了纵向进行搜索达到DFS或者BFS。剪枝语句涉及到进行剪枝的条件或者说函数,这个函数涉及到搜索的效率,因为他决定了是否继续进行搜索,该状态是否已经不是最优的了。
提高剪枝搜索效率的关键在于改良搜索路径和条件函数。更加快速并且准确的判断状态进而判断是否继续进行搜索。
void CPrefixSorting::Search(int *pCakesArray, int step)
{
++searchCount;
if (LowerBound(pCakesArray) + step >= reversesCount)
{
return;
}
if (IsSorted(pCakesArray) && step < reversesCount)
{
reversesCount = step;
for (int i = 0; i < reversesCount; ++i)
{
cakesArrayReverse[i] = curCakesArrayReverse[i];
cout << curCakesArrayReverse[i] << " ";
}
cout << endl;
return;
}
for (int i = 1; i < cakesCount; ++i)
{
int *cakesArrayTmp = new int[cakesCount];
assert(NULL != cakesArrayTmp);
for (int j = 0; j < cakesCount; ++j)
{
cakesArrayTmp[j] = pCakesArray[j];
}
Reverse(cakesArrayTmp, 0, i);
curCakesArrayReverse[step] = i;
Search(cakesArrayTmp, step + 1);
if (cakesArrayTmp)
{
delete [] cakesArrayTmp;
}
}
}