zoukankan      html  css  js  c++  java
  • 打印序列的所有出栈顺序(回溯法)及复杂度分析(卡特兰数)

    题目描述

    对任意给定的n,输出 1,2,…,n 的所有出栈顺序。

    输入

    正整数 n(1≤n≤9)

    输出

    输出 1,2,…,n 的所有出栈顺序
    示例
    输入:
    3
    输出:
    3 2 1
    2 3 1
    2 1 3
    1 3 2
    1 2 3
     

    思路

    这题是递归、回溯的思想,对于当前元素,只有2种操作:
    (1) 进栈,处理下一个元素
    (2) 栈非空,栈顶元素出栈,继续处理当前元素
    注意:
    (1) 47行的popVal不能是全局变量, 否则下面回溯的就不是之前的popVal了 
    (2) 56,57行要回溯,这里当时没想明白为什么要回溯,要仔细思考原因,要能模拟出这个递归的过程。
     

    代码实现

     1 #include <iostream>
     2 #include <vector>
     3 #include <stdio.h>
     4 #include <stack>
     5 #include <deque>
     6 
     7 using namespace std;
     8 
     9 int n;
    10 
    11 //因为需要遍历栈中剩余的元素,但又不能令元素出栈, 
    12 //所以不能使用stack,这里把双端队列当栈使用
    13 deque<int> mystack; 
    14 
    15 //保存当前已经出栈的元素 
    16 vector<int> p;    
    17 
    18 void dfs(int i)
    19 {
    20     if(i == n+1)
    21     {
    22         // 打印已经出栈的元素 
    23         for(int j = 0; j < p.size(); ++j)
    24             printf("%d ", p[j]);
    25         
    26         // 打印栈中剩余的元素,因为要回溯,所以剩余的元素还不能出栈 
    27         if(!mystack.empty())
    28         {
    29             for(int j = mystack.size()-1; j >= 0; --j)
    30             {
    31                 printf("%d ", mystack[j]);
    32             }
    33         }
    34         
    35         printf("
    ");    
    36         return;
    37     }
    38     
    39     mystack.push_back(i);    //当前元素入栈 
    40     dfs(i+1);                 //处理下一个元素
    41     mystack.pop_back();        //回溯
    42      
    43     //栈非空,栈顶元素出栈 
    44     if(!mystack.empty())
    45     {
    46         //不能把popVal定义为全局变量,否则下面回溯的就不是之前的popVal了 
    47         int popVal = mystack.back();    
    48         //栈顶元素出栈 
    49         mystack.pop_back();
    50         //出栈的元素保存到p 
    51         p.push_back(popVal);    
    52         
    53         dfs(i);    //继续处理当前元素
    54         
    55         //回溯,这里一定要回溯,不回溯会出错! 
    56         p.pop_back();
    57         mystack.push_back(popVal);      
    58     }      
    59 } 
    60 
    61 int main()
    62 {
    63     scanf("%d", &n);
    64     dfs(1);
    65     
    66     return 0;
    67 }

    复杂度分析

    空间复杂度:O(n)
    递归的深度为n,所以空间复杂度为O(n)。
    时间复杂度:
    可以证明(证明过程见下文),所有的出栈顺序总数为卡特兰数,1,2,3,..,n 一共有h(n)种出栈序列,其中:
    出栈次序总数为卡特兰数的证明过程:
    出栈次序
    一个栈(无穷大)的进栈序列为1,2,3,…,n,有多少个不同的出栈序列? 
    常规分析
    首先,我们设 f(n)=序列个数为n的出栈序列种数。(我们假定,最后出栈的元素为k,显然,k取不同值时的情况是相互独立的,也就是求出每种k最后出栈的情况数后可用加法原则,由于k最后出栈,因此,在k入栈之前,比k小的值均出栈,此处情况有f(k-1)种,而之后比k大的值入栈,且都在k之前出栈,因此有f(n-k)种方式,由于比k小和比k大的值入栈出栈情况是相互独立的,此处可用乘法原则,f(n-k)*f(k-1)种,求和便是Catalan递归式。)
    首次出空之前第一个出栈的序数k将1~n的序列分成两个序列,其中一个是1~k-1,序列个数为k-1,另外一个是k+1~n,序列个数是n-k。
    此时,我们若把k视为确定一个序数,那么根据乘法原理,f(n)的问题就等价于——序列个数为k-1的出栈序列种数乘以序列个数为n - k的出栈序列种数,即选择k这个序数的 f(n) =f(k-1) × f(n-k)。而k可以选1到n,所以再根据加法原理,将k取不同值的序列种数相加,得到的总序列种数为:f(n) = f(0)f(n-1) + f(1)f(n-2) + …… + f(n-1)f(0)。
    看到此处,再看看卡特兰数的递推式,答案不言而喻,即为 f(n) = h(n) = C(2n,n) / (n+1) = C(2n,n) - C(2n,n-1)(n=0,1,2 ……)。
    最后,令f(0)=1,f(1)=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)=h(n)。

    问题的变种

    一个有n个1和n个-1组成的字串,且前k个数的和均不小于0,那这种字串的总数为多少?
    提示:将入栈操作记为1,出栈记为-1,即转化成了求所有出栈序列的问题。
  • 相关阅读:
    poj 1321
    Cocos2D-html5 公布游戏js编译为jsc
    Android定位开发之百度定位、高德定位、腾讯定位,三足鼎立一起为我所用!
    python 设计模式之 单例模式
    css画电脑键盘
    【C/C++学院】(23)Mysql数据库编程--C语言编程实现mysqlclient
    用DOM4J包实现对xml文件按属性分离。
    MVC4中AJAX Html页面打开调用后台方法实现动态载入数据库中的数据
    贝勒爷教你怎样在Mac上安装Microsoft Office
    6.Swift教程翻译系列——Swift集合类型
  • 原文地址:https://www.cnblogs.com/FengZeng666/p/13891463.html
Copyright © 2011-2022 走看看