编程基础 - 栈的应用 - 混洗(Stack Shuffling)
文章目录
1 混洗简述
“混洗”原意就是重新洗牌。
在栈的混洗中,假定元素为1到n,入栈顺序也为1到n,在应用中有可能会需要这些问题的答案:
-
(1) 出栈序列共有多少种可能?
-
(2) 求出所有出栈(入栈)序列。
-
(3) 给出一个出栈序列,问栈的容量最少需要多少?
-
(4) 给出一个(或多个)出栈序列,问此序列是不是(哪一个序列不是)原序列混洗得到的?
之后我们主要进行问题(1)和问题(2)的求解,代码将使用C++。
2 出栈序列共有多少种可能?
在元素个数较少的情况下,我们可以写出它们的出栈顺序,例如:
-
元素个数为2时,入栈序列为[1, 2],出栈顺序为:
- [1, 2]:入1、出1、入2、出2
- [2, 1]:入1,入2,出2,出1
-
元素个数为3时,入栈顺序为[1, 2, 3],出栈顺序为:
- [1, 2, 3]:入1,出1,入2,出2,入3,出3
- [1, 3, 2]:入1,出1,入2,入3,出3,出2
- [2, 1, 3]:入1,入2,出2,出1,入3,出3
- [2, 3, 1]:入1,入2,出2,入3,出3,出1
- [3, 2, 1]:入1,入2,入3,出3,出2,出1
同理,可以写出元素个数为4时的出栈顺序。
有了这些,我们可以观察其规律,推导更多元素的出栈顺序。
注意:元素个数0时,有1种出栈顺序(空)。元素个数1时,同样也只有1种出栈顺序(就是1本身)
我们假设元素个数为n,出栈顺序共有m种,则:
(提示:公式在CSDN的app中可能显示乱码,请在网页中打开)
我们取第一个元素(即1)的位置来做推导:
-
当n为2时,元素1的出栈位置的下标为[0, 1]
元素1的位置 左侧出栈序列个数 右侧出栈序列个数 总序列个数 0 , , 1 , , 出栈序列个数为这2个位置的情况的总和,即:
-
当n为3时,元素1的出栈位置的下标为[0, 1, 2]
元素1的位置 左侧出栈序列个数 右侧出栈序列个数 总序列个数 0 , , 1 , , 2 , , 出栈序列个数为这3个位置的情况的总和,即:
-
当n为4时,元素1的出栈位置的下标为[0, 1, 2, 3]
元素1的位置 左侧出栈序列个数 右侧出栈序列个数 总序列个数 0 , , 1 , , 2 , , 3 , , 出栈序列个数为这4个位置的情况的总和,即:
同理可以得到:
这是典型的卡特兰数(Catalan Number)公式,即:
Tips:卡特兰数参考链接
关于卡特兰数详细的推导过程与说明见如下链接:
Wikipedia(en): Catalan number
百度百科:卡特兰数
3 求出所有出栈(入栈)序列(C++ Code)
这个就是说每一个元素都要进一次栈,并且都要出一次栈。
3.1 头文件main.h
我们需要的头文件(main.h
):
#pragma once
#include <vector> // 向量列表
#include <stack> // 栈
3.2 主函数文件main.cpp
这是我们所需的递归函数:
// 计算所有出栈序列(下标从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 <iostream>
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.3 运行结果
栈的混洗,请输入栈长度: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种出栈序列。