返回分类:全部文章 >> 基础知识
返回上级:编程基础 - 栈(Stack)
“混洗”原意就是重新洗牌。
在栈的混洗中,假定元素为1到n,入栈顺序也为1到n,在应用中有可能会需要这些问题的答案:
(1) 出栈序列共有多少种可能?
(2) 求出所有出栈(入栈)序列。
(3) 给出一个出栈序列,问栈的容量最少需要多少?
(4) 给出一个(或多个)出栈序列,问此序列是不是(哪一个序列不是)原序列混洗得到的?
之后我们主要进行问题(1)和问题(2)的求解,代码将使用C++。
在元素个数较少的情况下,我们可以写出它们的出栈顺序,例如:
元素个数为2时,入栈序列为[1, 2],出栈顺序为:
元素个数为3时,入栈顺序为[1, 2, 3],出栈顺序为:
同理,可以写出元素个数为4时的出栈顺序。
有了这些,我们可以观察其规律,推导更多元素的出栈顺序。
注意:元素个数0时,有1种出栈顺序(空)。元素个数1时,同样也只有1种出栈顺序(就是1本身)
我们假设元素个数为n,出栈顺序共有m种,则:
(提示:公式在CSDN的app中可能显示乱码,请在网页中打开)
n = 0 → m 0 = 1 n = 1 → m 1 = 1 n = 2 → m 2 = 2 n = 3 → m 3 = 5 ⋮ → ⋮ n → m n \begin{array}{cc} n=0 & \to & m_0=1 \\ n=1 & \to & m_1=1 \\ n=2 & \to & m_2=2 \\ n=3 & \to & m_3=5 \\ \vdots & \to & \vdots \\ n & \to & m_n \end{array} n=0n=1n=2n=3⋮n→→→→→→m0=1m1=1m2=2m3=5⋮mn
我们取第一个元素(即1)的位置来做推导:
当n为2时,元素1的出栈位置的下标为[0, 1]
元素1的位置 | 左侧出栈序列个数 | 右侧出栈序列个数 | 总序列个数 |
---|---|---|---|
0 | n = 0 n=0 n=0 , m 0 = 1 m_0=1 m0=1 | n = 1 n=1 n=1 , m 1 = 1 m_1=1 m1=1 | m 0 × m 2 = 1 m_0 \times m_2 = 1 m0×m2=1 |
1 | n = 1 n=1 n=1 , m 1 = 1 m_1=1 m1=1 | n = 0 n=0 n=0 , m 0 = 1 m_0=1 m0=1 | m 1 × m 1 = 1 m_1 \times m_1 = 1 m1×m1=1 |
出栈序列个数为这2个位置的情况的总和,即: m 2 = m 0 × m 1 + m 1 × m 0 = 2 m_2 = m_0 \times m_1 + m_1 \times m_0 = 2 m2=m0×m1+m1×m0=2
当n为3时,元素1的出栈位置的下标为[0, 1, 2]
元素1的位置 | 左侧出栈序列个数 | 右侧出栈序列个数 | 总序列个数 |
---|---|---|---|
0 | n = 0 n=0 n=0 , m 0 = 1 m_0=1 m0=1 | n = 2 n=2 n=2 , m 2 = 2 m_2=2 m2=2 | m 0 × m 2 = 2 m_0 \times m_2 = 2 m0×m2=2 |
1 | n = 1 n=1 n=1 , m 1 = 1 m_1=1 m1=1 | n = 1 n=1 n=1 , m 1 = 1 m_1=1 m1=1 | m 1 × m 1 = 1 m_1 \times m_1 = 1 m1×m1=1 |
2 | n = 2 n=2 n=2 , m 2 = 2 m_2=2 m2=2 | n = 0 n=0 n=0 , m 0 = 1 m_0=1 m0=1 | m 2 × m 0 = 2 m_2 \times m_0 = 2 m2×m0=2 |
出栈序列个数为这3个位置的情况的总和,即: m 3 = m 0 × m 2 + m 1 × m 1 + m 2 × m 0 = 5 m_3 = m_0 \times m_2 + m_1 \times m_1 + m_2 \times m_0 = 5 m3=m0×m2+m1×m1+m2×m0=5
当n为4时,元素1的出栈位置的下标为[0, 1, 2, 3]
元素1的位置 | 左侧出栈序列个数 | 右侧出栈序列个数 | 总序列个数 |
---|---|---|---|
0 | n = 0 n=0 n=0 , m 0 = 1 m_0=1 m0=1 | n = 3 n=3 n=3 , m 3 = 5 m_3=5 m3=5 | m 0 × m 3 = 5 m_0 \times m_3 = 5 m0×m3=5 |
1 | n = 1 n=1 n=1 , m 1 = 1 m_1=1 m1=1 | n = 2 n=2 n=2 , m 2 = 2 m_2=2 m2=2 | m 1 × m 2 = 2 m_1 \times m_2 = 2 m1×m2=2 |
2 | n = 2 n=2 n=2 , m 2 = 2 m_2=2 m2=2 | n = 1 n=1 n=1 , m 1 = 1 m_1=1 m1=1 | m 2 × m 1 = 2 m_2 \times m_1 = 2 m2×m1=2 |
3 | n = 3 n=3 n=3 , m 3 = 5 m_3=5 m3=5 | n = 0 n=0 n=0 , m 0 = 1 m_0=1 m0=1 | m 3 × m 0 = 5 m_3 \times m_0 = 5 m3×m0=5 |
出栈序列个数为这4个位置的情况的总和,即: m 4 = m 0 × m 3 + m 1 × m 2 + m 2 × m 1 + m 3 × m 0 = 14 m_4 = m_0 \times m_3 + m_1 \times m_2 + m_2 \times m_1 + m_3 \times m_0 = 14 m4=m0×m3+m1×m2+m2×m1+m3×m0=14
同理可以得到:
m n = m 0 × m n − 1 + m 1 × m n − 2 + ⋯ + m n − 2 × m 1 + m n − 1 × m 0 m_n = m_0 \times m_{n-1} + m_1 \times m_{n-2} + \cdots + m_{n-2} \times m_1 + m_{n-1} \times m_0 mn=m0×mn−1+m1×mn−2+⋯+mn−2×m1+mn−1×m0
这是典型的卡特兰数(Catalan Number)公式,即:
m n = 1 n + 1 ( 2 n n ) = 1 n + 1 C 2 n n m_n = \frac{1}{n+1} \begin{pmatrix} 2n \\ n \end{pmatrix} = \frac{1}{n+1} C_{2n}^{n} mn=n+11(2nn)=n+11C2nn
Tips:卡特兰数参考链接
关于卡特兰数详细的推导过程与说明见如下链接:
Wikipedia(en): Catalan number
百度百科:卡特兰数
这个就是说每一个元素都要进一次栈,并且都要出一次栈。
我们需要的头文件(main.h
):
#pragma once
#include // 向量列表
#include // 栈
这是我们所需的递归函数:
// 计算所有出栈序列(下标从0计算)
// params:
// length: 入栈元素个数
// outputs: 输出所有出栈序列结果(下标)
void Shuffling(int length, vector<vector<int>> &outputs)
{
if (length <= 0)
{
return;
}
stack<int> inStack; // 入栈序列
vector<int> outList; // 出栈序列
Internal_ShufflingRecursion(length, 0, inStack, outList, outputs); // 递归求解
}
// 内部计算递归
// params:
// length: 入栈元素个数
// index: 当前元素下标
// inStack: 当前入栈序列
// outList: 当前出栈序列
// outputs: 输出的所有出栈序列结果(下标)
void Internal_ShufflingRecursion(int length,
int index,
stack<int> &inStack,
vector<int> &outList,
vector<vector<int>> &outputs)
{
// 如果全部元素都出栈
if (outList.size() == length)
{
outputs.push_back(outList); // 将其加入到结果`outputs`中
return;
}
// 如果当前元素下标小于总元素个数,开始入栈递归
if (index < length)
{
inStack.push(index); // 入栈当前下标
Internal_ShufflingRecursion(length, index + 1, inStack, outList, outputs);
inStack.pop(); // 出栈栈顶元素
}
// 如果栈元素个数大于0,开始出栈递归
if (inStack.size() > 0)
{
int top = inStack.top(); // 获取栈顶元素下标
inStack.pop(); // 出栈栈顶元素
outList.push_back(top); // 将出栈元素加入到出栈序列中
// 此处递归,将判断元素出栈后,是否还需要入栈出栈操作(后面是否还有元素没有入过栈)
Internal_ShufflingRecursion(length, index, inStack, outList, outputs);
inStack.push(top); // 重新入栈原栈顶元素
outList.pop_back(); // 将原栈顶元素移除
}
}
最后是我们的主函数:
#include "main.h"
#include
using namespace std;
void Shuffling(int length, vector<vector<int>> &outputs);
void Internal_ShufflingRecursion(int length,
int index,
stack<int> &inStack,
vector<int> &outList,
vector<vector<int>> &outputs);
int main()
{
int length = -1;
while (true)
{
cout << "栈的混洗,请输入栈长度:";
while (!(cin >> length) || length < 0)
{
cin.clear();
cin.ignore(numeric_limits<streamsize>::max(), '\n');
cout << "输入不合法,请重新输入栈长度:";
}
cin.ignore(numeric_limits<streamsize>::max(), '\n');
vector<vector<int>> outputs; // 定义所有出栈结果
Shuffling(length, outputs); // 计算出栈序列
// 打印所有出栈顺序
for (int i = 0; i < outputs.size(); i++)
{
cout << i << ": ";
for (int j = 0; j < outputs[i].size(); j++)
{
// 出栈结果是下标从0开始,元素是从1开始
// 如果原始数据不是1-n
// 这里替换成`原入栈序列[outputs[i][j]]`
cout << (outputs[i][j] + 1) << " ";
}
cout << endl;
}
cout << "一共有" << outputs.size() << "种出栈序列。" << endl;
cout << endl;
}
system("pause");
return 0;
}
如果需要入栈序列,只需要将outputs
中每个序列反向输出即可。
栈的混洗,请输入栈长度:3
0: 3 2 1
1: 2 3 1
2: 2 1 3
3: 1 3 2
4: 1 2 3
一共有5种出栈序列。
栈的混洗,请输入栈长度:4
0: 4 3 2 1
1: 3 4 2 1
2: 3 2 4 1
3: 3 2 1 4
4: 2 4 3 1
5: 2 3 4 1
6: 2 3 1 4
7: 2 1 4 3
8: 2 1 3 4
9: 1 4 3 2
10: 1 3 4 2
11: 1 3 2 4
12: 1 2 4 3
13: 1 2 3 4
一共有14种出栈序列。