代码改变世界

算法学习之栈

2011-10-10 20:16  MichaelYin  阅读(869)  评论(0编辑  收藏

最近趁着找工作做了很多关于数据结构和算法的题,在这里也将涉及到栈的一些不错的知识点和题目以及代码贴出来,一方面是为了夯实基础,另外一方面方便大家更好的掌握数据结构中的栈

栈的特性其实一说对数据结构有稍微了解的人都知道,后进先出,但是在解决问题的过程中熟练的使用合适的数据结构我觉得才是对栈真正的理解

首先来讲一个很经典的问题,就是出栈顺序,题目是这样的,比如现在又1 2 3 4 5五个数字,规定这五个数字入栈的顺序不变,但是中间可以任意的出栈,出栈的数字就当做输出,请写出程序输出所有的出栈的序列

这个问题可以用很经典的回溯法来解,就是通过递归方法调用,在每一层找出所有可能的操作方式,直到全部输出,另外需要加一句的是在以前我是不知道递归的时候还原现场的,在这个代码里面也是学到了这个知识点,而这个小细节其实是很重要的,当时就是因为这个细节让我在思考递归调用的时候否定了该方法,后来想了许久才重新相通

下面贴出相关代码

    public static void SearchStack(Stack<int> input, Stack<int> stack, Stack<int> output)
    {
        if (input.Count == 0 && stack.Count == 0)
        {
            //输出结果
            Array array = output.ToArray();
            foreach (int obj in array)
                Console.Write(obj);
            Console.WriteLine("");

        }
        else
        {
            if (input.Count > 0)
            {
                //入栈
                  stack.Push(input.Pop());
                SearchStack(input, stack, output);
                input.Push(stack.Pop());
            }

            if (stack.Count > 0)
            {
                //出栈
                  output.Push(stack.Pop());
                SearchStack(input, stack, output);
                stack.Push(output.Pop());
            }
        }
    }
输入的数字首先入栈到input中,stack是我们程序中实际模拟的栈的操作,该题主要考察的是对递归算法的理解,属于一个很经典的题目
上面一题有一个变形,就是给出一个输入序列和一个输出序列,如何判断该序列是不是该输入序列入栈后的出栈顺序
 
比如网上流传的某笔试题是这样的

题目:输入两个整数序列。其中一个序列表示栈的push顺序,
判断另一个序列有没有可能是对应的pop顺序。
为了简单起见,我们假设push序列的任意两个整数都是不相等的。
比如输入的push序列是1、2、3、4、5,那么4、5、3、2、1就有可能是一个pop系列。
因为可以有如下的push和pop序列:
push 1,push 2,push 3,push 4,pop,push 5,pop,pop,pop,pop,
这样得到的pop序列就是4、5、3、2、1。
但序列4、3、5、1、2就不可能是push序列1、2、3、4、5的pop序列

这个题目要解其实我们可以模拟一个栈,然后通过栈顶得元素和输入序列进行比较,如果没有的,则往前继续入栈,知道找到出栈的那个数字为止,如果中间没有找到,且栈中还有元素,则查找失败

        public static bool CheckStack(int[] input, int[] output, int length)
        {
            int indexOfInput, indexOfOutput;
            indexOfInput = 0;
            indexOfOutput = 0;
            System.Collections.Generic.Stack<int> stack = new Stack<int>();

            //初始化
            while (input[indexOfInput] != output[0])
            {
                stack.Push(input[indexOfInput]);
                indexOfInput++;
            }

            //入栈
            stack.Push(input[indexOfInput]);
            indexOfInput++;

            //check
            while (indexOfOutput < length)
            {
                if (stack.Count != 0 && stack.Peek() == output[indexOfOutput])
                {
                    //栈有元素相等,pop
                    stack.Pop();
                    indexOfOutput++;
                }
                else
                {
                    if (indexOfInput < length)
                    {
                        //入栈
                        stack.Push(input[indexOfInput]);
                        indexOfInput++;
                    }
                    else
                    {
                        return false;
                    }
                }
            }
            return true;
        }

上面是两个很典型的关于出栈序列相关的题目,接下来另外一个题目

输入序列为一串数字,有N个,互不相等,求输入序列一共有多少种

这个题目的解法就是卡特兰数的推导过程,有兴趣的同学可以去看看推导过程。

此题有多种扩展题目,比如编程之美4.3的买票找零问题

假设有2N个人在排队买票,其中有N个人手持50元的钞票,另外有N个人手持100元的钞票,假设开始售票时,售票处没有零钱,问这2N个人有多少种排队方式,不至使售票处出现找不开钱的局面

在比如这个问题,此题是搜狐的笔试题

四对括号可以有多少种匹配排列方式?比如两对括号可以有两种:()()和(())

这些题目本质其实都可以归纳到栈的出栈序列有多少种这个上面去。

栈并不是一个单独的数据结构,其实在很多其他的数据结构相关的使用中也能看到栈的影子

二叉树

在我的另外一篇Post二叉树的非递归遍历中,其实可以发现二叉树的前序和中序遍历的代码是惊人的相似,唯一不同之处是前序是在入栈的时候打印元素,而中序则是在出栈的时候打印元素,而如果我们已知了一个二叉树的前序,那这个二叉树有多少种排列呢?如果你想到了卡特兰数,恭喜你,答对了。

另外二叉树的后续遍历也能用栈来写出很优雅的代码,想看的可以点击Here.

队列

用两个栈能实现队列这个数据结构,具体的实现,就留给读者自己解决了,呵呵。。。