您的位置:首页 > 其它

n个元素进栈,共有多少种出栈顺序

2016-07-18 22:50 357 查看

1.基于栈的问题分析

我们把n个元素的出栈个数的记为f(n), 那么对于1,2,3, 我们很容易得出:

f(1) = 1 //即 1

f(2) = 2 //即 12、21

f(3) = 5 //即 123、132、213、321、231

然后我们来考虑f(4), 我们给4个元素编号为a,b,c,d, 那么考虑:元素a只可能出现在1号位置,2号位置,3号位置和4号位置(很容易理解,一共就4个位置,比如abcd,元素a就在1号位置)。

分析:

1) 如果元素a在1号位置,那么只可能a进栈,马上出栈,此时还剩元素b、c、d等待操作,就是子问题f(3);

2) 如果元素a在2号位置,那么一定有一个元素比a先出栈,即有f(1)种可能顺序(只能是b),还剩c、d,即f(2), 根据乘法原理,一共的顺序个数为f(1) * f(2);

3) 如果元素a在3号位置,那么一定有两个元素比1先出栈,即有f(2)种可能顺序(只能是b、c),还剩d,即f(1), 根据乘法原理,一共的顺序个数为f(2) * f(1);

4) 如果元素a在4号位置,那么一定是a先进栈,最后出栈,那么元素b、c、d的出栈顺序即是此小问题的解,即f(3);

结合所有情况,即f(4) = f(3) + f(2) * f(1) + f(1) * f(2) + f(3);

为了规整化,我们定义f(0) = 1;于是f(4)可以重新写为:

f(4) = f(0)f(3) + f(1)*f(2) + f(2) f(1) + f(3)*f(0)

然后我们推广到n,推广思路和n=4时完全一样,于是我们可以得到:

f(n) = f(0)*f(n-1) + f(1)*f(n-2) + … + f(n-1)*f(0)

但这只是一个递推公式(若编程实现,需要维护一个一维数组,时间复杂度为O(n^2))。怎么把它转化为通项公式呢,复杂度仅为O(1)?

2. 相关的求解方法

(1)非常规数值分析

对于每一个数来说,必须进栈一次、出栈一次。我们把进栈设为状态‘1’,出栈设为状态‘0’。n个数的所有状态对应n个1和n个0组成的2n位二进制数。由于等待入栈的操作数按照1‥n的顺序排列、入栈的操作数b大于等于出栈的操作数a(a≤b),因此输出序列的总数目=由左而右扫描由n个1和n个0组成的2n位二进制数,1的累计数不小于0的累计数的方案种数。

在2n位二进制数中填入n个1的方案数为c(2n,n),不填1的其余n位自动填0。从中减去不符合要求(由左而右扫描,0的累计数大于1的累计数)的方案数即为所求。

不符合要求的数的特征是由左而右扫描时,必然在某一奇数位2m+1位上首先出现m+1个0的累计数和m个1的累计数,此后的2(n-m)-1位上有n-m个 1和n-m-1个0。如若把后面这2(n-m)-1位上的0和1互换,使之成为n-m个0和n-m-1个1,结果得1个由n+1个0和n-1个1组成的2n位数,即一个不合要求的数对应于一个由n+1个0和n-1个1组成的排列。

反过来,任何一个由n+1个0和n-1个1组成的2n位二进制数,由于0的个数多2个,2n为偶数,故必在某一个奇数位上出现0的累计数超过1的累计数。同样在后面部分0和1互换,使之成为由n个0和n个1组成的2n位数,即n+1个0和n-1个1组成的2n位数必对应一个不符合要求的数。

因而不合要求的2n位数与n+1个0,n-1个1组成的排列一一对应。

显然,不符合要求的方案数为c(2n,n+1)。

由此得出输出序列的总数目=c(2n,n)-c(2n,n+1)=c(2n,n)/(n+1)。其中,n为节点的个数。

我们来看个例子:对于1 2 3这个入栈序列,1 1 0 1 0 0就是一个入栈出栈序列,第一个1代表元素1入栈,然后第二个1代表元素2入栈,然后第三个是0,代表出栈,即元素2出栈,然后第四个是1,代表元素3入栈,然后第五个是0,代表出栈,即元素3出栈,然后第六个是0,代表元素1出栈。最后1 1 0 1 0 0就代表了出栈序列2 3 1。

那么现在的问题就转换为如何求出所有符合条件的0 1序列了。其实这和以下问题相同:给定括号对数,输出所有符合要求的序列。如2对括号,输出有()()或者(())两种。1可以看成’(‘,0可以看成‘)’。

下面贴上本人的程序,并给出详细注释。

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

void func(vector<char>kind,int count[],int n)
{
if(count[0]>=1)
{
kind.push_back('(');
count[0]--;
func(kind,count,n);
count[0]++;
kind.pop_back();
}
if((count[1]>=1) && (count[1]>count[0]))
{
kind.push_back(')');
count[1]--;
func(kind,count,n);
count[1]++;
kind.pop_back();
}
if(kind.size()==2*n)
{
vector<char>::iterator iter;
for(iter=kind.begin();iter!=kind.end();iter++)
{
cout<<(*iter)<<" ";
}
cout<<endl;
}
}

int main()
{
int n;
cout << "please input the number of ():" << endl;
cin>>n;
int count[2]={n-1,n};
vector<char>kind;
kind.push_back('(');
func(kind,count,n);
return 0;
}


count[0]存着左括号数目,count[1]存着右括号数目。一开始kind中压入左括号,因为第一个肯定是左括号。然后count数组初始化为n-1个左括号,n个右括号。然后我们递归的处理。如果剩余左括号数count[0]大于0,就可以把左括号压栈。而对于右括号,栈中左括号个数必须多于右括号个数,也就是剩余右括号个数大于左括号个数,即count[1]>count[0]时,才能将右括号压栈。如果栈中元素个数达到2n时,就把栈中元素输出。

下面贴出出栈序列代码,几乎和上面相同。

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

int number=0;
void func(vector<int>kind,int count[],int n,int A[])
{
if(count[0]>=1)
{
kind.push_back(1);
count[0]--;
func(kind,count,n,A);
count[0]++;
kind.pop_back();
}
if((count[1]>=1) && (count[1]>count[0]))
{
kind.push_back(0);
count[1]--;
func(kind,count,n,A);
count[1]++;
kind.pop_back();
}
if(kind.size()==2*n)
{
vector<int>::iterator iter;
stack<int>stk;
int j=0;
for(iter=kind.begin();iter!=kind.end();iter++)
{
//cout<<(*iter)<<" ";
if(1==(*iter))
{
stk.push(A[j]);
j++;
}
else
{
cout<<stk.top()<<" ";
stk.pop();
}
}
number++;
cout<<endl;
}
}

int main()
{
int n,i;
cout << "please input the number:" << endl;
cin>>n;
int A
;
cout << "please input the push sequence:" << endl;
for(i=0;i<n;i++)
{
cin>>A[i];
}
int count[2]={n-1,n};
vector<int>kind;
kind.push_back(1);

cout<<"the result is:"<<endl;
func(kind,count,n,A);
cout<<"total:"<<number<<endl;
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: