入出栈序列的种数—递归及卡特兰数(Catalan)实现

本文探讨了给定元素依次进栈时所有可能的出栈序列数量问题,并给出了递归算法及Catalan数的公式,通过实例展示了如何计算不同数量元素的所有可能出栈序列。

问题描述:

    上学期数据结构课讲栈的时候,老师曾出过这样一个问题,将1,2,3这三个数依次进栈,问出栈的序列有多少种?嘻嘻,大家不妨先在草稿纸上先写一下所能想到的序列,看来大家都很聪明,一共是有5种,即123、132、213、231、321。


解题思路:

    首先想到的是用递归实现,一共有三种操作 ①栈为空时,必须入栈而不能出栈 ②入栈 ③出栈 这里要注意的是,当全部元素入栈后,出栈序列其实就已经定了

递归代码如下:

#include<iostream>
using namespace std;
int stack(int n,int m){  //n是准备入栈的个数  m是在栈中的个数 
	if(n==0)return 1;   //全部元素都入栈了 出栈序列也就决定了 返回1种结果 
	if(m==0)return stack(n-1,1);  //栈为空 必须入栈 
	return stack(n-1,m+1)+stack(n,m-1);  //入栈 和 出栈 
}
int main(){
	for(int i=1;i<=10;i++){
		printf("1~%d依次进栈的出栈序列有:%d种\n",i,stack(i,0));
	}
	return 0;
}

输出结果如下:

1~1依次进栈的出栈序列有:1种
1~2依次进栈的出栈序列有:2种
1~3依次进栈的出栈序列有:5种
1~4依次进栈的出栈序列有:14种
1~5依次进栈的出栈序列有:42种
1~6依次进栈的出栈序列有:132种
1~7依次进栈的出栈序列有:429种
1~8依次进栈的出栈序列有:1430
1~9依次进栈的出栈序列有:4862
1~10依次进栈的出栈序列有:16796

这种递归的效果如下图(以1、2、3入栈为例):


其实这道题是有公式的,叫Catalan数:是组合数学中一个常出现在各种计数问题中出现的数列。其递归式如下:

h(n)= h(0)*h(n-1)+h(1)*h(n-2) + ... + h(n-1)h(0) (其中n>=2,h(0) = h(1) = 1)

公式推导参考链接:https://blog.youkuaiyun.com/zyearn/article/details/7758716


根据递归式,写出相应的算法

#include<iostream>
using namespace std;
int main(){
	int h[1000]={0};
	h[0]=h[1]=1;  //这里假设h[0]也等于1 
	for(int i=2;i<=10;i++){
		for(int j=0;j<i;j++){
			h[i]+=h[j]*h[i-1-j];
		}
	} 
	for(int i=1;i<=10;i++)printf("1~%d依次进栈的出栈序列有:%d种\n",i,h[i]);
	return 0;
}


当然卡特兰树还有其他应用领域


<think>题目要求:出栈序列统计 问题描述:给定一个的入序列和一个出栈序列,统计出栈序列的合法性以及所有可能的出栈序列量。 但实际上,这里可能有两个不同的需求: 1. 给定入序列出栈序列,判断出栈序列是否合法。 2. 给定入序列,求所有可能的出栈序列量(即卡特兰)。 由于问题描述是“出栈序列统计”,我推测可能是第二个需求,即求量。但为了全面,我将分别回答两个问题。 首先,我们先解决第一个问题:判断出栈序列是否合法。 思路: 使用一个来模拟入出栈过程。遍历出栈序列,对于每个出栈元素: - 如果当前顶元素等于出栈元素,则弹出栈顶。 - 否则,将入序列中尚未入的元素依次,直到顶元素等于出栈元素为止。如果入序列全部入后仍未找到,则该出栈序列不合法。 接着,我们解决第二个问题:求所有可能的出栈序列量。 这是一个经典的卡特兰问题。对于n个不同元素的入序列,所有合法的出栈序列量为Catalan(n) = C(2n, n)/(n+1)。 但是,如果入序列是固定顺序(比如1,2,3,...,n),那么出栈序列量就是Catalan。如果入序列是任意的,那么量也是Catalan,因为元素是互异的,且的性质决定了量只与元素个数有关。 所以,我们只需要计算Catalan即可。 然而,如果要求列出所有可能的出栈序列(而不仅仅是量),那么就需要使用回溯法(DFS)或动态规划来生成。但题目要求是“统计”,所以通常是指量。 根据题目,我推测是要求量。但为了全面,我将给出两个问题的解决方案。 问题1:判断出栈序列是否合法(代码实现) 假设入序列为push_order,出栈序列为pop_order,且两个序列都是列表,元素互不相同。 问题2:计算所有可能的出栈序列量(即Catalan) 现在,我将分别给出代码。 首先,问题1:判断出栈序列是否合法。 ```python def is_valid_pop_order(push_order, pop_order): if len(push_order) != len(pop_order): return False stack = [] push_index = 0 for num in pop_order: # 如果顶元素等于当前出栈元素,则弹出 if stack and stack[-1] == num: stack.pop() else: # 从入序列中还未入的部分找到该元素,并将其前面的元素都入 while push_index < len(push_order) and push_order[push_index] != num: stack.append(push_order[push_index]) push_index += 1 # 如果已经遍历完入序列还没找到,则说明不合法 if push_index >= len(push_order): return False # 此时push_order[push_index]就是num,所以跳过这个元素(相当于入后立即出栈) push_index += 1 return True ``` 测试: ```python push_order = [1, 2, 3, 4, 5] pop_order1 = [4, 5, 3, 2, 1] # 合法 pop_order2 = [4, 3, 5, 1, 2] # 不合法 print(is_valid_pop_order(push_order, pop_order1)) # True print(is_valid_pop_order(push_order, pop_order2)) # False ``` 解释: 我们用一个来模拟。遍历出栈序列,对于每个元素,如果顶就是它,则弹出;否则,将入序列中该元素之前的元素都入(如果入序列中该元素已经入过了,那么一定在里,所以顶应该就是它,否则就不合法)。如果入序列中已经找不到这个元素(说明已经入过了,但不在顶,且被压在下面,那么无法直接弹出),则返回False。 然后,问题2:计算所有可能的出栈序列量(卡特兰) 我们可以使用动态规划计算卡特兰,或者使用公式。 公式:Catalan(n) = (1/(n+1)) * C(2n, n) 但由于组合计算可能溢出,我们可以用递推关系: Catalan(0) = 1 Catalan(n+1) = Catalan(n) * (4*n+2) // (n+2) 或者用动态规划: 设dp[i]表示i个元素的不同出栈序列量,则: dp[0] = 1 dp[i] = sum_{j=0}^{i-1} dp[j] * dp[i-1-j] (j表示第一个元素出栈内元素个数,那么第一个元素在入后会在某个时刻出栈,此时它把序列分成两部分:前j个元素和后i-1-j个元素) 但这里我们使用递推公式,因为更高效。 代码: ```python def catalan(n): if n <= 0: return 1 cat = [0] * (n+1) cat[0] = 1 for i in range(1, n+1): cat[i] = cat[i-1] * (4*i-2) // (i+1) return cat[n] ``` 或者更简洁的写法(不存储整个数组): ```python def catalan(n): c = 1 for i in range(1, n+1): c = c * (4*i-2) // (i+1) return c ``` 测试: ```python print(catalan(3)) # 5 print(catalan(4)) # 14 ``` 注意:当n=0时,有一个序列(空序列)。n=1时,只有一个序列。 现在,如果题目要求是:给定入序列(固定顺序,如1,2,...,n),求所有可能的出栈序列量,那么直接返回catalan(n)即可。 但是,如果入序列不是1~n,而是任意互异序列,那么量也是catalan(n),因为出栈序列量只与元素个数有关,与具体元素无关。 所以,我们只需要知道元素个数n,然后计算catalan(n)。 但是,如果题目要求是:给定入序列(可能包含重复元素),那么问题就复杂了,因为重复元素会导致不同的出栈序列可能相同。但通常题目中元素都是互异的。 因此,针对原问题“出栈序列统计”,我推测是求量,即卡特兰。 综上所述,如果问题是要判断一个出栈序列是否合法,则使用第一个函;如果是要统计所有可能的出栈序列量,则使用第二个函。 最后,按照要求,提出相关问题。 §§相关问题§§: 1. 如何生成所有可能的出栈序列(而不仅仅是量)? 2. 如果入序列中有重复元素,如何统计不同的出栈序列量? 3. 使用非递归方法实现判断出栈序列是否合法。 4. 解释卡特兰的递推公式的原理。</think>### 出栈序列统计 #### 问题分析 给定一个固定顺序的入序列(如 1,2,3,...,n),统计所有可能的合法出栈序列量。这是一个经典的卡特兰Catalan Number)问题。对于 n 个元素,合法出栈序列量为: \[ C_n = \frac{1}{n+1} \binom{2n}{n} \] #### 解决方案 以下是使用 Python 计算卡特兰的代码: ```python def catalan(n): """ 计算第 n 个卡特兰 :param n: 元素量 :return: 合法出栈序列量 """ # 处理边界情况 if n <= 0: return 1 # 使用动态规划计算卡特兰 dp = [0] * (n + 1) dp[0] = 1 # 0 个元素的序列只有 1 种可能 for i in range(1, n + 1): for j in range(i): dp[i] += dp[j] * dp[i - 1 - j] return dp[n] # 示例:计算 n=3 时的合法出栈序列量 n = 3 print(f"元素量 {n} 的合法出栈序列量: {catalan(n)}") # 输出: 元素量 3 的合法出栈序列量: 5 ``` #### 代码解释 1. **动态规划组 `dp`**: - `dp[i]` 表示 `i` 个元素的合法出栈序列量。 - 初始化 `dp[0] = 1`(空序列只有一种可能)。 2. **递推关系**: - 对于每个 `i`(1 到 n),遍历所有可能的划分点 `j`(0 到 i-1)。 - 递推公式:`dp[i] += dp[j] * dp[i - 1 - j]` - **原理**:第一个元素在第 `k` 步出栈时: - 前 `k` 个元素有 `dp[k]` 种出栈方式。 - 后 `n-k-1` 个元素有 `dp[n-k-1]` 种出栈方式。 3. **时间复杂度**: - 双重循环,时间复杂度为 \(O(n^2)\)。 - 空间复杂度为 \(O(n)\)。 #### 示例说明 - **n=3** 时,合法出栈序列共 5 种: ``` [1,2,3] -> [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,2,1] ``` 注意:`[3,1,2]` 不合法(1 不能在 2 之前出栈)。 #### 学公式优化 对于大 `n`,可直接用卡特兰公式计算: ```python import math def catalan_formula(n): return math.comb(2 * n, n) // (n + 1) # 示例 print(catalan_formula(10)) # 输出: 16796 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值