最近写数据结构的车厢调度,写好后给老师验收,我用回溯树的阐释了下我的想法,老师却说我这种解释很牵强。唉,现帖于博客,献丑了,惭愧惭愧啊。
题目核心说白了就是让你求所有出栈序列,言简意赅,但就是程序不太好写,特别是里面涉及到递归中又有栈。还好之前有位前辈(也是个学生)已经把这个写出来了,写得很不错的,前辈很谦虚,报告中仍感慨唏嘘不已,令晚生更是惭愧了。
Arrange( int position, int * sequence, int seq_cur)
... {
if (position<length)
...{
//一个数进栈后,有两种处理方式:要么立刻出栈,要么进行下一个数的进栈
}
if (!IsStackEmpty(&stack))
...{
//一个数出栈后,有两种处理方式:要么继续出栈,要么继续下一个数的进栈
}
if (position>=length&&IsStackEmpty(&stack))
...{
//一种可能输出序列产生,输出;
}
}
这里是网上一份流传很广的车厢调度算法,原作者是胡海洪,写作时间是06年七月份(在此向他表示感谢)。我基于他的思想写的代码如下:
... {
int temp,i;
if (position<length) //块1
...{
Push(&stack,position+1);
Arrange(position+1,sequence,seq_cur);
Pop(&stack);
}
if (!IsStackEmpty(&stack)) //块2
...{
temp=Pop(&stack);
sequence[seq_cur++]=temp;
Arrange(position,sequence,seq_cur);
Push(&stack,temp);
}
if (position>=length&&IsStackEmpty(&stack)) //块3
...{
for (i=0;i<length-1;i++)
...{
printf("%1d->",sequence[i]);
}
printf("%d",sequence[i]);
printf(" ");
}
}
在这个求所有序列的算法中,由于算法涉及到“多路递归”,加之递归中又有栈的出现,如果仅仅是采用单纯的工作栈模拟系统递归来分析算法的话将会非常麻烦。我在分析时采用了回溯树的办法,将递归模拟为先序遍历二叉树,算法分析难度大大降低。现详述如下:
为了讨论方便,当然也是为了我少画一点树节点(画图其实真的很累),在利用树形结构分析算法中我们暂且设置车厢数量为3,车厢数目在3和3以上时算法思想是完全一样的。
由于算法中在进入递归函数之前,就已经把1压入栈中。那么我们把二叉树的根节点data设置为1。如下图
接下来我们就进入了递归函数里真正核心算法的分析。这里建立二叉回溯树的基本思想是:若要进栈,则在当前节点生长出左子树,左子树节点写入进栈后所有元素;若要表示退栈,则在当前节点生长出右子树,右子树节点写入退栈元素。 当进入递归函数,遇到第一个判断:
3入栈后,又得递归了,这次有所不同,还记得这次是第几次递归了?第三次!前两次分别进栈的是2和3,这时车厢全部进栈完毕(记不记得我们假设共有3节车厢),无车厢可以进栈了。到退栈的时候,执行块2,记得在当前节点的右子树生成新结点。
注意我退栈使用负数表示,-3表示退车厢3,再次递归,此时已经不能进栈,块1不执行,跳到块2,继续退栈。
再次递归,也就只能再次出栈,如图
到此所有元素出栈完毕,调用块3。输出。
一次完整的入栈出栈过程就完成了,调用过程借助于二叉树而变得十分清晰明朗。接下来回溯过程,二叉树的作用才是真正显示出来。
函数输出所有元素后,便开始“回退”,在系统中堆栈开始出栈操作了,而在二叉树里的回溯就类似二叉先序遍历回溯过程一样,从-1节点开始(表示1出栈),回退到-2节点,即2出栈后,看看上面原函数块2吧,2出栈后下一步就要么继续出栈(那就是我们后面1出栈啦),要么等下一个入栈,但很明显已经没有任何元素可以入栈了,两个操作都不能继续下去,那我们继续回退至12入栈。
现在情况变啦,栈中只有12两个元素,现在要么3元素入栈(就是刚才才走过的路线),要么出栈一个元素,现在能出栈的只能是2,那么我们就出2吧:
还记得原作者的注释么?“一个数出栈后,有两种处理方式:要么继续出栈要么继续下一个数的进栈”。
接下来我们面临要么入栈,但以现在情况,只能入3;或者出栈,以现在情况只能出1。我们先入栈3,如图:
然后又是回溯到-2节点,然后继续1退栈,接下来3入栈等等,大致思想如此,恕不细表了。
主体思想就是借用一棵二叉树直观的表示出回溯递归过程,现在还有一个小地方我要叙述一下,不知道大家注意到没有:
在块1中最后有一句话:
Pop(&stack);
就是弹出当前元素,这是何意,如果用栈的话这句话是不好说清楚的,其实就是为了回溯到前一次递归,但直观形象没有,如果用树的话是很形象的。
比如
我们回溯到123这个节点时,下一步是要到12,自然需要先弹出3了,不是很明显么?那么块2中的压栈呢?