代码改变世界

面试题:递归颠倒栈 与栈排序

2013-09-01 22:01  youxin  阅读(2200)  评论(0编辑  收藏  举报

题目:用递归颠倒一个栈。例如输入栈{1, 2, 3, 4, 5}1在栈顶。颠倒之后的栈为{5, 4, 3, 2, 1}5处在栈顶。

分析:乍一看到这道题目,第一反应是把栈里的所有元素逐一pop出来,放到一个数组里,然后在数组里颠倒所有元素,最后把数组中的所有元素逐一push进入栈。这时栈也就颠倒过来了。颠倒一个数组是一件很容易的事情。不过这种思路需要显示分配一个长度为O(n)的数组,而且也没有充分利用递归的特性。

我们再来考虑怎么递归。我们把栈{1, 2, 3, 4, 5}看成由两部分组成:栈顶元素1和剩下的部分{2, 3, 4, 5}。如果我们能把{2, 3, 4, 5}颠倒过来,变成{5, 4, 3, 2}然后在把原来的栈顶元素1放到底部,那么就整个栈就颠倒过来了,变成{5, 4, 3, 2, 1}

思路类似hanoi问题

接下来我们需要考虑两件事情:一是如何把{2, 3, 4, 5}颠倒过来变成{5, 4, 3, 2}。我们只要把{2, 3, 4, 5}看成由两部分组成:栈顶元素2和剩下的部分{3, 4, 5}。我们只要把{3, 4, 5}先颠倒过来变成{5, 4, 3},然后再把之前的栈顶元素2放到最底部,也就变成了{5, 4, 3, 2}

至于怎么把{3, 4, 5}颠倒过来……很多读者可能都想到这就是递归。也就是每一次试图颠倒一个栈的时候,现在栈顶元素pop出来,再颠倒剩下的元素组成的栈,最后把之前的栈顶元素放到剩下元素组成的栈的底部。递归结束的条件是剩下的栈已经空了。这种思路的代码如下:

// Reverse a stack recursively in three steps:

// 1. Pop the top element

// 2. Reverse the remaining stack

// 3. Add the top element to the bottom of the remaining stack

template<typename T> void ReverseStack(std::stack<T>& stack)

{

    if(!stack.empty())

    {

        T top = stack.top();

        stack.pop();

        ReverseStack(stack);

        AddToStackBottom(stack, top);

    }

}

我们需要考虑的另外一件事情是如何把一个元素e放到一个栈的底部,也就是如何实现AddToStackBottom。这件事情不难,只需要把栈里原有的元素逐一pop出来。当栈为空的时候,push元素e进栈,此时它就位于栈的底部了。然后再把栈里原有的元素按照pop相反的顺序逐一push进栈。

注意到我们在push元素e之前,我们已经把栈里原有的所有元素都pop出来了,我们需要把它们保存起来,以便之后能把他们再push回去。我们当然可以开辟一个数组来做,但这没有必要。由于我们可以用递归来做这件事情,而递归本身就是一个栈结构。我们可以用递归的栈来保存这些元素。

基于如上分析,我们可以写出AddToStackBottom的代码:

// Add an element to the bottom of a stack:
template<typename T> void AddToStackBottom(std::stack<T>& stack, T t)
{
    if(stack.empty())
    {
        stack.push(t);
    }
    else
    {
        T top = stack.top();
        stack.pop();
        AddToStackBottom(stack, t);
        stack.push(top);
    }
}
 

 

完整代码:

#include<iostream>
#include<vector>
#include<stack>
using namespace std;

void reverseStack2(stack<int> s,int n)
{
    if(n==2)
    {
        int a=s.top();s.pop();
        int b=s.top();s.pop();
        s.push(b);
        s.push(a);
    }
    else
    {
        int topData=s.top(); s.pop();
        reverseStack2(s,n-1);
        
    }

}
void addToStackBottom(stack<int>& s,int top)
{
    if(s.empty())
    {
        s.push(top);
    }
    else
    {
        int tmp=s.top();s.pop();
        //错误:addToStackBottom(s,tmp);
        addToStackBottom(s,top);
        s.push(tmp);
    }
}



void reverseStack(stack<int>& s)
{
    if(!s.empty())
    {
        int top=s.top();s.pop();
        reverseStack(s);
        addToStackBottom(s,top);
    }
}
int main()
{
    stack<int> s;
    for(int i=1;i<=5;i++)
        s.push(i);

    cout<<"栈顶 ";
     
        for(int i=5;i>=1;i--)
            cout<<i<<ends;
         
     
    cout<<"栈尾"<<endl;

    reverseStack(s);

    cout<<"栈顶 ";
    while(!s.empty())
    {
        int top=s.top();s.pop();
        cout<<top<<ends;
    }
    cout<<"栈尾"<<endl;

}

 

转自:剑指offer:http://zhedahht.blog.163.com/blog/static/25411174200943182411790/

另外一个相似的题目;

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

 (剑指offer:

分析:这到题除了考查对栈这一基本数据结构的理解,还能考查我们的分析能力。

这道题的一个很直观的想法就是建立一个辅助栈,每次push的时候就把一个整数push进入这个辅助栈,同样需要pop的时候就把该栈的栈顶整数pop出来。

我们以前面的序列4、5、3、2、1为例。第一个希望被pop出来的数字是4,因此4需要先push到栈里面。由于push的顺序已经由push序列确定了,也就是在把4 push进栈之前,数字1,2,3都需要push到栈里面。此时栈里的包含4个数字,分别是1,2,3,4,其中4位于栈顶。把4 pop出栈后,剩下三个数字1,2,3。接下来希望被pop的是5,由于仍然不是栈顶数字,我们接着在push序列中4以后的数字中寻找。找到数字5后再一次push进栈,这个时候5就是位于栈顶,可以被pop出来。接下来希望被pop的三个数字是3,2,1。每次操作前都位于栈顶,直接pop即可。

再来看序列4、3、5、1、2。pop数字4的情况和前面一样。把4 pop出来之后,3位于栈顶,直接pop。接下来希望pop的数字是5,由于5不是栈顶数字,我们到push序列中没有被push进栈的数字中去搜索该数字,幸运的时候能够找到5,于是把5 push进入栈。此时pop 5之后,栈内包含两个数字1、2,其中2位于栈顶。这个时候希望pop的数字是1,由于不是栈顶数字,我们需要到push序列中还没有被push进栈的数字中去搜索该数字。但此时push序列中所有数字都已被push进入栈,因此该序列不可能是一个pop序列。

也就是说,如果我们希望pop的数字正好是栈顶数字,直接pop出栈即可;如果希望pop的数字目前不在栈顶,我们就到push序列中还没有被push到栈里的数字中去搜索这个数字,并把在它之前的所有数字都push进栈。如果所有的数字都被push进栈仍然没有找到这个数字,表明该序列不可能是一个pop序列

http://zhedahht.blog.163.com/blog/static/25411174200732102055385/

/////////////////////////////////////////////////////////////////////////////
// Given a push order of a stack, determine whether an array is possible to 
// be its corresponding pop order
// Input: pPush   - an array of integers, the push order
//        pPop    - an array of integers, the pop order
//        nLength - the length of pPush and pPop
// Output: If pPop is possible to be the pop order of pPush, return true.
//         Otherwise return false
/////////////////////////////////////////////////////////////////////////////
bool IsPossiblePopOrder(const int* pPush, const int* pPop, int nLength)
{
      bool bPossible = false;

      if(pPush && pPop && nLength > 0)
      {
            const int *pNextPush = pPush;
            const int *pNextPop = pPop;

            // ancillary stack
            std::stack<int> stackData;

            // check every integers in pPop
            while(pNextPop - pPop < nLength)
            {
                  // while the top of the ancillary stack is not the integer 
                  // to be poped, try to push some integers into the stack
                  while(stackData.empty() || stackData.top() != *pNextPop)
                  {
                        // pNextPush == NULL means all integers have been 
                        // pushed into the stack, can't push any longer
                        if(!pNextPush)
                              break;

                        stackData.push(*pNextPush);

                        // if there are integers left in pPush, move 
                        // pNextPush forward, otherwise set it to be NULL
                        if(pNextPush - pPush < nLength - 1)
                              pNextPush ++;
                        else
                              pNextPush = NULL;
                  }

                  // After pushing, the top of stack is still not same as 
                  // pPextPop, pPextPop is not in a pop sequence
                  // corresponding to pPush
                  if(stackData.top() != *pNextPop)
                        break;

                  // Check the next integer in pPop
                  stackData.pop();
                  pNextPop ++;
            }

            // if all integers in pPop have been check successfully, 
            // pPop is a pop sequence corresponding to pPush 
            if(stackData.empty() && pNextPop - pPop == nLength)
                  bPossible = true;
      }

      return bPossible;
}

 

 

思路:建立一个栈,若栈不为空,检查栈顶元素是否与pop序列将要弹出的元素相同,若相同则弹出栈顶元素,若不同,则检查push序列,将push序列中将与pop序列将要弹出元素不同的全部压入栈,直到找到相同的,若在push剩下的元素中找不到与pop序列将要弹出的元素相同的元素,则这个pop序列不可能是push序列的一个弹出序列,循环下去,直到pop序列完成,或肯定了pop不是朴实的一个出栈顺序。

  基于以上分析,代码如下:

bool IsPopSeries(int *push, int *pop, int n)
{
    assert (push != NULL);

    assert (pop != NULL);

    assert (n > 0);

    int *Stack = NULL;
    int nTop = -1;//nTop指向栈顶元素,不是栈顶+1

    if (NULL == (Stack = (int *)malloc (n * sizeof (int))))
    {
        printf ("Fail to malloc space to Stack!\n");
        exit(1);
    }

    int i = 0;
    int j = 0;

    while (j < n)
    {
        if (nTop >= 0)
        {
            // 若栈不为空则首先检查栈顶元素是否符合要求
            if (Stack[nTop] == pop[j])
            {
                --nTop;
                ++j;

                continue;
            }
        }

        while ((i < n) && (push[i] != pop[j]))
        {
            // 在push序列中找下一个要弹出的元素
            Stack[++nTop] = push[i++];
        }

        if (i >= n)
        {
            // push序列找完了都没有找到pop序列中将要弹出的元素
            // 则pop不可能是push的弹出序列
            break;
        }

        ++i;
        ++j;
    }

    if (j < n)
    {
        return (false);
    }
    
    return true;
}

 以

1 2 3  4 5

4 5 3 2 1 为例:

开始

开始
push[0]!=pop[0]
while ((i < n) && (push[i] != pop[j]))
{
// 在push序列中找下一个要弹出的元素
Stack[++nTop] = push[i++];
}
只要pop[j]和push不相等
把push压入中,
stack变成了1,2,3.
nTop=2; i=3;


++i,++j;
i=4,j=1; 每次遇到相等的就增加j


第二次while:
nTop>=0;
stack[2]!=pop[1]

++i,++j
i=5;j=2;

while(i<n && push[i]!=pop[j])

 

另外一种代码:

这题可以利用一个辅助栈来实现,过程很简单,但描述起来会有一些困难,所以直接代码了。
 
#include 
#include
using namespace std;
bool isStackPushPop(int a[], int b[], int n)
{
 stack ms;
 int i = -1, j = 0;
 while (i < n && j < n)
 {
  if (ms.empty() || ms.top() != b[j])
   ms.push(a[++i]);
  else
  {
   ms.pop();
   j ++;
  }
 }
 return ms.empty();
}
int main()
{
 int a[] = {1,2,3,4,5};
 int b[] = {5,4,2,3,1};
 cout< return 0;
}

注意i开始为-1,使用a[++i],

如果我改成i=0; a[i++]错误。为什么?

我们假设n=2;push一个元素后i=1,push下一个后i=2;

这是i<n不成立就退出了,(假设push为:1,2,pop为2,1,应该是合法的,由于推出就变成不合法了),

改正办法:只需把i<n改成i<=n即可

int i,j;
i=j=0;
while(i<=n && j<n)
{
if(s.empty() || s.top()!=b[j])
{
s.push(a[i++]);
}

 

 给定一个入栈序列,求所有可能的出栈序列

看以前的:http://www.cnblogs.com/youxin/p/3351944.html