回溯法大成!以回溯法实现栈的出栈情况的遍历为例子,轻松帮你深刻领悟回溯法

来源:互联网 发布:linux系统iso镜像下载 编辑:程序博客网 时间:2024/05/12 05:16

这里用回溯法实现了 栈的出栈情况的遍历 。虽然这个题有更好的做法,但是你如果用回溯法做这道题,做完后一定会对回溯法有这更高境界的领悟,而且在整个设计算法,debug算法的过程中会感受到一种酣畅淋漓的快感。因为这个题看似很小,其实规模很大,要考虑方方面面的问题,很多很多。


上题。

输入一个序列 比如 123。你进栈的顺序必须是按照这个序列来,但是你可以这样,进1,然后在2进入前,把1出栈。所以出栈顺序就有了许多种可能。


我讲一下我一开始是怎么做的:

感觉这题很简单,直接开始码代码,发现有新的需求,直接改,一处改,处处改,bug比比皆是,而且怎么改都是错的,因为思路上就有了偏差。


正确的做法是怎样的:

做回溯法和其他的不一样,对逻辑的缜密和条理性要求更高,特别是碰到大规模的题目的时候。所以你应该做的第一件事,是,打开一个txt文档,写下你的思路。



接下来你只需要把你的文字转化成代码就可以了。由于我的思路是写给我自己看的,所以我这里重新写一下思路分析。

1.你的输入队列是1,2,3。所以我3推进去之后,后面就再也没有进栈的操作了,因为没有已经元素已经推光了,所以只需要进行出栈操作就可以了,所以这里进行统一处理。分两类情况,一,元素推光。二,元素还未推光。

2.元素未推光的情况。这里又分两种情况。当前栈是空的,当前栈是非空的。栈空,只能直接入栈(因为你元素未推光,所以肯定可以进行入栈操作)。栈非空。

3.栈非空的时候其实又有两种情况,你可以选择入栈(因为你元素未推光,所以肯定可以进行入栈操作),也可以选择出栈(栈非空)。所以我们用一个for循环,第一次执行入栈操作,当回溯回来的时候,for循环到下一个,执行出栈操作。


这样就把思路完美的分析出来了。接下来我会每一步都贴上对应的代码。


元素推光的一类情况

if (level == length) {    // TODO: 2017/11/19 这里执行的是:把当前记录的出栈情况数组添加到所有情况的二维数组里,最后一起输出 
level == length。这里的level是我当前的层级。我什么都没入栈。level是0。每入栈一个数字,会执行递归,参数是level + 1。

length是数组的长度。这里是3。你的level是从0开始到,如果想到3,就已经加了4次。所以说这是一种元素都推光的情形。


元素未推光的一类情况-当前栈是空的

if (list.size() == 0) {    list.add(inOrder[level]);    back(level + 1, index);    list.remove(list.size() - 1);} 
我这里的list是模拟的栈。栈是空的,只能入栈,所以add,然后level+1进行回溯,进入下一层。这里的index先不用管,没有影响的。当你back(level + 1, index);执行完以后,你后面的层级的所有情况都已经搞定了,所以你这里需要把你推的东西重新移除,进行另外方向的寻找同时又避免你推进去的东西影响另外的判断,因为你和另外的情况是平行的,如果你不删除这个数的话你这次操作就影响了另外的情况,会出错。所以这里是必须移除的。


元素未推光的一类情况-当前栈非空的

for (int i = 0; i < 2; i++) {    if (i == 0) {        list.add(inOrder[level]);        back(level + 1, index);        list.remove(list.size() - 1);    } else {        int num = list.remove(list.size() - 1);        current[index] = num;        back(level, index + 1);        list.add(num);    }}
非空情况你可以推也可以不推,所以用了一个for循环,第一次弄入栈的情况,第二次回溯回来循环执行到下一个地方,就执行下面的代码块即出栈操作。

也是一样你添加了之后就要移除,移除了之后就要添加。这里出栈多了一个操作current[index] = num;就是记录下你的出栈情况,这个index初始是0,当你出了一次栈以后,在继续遍历的时候,就要把index + 1带进去,让后面的层级对你的出栈情况数组的接下来的一位进行分析。所以我们可以看到,如果是入栈,level + 1会被传进去,如果是出栈,index+1会被传进去,前者代表的是你的入栈队列处理到哪个位置了,后者代表的是你的出栈队列处理到哪个位置了。


这就没了。

如果说还有的话就是元素推光的那一类情况,数据又是怎么处理的。有兴趣可以看一下。


取得你所有出栈情况的二维数组的当前是第几行。这里从0,0开始向下找,找到第一个值不为0的数,就是我们要找的行数了。

int row = -1;//找到第一个没有被赋值过的行,这个找到的i就是pure(纯净,未赋值过的)行号int flag = -1;for (int i = 0; i < 100; i++) {    if (allPossible[i][0] == 0) {        row = i;        break;    }}

把你栈中的剩下的元素记录下来(应该是要推掉的,但是为了不影响回溯的时候的数据,所以直接读出来了)

//把栈中剩余的数按出栈顺序读到结果数组里,实际上不出栈,怕影响之前的情况for (int i = 0; i < list.size(); i ++) {//经检验,list出栈的数据完全正确!    int num = list.get(i);    for (int j = length - 1; j >= 0; j --) {        if (allPossible[row][j] == 0) {            allPossible[row][j] = num;            break;        }    }}

把在回溯的过程中出栈的元素也加上(因为我们这里到3就结束了简化了操作,所以需要通过这一步和上一步把出栈顺序完整拼接起来)

//current里的结果也一并加上for (int i = 0; i < length; i++) {    if (allPossible[row][i] == 0) {        allPossible[row][i] = current[i];    } else break;}

搞定了。上结果图



贴上完整回溯代码

void back(int level, int index) {//1,2带进去验证 level = 0 length = 2//最后发现index还是有必要的        if (level == length) {            // TODO: 2017/11/19 这里执行的是:把当前记录的出栈情况数组添加到所有情况的二维数组里,最后一起输出            int row = -1;            //找到第一个没有被赋值过的行,这个找到的i就是pure(纯净,未赋值过的)行号            int flag = -1;            for (int i = 0; i < 100; i++) {                if (allPossible[i][length - 1] == 0) {                    row = i;                    break;                }            }            //把栈中剩余的数按出栈顺序读到结果数组里,实际上不出栈,怕影响之前的情况            for (int i = 0; i < list.size(); i ++) {//经检验,list出栈的数据完全正确!                int num = list.get(i);                for (int j = length - 1; j >= 0; j --) {                    if (allPossible[row][j] == 0) {                        allPossible[row][j] = num;                        break;                    }                }            }            //current里的结果也一并加上            for (int i = 0; i < length; i++) {                if (allPossible[row][i] == 0) {                    allPossible[row][i] = current[i];                } else break;            }//            for (int i = 0; i < length; i ++) {//肯定不能都置为0,不然回溯的时候会打乱前面的数据//                current[i] = 0;//            }            return;        } else {//这里level0到最后一层,index指代当前操作数,如果仍在这个范围内,代表数没用完            if (list.size() == 0) {                list.add(inOrder[level]);                back(level + 1, index);                list.remove(list.size() - 1);            } else {                for (int i = 0; i < 2; i++) {                    if (i == 0) {                        list.add(inOrder[level]);                        back(level + 1, index);                        list.remove(list.size() - 1);                    } else {                        int num = list.remove(list.size() - 1);                        current[index] = num;                        back(level, index + 1);                        list.add(num);                    }                }            }        }    }

贴上全部代码

public class Test {    int length;    int[][] allPossible;    String[][] allOperate;    int[] current;    String[] operate;    int[] inOrder;//    int[] outOrder;    List<Integer> list;//这个当做栈    int operateIndex;    Test(int... ints) {//123321,后面3个是要求的出栈顺序,可以不用理会        //length = ints.length / 2;        length = ints.length;        allPossible = new int[100][length];//不想分析有几种可能,暂定100        allOperate = new String[100][length * 2];        current = new int[length];        operate = new String[length * 2];        inOrder = new int[length];        for (int i = 0; i < length; i++) {            inOrder[i] = ints[i];        }//        outOrder = new int[length];//        for (int i = 0; i < length; i++) {//            outOrder[i] = ints[i + length];//        }        list = new ArrayList<>();        operateIndex = 0;    }    void back(int level, int index) {//1,2带进去验证 level = 0 length = 2//最后发现index还是有必要的        if (level == length) {            // TODO: 2017/11/19 这里执行的是:把当前记录的出栈情况数组添加到所有情况的二维数组里,最后一起输出            int row = -1;            //找到第一个没有被赋值过的行,这个找到的i就是pure(纯净,未赋值过的)行号            int flag = -1;            for (int i = 0; i < 100; i++) {                if (allPossible[i][length - 1] == 0) {                    row = i;                    break;                }            }            //把栈中剩余的数按出栈顺序读到结果数组里,实际上不出栈,怕影响之前的情况            for (int i = 0; i < list.size(); i ++) {//经检验,list出栈的数据完全正确!                int num = list.get(i);                for (int j = length - 1; j >= 0; j --) {                    if (allPossible[row][j] == 0) {                        allPossible[row][j] = num;                        break;                    }                }            }            //current里的结果也一并加上            for (int i = 0; i < length; i++) {                if (allPossible[row][i] == 0) {                    allPossible[row][i] = current[i];                } else break;            }//            for (int i = 0; i < length; i ++) {//肯定不能都置为0,不然回溯的时候会打乱前面的数据//                current[i] = 0;//            }            return;        } else {//这里level0到最后一层,index指代当前操作数,如果仍在这个范围内,代表数没用完            if (list.size() == 0) {                list.add(inOrder[level]);                back(level + 1, index);                list.remove(list.size() - 1);            } else {                for (int i = 0; i < 2; i++) {                    if (i == 0) {                        list.add(inOrder[level]);                        back(level + 1, index);                        list.remove(list.size() - 1);                    } else {                        int num = list.remove(list.size() - 1);                        current[index] = num;                        back(level, index + 1);                        list.add(num);                    }                }            }        }    }    void print() {        int row = -1;        for (int i = 0; i < 100; i++) {            if (allPossible[i][0] == 0) {                row = i;                break;            }        }        for (int i = 0; i < row; i++) {            for (int j = 0; j < length; j++) {                System.out.print(allPossible[i][j] + " ");            }            System.out.println();        }    }    public static void main(String[] args) throws Exception {        Test test = new Test(1, 2, 3);        test.back(0, 0);        test.print();    }}

懂的人会发现自己的回溯法已经大成了,夸张点的甚至感觉自己的逻辑思维都清晰了许多。