编程基础 - 栈的应用 - 混洗(Stack Shuffling)

编程基础 - 栈的应用 - 混洗(Stack Shuffling)

返回分类:全部文章 >> 基础知识

返回上级:编程基础 - 栈(Stack)


文章目录

  • 编程基础 - 栈的应用 - 混洗(Stack Shuffling)
    • 1 混洗简述
    • 2 出栈序列共有多少种可能?
    • 3 求出所有出栈(入栈)序列(C++ Code)
      • 3.1 头文件main.h
      • 3.2 主函数文件main.cpp
      • 3.3 运行结果


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中可能显示乱码,请在网页中打开)

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=3nm0=1m1=1m2=2m3=5mn

我们取第一个元素(即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×mn1+m1×mn2++mn2×m1+mn1×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
百度百科:卡特兰数


3 求出所有出栈(入栈)序列(C++ Code)

这个就是说每一个元素都要进一次栈,并且都要出一次栈。

3.1 头文件main.h

我们需要的头文件(main.h):

#pragma once

#include  // 向量列表
#include  // 栈

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 
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种出栈序列。


你可能感兴趣的:(基础知识)