栈及其应用

198 阅读10分钟

使用线性表实现

时间复杂度

pop()
O(1)O(1)
push()

栈长度不变时:

O(1)O(1)

栈长度增加时:

O(stackTop+1)O(stackTop+1)
top()
O(1)O(1)
完整代码
#include<iostream>
using namespace std;

// 抽象类
template<class T>class stack {
public:
    virtual ~stack() {}
    virtual bool empty() const = 0; // 返回 true, 表示栈空
    virtual int size() const = 0; // 返回栈中元素个数
    virtual T& top() = 0; // 返回栈顶元素的引用
    virtual void pop() = 0; // 删除栈顶元素
    virtual void push(const T& theElement) = 0; // 将元素 theElement 压入栈顶
};

// 使用数组实现栈
template<class T>
class arrayStack :public stack<T> {
public:
    arrayStack(int initialcapacity = 10);
    ~arrayStack() { 
        delete[] stack; 
    }
    bool empty() const {
        // 栈顶的下标是 -1 则栈空
        return stackTop == -1; 
    }
    int size() const {
        // 栈中元素个数, 栈顶下标 + 1
        return stackTop + 1;
    }
    T& top() {
        if (stackTop == -1)
            throw "栈空";
        return stack[stackTop];
    }
    void pop() {
        if (stackTop == -1)
                throw "栈空";
        stack[stackTop--].~T(); // T 的析构函数
    }

    void push(const T & theElement);
private:
    int stackTop; // 当前栈顶索引
    int arrayLength; // 栈容量
    T * stack; // 元素数组
};

// 构造函数
template<class T>
arrayStack<T>::arrayStack(int initialCapacity) {
    if (initialCapacity < 1) {
        throw "栈的大小必须 > 0";
    }
    arrayLength = initialCapacity;
    stack = new T[arrayLength]; 
    stackTop = -1;
}

// 改变数组长度
template<class T>
void changeLength(T*& a, int oldLength, int newLength) {
    if (newLength < 0) {
        throw "新长度必须大于 0";
    }
    T* temp = new T[newLength]; // 新数组
    int number = min(oldLength, newLength); // 需要复制的元素个数
    copy(a, a + number, temp); // 拷贝元素到新数组(temp)
    delete[] a; // 释放老数组的内存空间
    a = temp; // 重新指向新数组
};

// 入栈
template<class T>
void arrayStack<T>::push(const T& theElement) {
    if (stackTop = arrayLength - 1) {
        // 空间已满, 容量加倍
        changeLength(stack, arrayLength, 2 * arrayLength);
        arrayLength *= 2;
    }
    // 在栈顶插入元素, 栈顶下标需要先自增
    stack[++stackTop] = theElement;
}

int main() {
    arrayStack<int> s;
    s.push(10);
    s.push(12);
    cout << "栈顶元素" << s.top();
    return 0;
}

使用链表实现

注意链表实现的栈 next 指针指向上一个元素(即从栈顶一直指向栈底)

image.png

时间复杂度

都是 O(1)O(1)

完整代码
#include<iostream>
using namespace std;

// 抽象类
template<class T>class stack {
public:
    virtual ~stack() {}
    virtual bool empty() const = 0; // 返回 true, 表示栈空
    virtual int size() const = 0; // 返回栈中元素个数
    virtual T& top() = 0; // 返回栈顶元素的引用
    virtual void pop() = 0; // 删除栈顶元素
    virtual void push(const T& theElement) = 0; // 将元素 theElement 压入栈顶
};

// 节点元素类, 具有 next 指针和 element 数据
template <class T>
struct chainNode {
    // 数据成员
    T element;
    chainNode<T>* next;

    // 方法
    chainNode() { }
    chainNode(const T& element) {
        this->element = element;
    }
    chainNode(const T& element, chainNode<T>* next) {
        this->element = element;
        this->next = next;
    }
};

template <class T>
class linkedStack:public stack<T> {
private:
    chainNode<T>* stackTop;
    int stackSize;
public:
    linkedStack(int initialCapacity = 10) {
        stackTop = NULL; // 栈顶指针 0
        stackSize = 0; // 栈中元素个数
    }
    ~linkedStack();
    bool empty() const {
        return stackSize == 0;
    }
    int size() const {
        return stackSize;
    }
    T& top();
    void pop();
    void push(const T& theElement);
};

// 析构函数
template<class T>
linkedStack<T>::~linkedStack() {
    while (stackTop != NULL) {
        chainNode<T>* nextNode = stackTop->next; // 保存下一个节点
        delete stackTop; // 删除栈顶
        stackTop = nextNode;
    }
}

// 返回栈顶元素值
template<class T>
T& linkedStack <T>::top() {
    if (stackSize == 0)
        throw "栈空";
    return stackTop->element;
}

// 出栈
template<class T>
void linkedStack<T>::pop() {
    if (stackSize == 0)
        throw "栈空";

    chainNode<T>* nextNode = stackTop->next;
    delete stackTop;
    stackTop = nextNode;
    stackSize--;
}

// 入栈
template<class T>
void linkedStack<T>::push(const T& theElement) {
    stackTop = new chainNode<T>(theElement, stackTop);
    stackSize++;
}

int main() {
    linkedStack<int> s;
    s.push(10);
    s.push(12);
    cout << "栈顶元素" << s.top();
    return 0;
}

车厢重排

image.png

步骤

  1. 初始时符合条件的出轨车厢是 1 号
  2. 3 号车厢不符合条件, 入栈 H1, 同理 6 号 和 9 号也不符合 image.png
  3. 继续入栈, 要求入栈的车厢不能比栈顶元素大
  4. 2 号车厢找一个栈顶车厢号最接近它的入栈 image.png
  5. 以此类推 image.png
  6. 1 号车厢满足条件, 输出, 并将符合条件的车厢号变成 2 号
  7. 遍历所有缓冲轨道, 查找有没有 2 号, 如果有, 则 2 出轨, 符合条件的车厢又变成 3 号 image.png
  8. 以此类推, 直到缓冲轨道中没有符合条件的车厢, 此时符合条件的车厢是 5 号 image.png
  9. 重新入栈, 最后 5 号符合条件, 直接输出 image.png
  10. 同理, 缓冲轨道出栈 image.png

C++ 代码

bool railroad(int inputOrder[], int theNumberOfCars, int theNumberOfTracks) {
    numberOfCars = theNumberOfCars; // 车厢数
    numberOfTracks = theNumberOfTracks; // 轨道数
    
    track = new arrayStack<int>[numberOfTracks + 1]; // 创建缓冲轨道(栈)
    int nextCarToOutput = 1; // 符合出轨条件的车厢号(第一个是 1 号车厢)

    // 缓冲轨道中最小的车厢号
    // 初始值是一个不存在的车厢号, 表示轨道中没有车
    smallestCar = numberOfCars + 1;

    for (int i = 1; i <= numberOfCars; i++) // 遍历所有车厢
        if (inputOrder[i] == nextCarToOutput) {
            // 符合出轨条件, 车厢出轨
            cout << "车厢" << inputOrder[i] << "直接输出";
            nextCarToOutput++; // 符合条件的车厢+1(车厢号数只会是连续的)
            while (smallestCar == nextCarToOutput) {
                // 一旦有车厢出轨, 则从缓冲轨道中陆续取出车厢
                // 只有缓冲轨道中最小车厢号 smallestCar 符合出轨条件才能 output
                outputFromHoldingTrack();
                nextCarToOutput++;
            }
        }
        else
            // 车厢不符合出轨条件, 将车厢放入缓冲轨道
            if (!putInHoldingTrack(inputOrder[i]))
                return false;
    return true;
}
bool putInHoldingTrack(int c) {
    // 根据 c 查找符合条件的轨道
    int bestTrack = 0, // 符合条件的轨道, 初始是一个不存在的轨道号(0)

    // 所有缓冲轨道栈顶车厢号中最接近(最佳) c 的车厢号
    // 初始值是一个不可能存在的车厢号
        bestTop = numberOfCars + 1;

    for (int i = 1; i <= numberOfTracks; i++) // 遍历所有轨道
        if (!track[i].empty()) { // 轨道非空
            int topCar = track[i].top(); // 获取轨道栈顶车厢号
            if (c < topCar && topCar < bestTop) {
                // 如果某条轨道的栈顶车厢号大于 c, 且小于 bestTop
                // 让 bestTop 是所有栈顶车厢号最小的那个
                bestTop = topCar;
                bestTrack = i; // 顺便更新轨道号
            }
        }
        else // 轨道为空
            if (bestTrack == 0) bestTrack = i;

    if (bestTrack == 0) return false; // 循环结束了还没有符合条件的轨道
    track[bestTrack].push(c); // 把传入的 c 输入到符合条件的轨道上
    // 更新轨道最小车厢号 smallestCar 和轨道 itsTrack
    if (c < smallestCar) {
        smallestCar = c;
        itsTrack = bestTrack;
    }
    return true;
}
void outputFromHoldingTrack() {
    // itsTrack: 编号最小的车厢所在的轨道号
    track[itsTrack].pop();
    cout << "车厢" << smallestCar << "从缓冲轨道" 
         << itsTrack << "输出" << endl;
    // 更新 smallest 和 itsTrack
    smallestCar = numberOfCars + 2;
    for (int i = 1; i <= numberOfTracks; i++)
        if (!track[i].empty() && (track[i].top() < smallestCar)) {
            smallestCar = track[i].top();
            itsTrack = i;
        }
}

中缀表达式转后缀表达式

仅运算符会入栈

无括号的情况

中缀表达式: a + b × c − d / e

后缀表达式: a b c × + d e / −

  1. 输出 a 后遇到 + 号, 栈顶无元素可以比较, 直接入栈 image.png
  2. 输出 b 后遇到 × 号, 判断优先级高于栈顶的 + 号, 因此入栈 image.png
  3. 输出 c 后遇到 − 号, 判断优先级低于栈顶的 × 号, 因此栈顶出栈 image.png
  4. 判断优先级与栈顶的 + 号相同, 也出栈, 最后 − 号没有元素可以比较了, 直接入栈 image.png
  5. 输出 d 后遇到 / 号, 优先级高于 − 号, 入栈 image.png
  6. 输出 e 后没有运算符了, 把栈元素依次输出 image.png

有括号的情况

中缀表达式: a × ( b + c ) / d

后缀表达式: a b c + × d /

  1. 输出 a 后遇到 × 号, 入栈 image.png
  2. 遇到左括号 (, 入栈 image.png
  3. 输出 b 后遇到 + 号, 入栈 image.png
  4. 输出 c 后遇到右括号 ), 把上一个左括号之后的全部出栈 image.png
  5. 遇到 / 号优先级等于栈顶的 × 号, × 号出栈, / 入栈 image.png
  6. 输出 d 后全部出栈 image.png

c++ 代码

#include<iostream>
#include<string>
#include<stack>
using namespace std;

string changeSuffix(string infix) {
    stack<char> s; // 保存运算符的栈
    string p = ""; // 保存最终结果的字符串
    
    // 遍历字符串
    for (int i = 0; i < infix.length(); i++) {
        // 如果遇到的是字母, 则直接输出
        if (infix[i] >= 'a' && infix[i] <= 'z') {
            p += infix[i];
        }

        // 如果遇到 + 号或者 - 号
        if ('+' == infix[i] || '-' == infix[i]) {
            if (s.empty()) { // 如果栈为空, 则直接入栈
                s.push(infix[i]);
            } else {
                while (!s.empty()) {
                    // 如果栈顶是左括号, 则直接入栈
                    if (s.top() == '(') {
                        break;
                    }

                    p += s.top();
                    s.pop();
                }
                s.push(infix[i]);
            }
        }
        // 如果遇到右括号
        else if (')' == infix[i]) {
            // 一直出栈到左括号才停止
            while ('(' != s.top()) {
                p += s.top();
                s.pop();
            }
            s.pop(); // 顺便把左括号也出栈, 但不拼接到字符串上
        }
        // 遇到 ( 直接入栈
        else if ('(' == infix[i]) {
            s.push(infix[i]);
        }
        // 遇到 * /
        else if ('*' == infix[i] || '/' == infix[i]) {
            if (s.empty()) {
                // 如果栈空, 直接入栈
                s.push(infix[i]);
            } else {
                // 如果栈顶是 + - 直接入栈
                if (s.top() == '+' || s.top() == '-') {
                    s.push(infix[i]);
                } else {
                    // 一直出栈到左括号或者 + - 或者栈空才停止
                    while (!s.empty() && '(' != s.top()  && '+' != s.top() && '-' != s.top()) {
                        p += s.top();
                        s.pop();
                    }
                    s.push(infix[i]); // 最后还要把 * / 入栈
                }
            }
        }
    }
    while (!s.empty()) {
        // 把剩余的运算符出栈
        p += s.top();
        s.pop();
    }

    return p;
}

int main() {
    cout << "中缀表达式(不能有空格):";
    string infix;
    cin >> infix;
    string p = changeSuffix(infix);
    cout << "后缀表达式:" << p;
    return 0;
}

括号匹配问题

没什么好说的, 挺简单的, 直接上代码

#include<iostream>
#include<string>
#include<stack>
using namespace std;

void printMatchedPairs(string expr) {
    stack<int> s;

    for (int i = 0; i < expr.length(); i++) {
        if (expr[i] == '(') s.push(i); // 左括号入栈
        else if (expr[i] == ')') {
            try {
                cout << "左括号位: " << s.top()
                     << ", 其相匹配的右括号位:" << i << endl;
                s.pop(); // 左括号出栈
            }
            catch(exception e) {
                // 当栈是空的就会报错, 表示括号匹配错误
                cout << "位于" << i << "位的右括号无匹配的左括号" << endl;
            }
        }
    }

    // 如果最终栈不为空, 则表示左括号不匹配
    while (!s.empty()) {
        cout << "位于" << s.top() << "位的左括号无匹配的右括号" << endl;
        s.pop();
    }
}

int main() {
    string expr = "(a+b)*(h-";
    printMatchedPairs(expr);
    return 0;
}

汉诺塔问题

方案一: 递归

#include<iostream>
using namespace std;

// n: 多少个盘
// x, y, z: 塔号
void towersOfHanoi(int n, int x, int y, int z) {
    if (n > 0) {
        // 把塔 x 上面的 n - 1 个盘移动到塔 z
        towersOfHanoi(n - 1, x, z, y);
        // 把塔 x 底下最后一个大盘(此时也是顶部的盘)移动到塔 y
        cout << "将塔" << x << "顶部的盘移动到塔" << y << endl;
        // 把塔 z 的 n - 1 个盘移动到塔 y
        towersOfHanoi(n - 1, z, y, x);
        // 从而实现从塔 x 移动 n 个盘到塔 y
    }
}

int main() {
    towersOfHanoi(10, 1, 2, 3);
    return 0;
}

时间复杂度:

O(2n)O(2^n)

方案二: 栈

#include<iostream>
#include<string>
#include<stack>
using namespace std;

stack<int> tower[4]; // 四个栈, [1:3] 代表三个塔(0不要了)

void move(int n, int x, int y, int z) {
    if (n > 0) {
        // 将塔 x 的 n - 1 个盘移动到塔 z
        move(n - 1, x, z, y);
        int d = tower[x].top();
        tower[x].pop(); // 取出塔 x 最上面的盘
        tower[y].push(d); // 放到塔 y 上
        cout << "将盘" << d << "从塔" << x
            << "移动到塔" << y << endl;
        // 将塔 z 的 n - 1 个盘移回塔 y
        move(n - 1, z, y, x);
    }
}

void towersOfHanoi(int n, int x, int y, int z) {
    for (int d = n; d > 0; d--) {
        // 每个盘用数字来编号, 加入到塔 1 中
        tower[1].push(d);
    }

    // 把塔 1 的 n 个盘移动到塔 2
    move(n, 1, 2, 3);
}

int main() {
    towersOfHanoi(3, 1, 2, 3);
    return 0;
}

互斥字符串

aa, ab 为互斥, aA 和 Aa 为非互斥, 输入一个字符串序列, 如 abBAcCc, 要求转换成互斥字符串, 如果字符串为空, 输出 -1

案例: "abBAcCc" --> "aAcCc" --> "cCc" --> "c"

其中 bB 为非互斥, 可以删除, 删除后 aA 为非互斥, 也可以删除, 最后 cC 为非互斥, 可以删除

自己瞎写的, 时间复杂度 O(n)O(n), 不知道更好的方法

#include<iostream>
#include<string>
#include<stack>
using namespace std;

int main() {
    string in;
    cin >> in;
    stack<char> s;

    for (int i = 0; i < in.length(); i++) {
        char b = in[i];

        if (!s.empty()) {
            char a = s.top();
            // 判断栈顶元素和 in[i] 是否相等
            if (b != a) {
                // 不相等时, 判断是否是大小写关系
                if (b + 32 == a || b - 32 == a) {
                    // 是大小写关系, 则栈顶元素出栈
                    s.pop();
                }
                else {
                    // 不是大小写关系
                    s.push(b);
                }
            }
            else {
                // 相等, 说明是互斥的
                s.push(b);
            }
        }
        else {
            // 如果栈空, 则直接入栈
            s.push(b);
        }
    }

    if (s.empty()) {
        cout << -1;
    }
    else {
        string res;
        while (!s.empty()) {
            res = s.top() + res;
            s.pop();
        }
        cout << res;
    }

    return 0;
}

表达式求值

步骤

  1. 判断表达式是否正确
  2. 表达式转成后缀形式
  3. 使用栈计算结果
完整代码
#include<iostream>
#include<string>
#include<stack>
#include<vector>
#include<cmath>
#include <iomanip>
using namespace std;

// 括号正确性
bool matchedPairs(string expr) {
    stack<int> s;

    for (int i = 0; i < expr.length(); i++) {
        if (expr[i] == '(') s.push(i); // 左括号入栈
        else if (expr[i] == ')') {
            if (!s.empty()) {
                s.pop(); // 左括号出栈
            }
            else {
                // 右括号不匹配
                return false;
            }
        }
    }

    // 循环结束后栈非空, 则表示左括号不匹配
    if (!s.empty()) return false;

    return true;
}

// 判断合法性
bool illegal(string expr) {
    int len = expr.length();

    // 检测括号正确性
    bool pairs = matchedPairs(expr);
    if (!pairs) return false;

    // 检测一般正确性
    for (int i = 0; i < len; i++) {
        // ^ / * % 的左右均需要数字
        if (expr[i] == '^' || expr[i] == '/' || expr[i] == '*' || expr[i] == '%') {
            // 如果这些运算符位于边界, 则表达式不合法
            if (i == 0 || i == len - 1) return false;
            // 如果右边是左括号或者左边是右括号, 是合法的
            if (expr[i + 1] == '(' || expr[i - 1] == ')') continue;
            // 如果不是边界, 也没有左括号, 则其左右两边若不为数字, 则表达式不合法
            if (expr[i + 1] < '0' || expr[i + 1] > '9' || expr[i - 1] < '0' || expr[i - 1]> '9') return false;
        }
        else if (expr[i] == '+' || expr[i] == '-') {
            // + - 位于结尾, 不合法
            if (i == len - 1) return false;
            // + - 右边是左括号, 合法
            if (expr[i + 1] == '(') continue;
            // + - 右边不是数字, 不合法
            if (expr[i + 1] < '0' || expr[i + 1] > '9') return false;
        }
        else if (expr[i] == '(') {
            // ( 不能位于结尾
            if (i == len - 1) return false;
            // ( 右边是 + - 号, 合法
            if (expr[i + 1] == '+' || expr[i + 1] == '-') continue;
            // ( 右边不是 + - 号, 又不是数字, 则不合法
            if (expr[i + 1] < '0' || expr[i + 1] > '9') return false;
            if (i > 0) {
                // 左边不能存在数字, 即 9(9+2) 这种形式
                if (expr[i - 1] >= '0' && expr[i - 1] <= '9') return false;
            }
        }
        else if (expr[i] == ')') {
            // ) 不能位于开头
            if (i == 0) return false;
            // ) 左边一定要有数字
            if (expr[i - 1] < '0' || expr[i - 1] > '9') return false;
            if (i < len - 2) {
                // 右边不能存在数字, 即 (9+2)9 这种形式
                if (expr[i + 1] >= '0' && expr[i + 1] <= '9') return false;
            }
        }
        else if (expr[i] == '.') {
            // 如果遇到小数点, 则左右两边均应该是数字
            if (expr[i + 1] < '0' || expr[i + 1] > '9' || expr[i - 1] < '0' || expr[i - 1]> '9') return false;
        }
        else if (expr[i] < '0' || expr[i] > '9') { // 其他不是数字的就是非法字符
            return false;
        }
    }

    return true;
}

// 中缀转后缀
void changeSuffix(vector<string>* p, string infix) {
    stack<char> s; // 保存运算符的栈

    // 遍历字符串
    for (int i = 0; i < infix.length(); i++) {
            // 如果遇到的是数字, 则向后遍历获取整个数字
            // 直到遇到非数字或者非小数点时退出
            if (infix[i] >= '0' && infix[i] <= '9') {
                string temp = "";

                // 正负号, 正号不需要判断
                if (i > 1) {
                    // 数字在第二位以后时, 前面若有 + - 号, 则前一位的前一位必须没有数字才表示正负性
                    if (infix[i - 2] < '0' && infix[i - 2] > '9') {
                        if (infix[i - 1] == '-') {
                            temp += "-";
                        }
                    }
                }
                else if (i > 0) {
                    // 数字在第二位时前面若有 + - 号, 则直接表示正负性
                    if (infix[i - 1] == '-') {
                        temp += "-";
                    }
                }

                while (infix[i] >= '0' && infix[i] <= '9' || infix[i] == '.') {
                    temp += infix[i];
                    i++;
                }
                // 这里因为 while 的 i 到运算符才结束, 但是运算符要进入下一次循环
                // for 循环中也会执行 i++, 这里不减的话会跳过运算符
                i--;
                p->push_back(temp);
            }
            else if ('+' == infix[i] || '-' == infix[i]) {
                // 只有不代表正负性时才需要入栈
                if (i > 0 && (infix[i - 1] >= '0' || infix[i - 1] <= '9')) {

                    // 如果遇到 + 号或者 - 号
                    if (s.empty()) {
                        // 如果栈为空, 则直接入栈
                        s.push(infix[i]);
                    }
                    else {
                        while (!s.empty()) {
                            // 如果栈顶是左括号, 则直接入栈
                            // 即底下的 s.push(infix[i]);
                            if (s.top() == '(') {
                                break;
                            }

                            // 如果不是左括号, 则出栈
                            string temp = "";
                            temp.append(1, s.top());
                            p->push_back(temp);
                            s.pop();
                        }
                        s.push(infix[i]);
                    }
                }
            }
            // 如果遇到右括号
            else if (')' == infix[i]) {
                // 一直出栈到左括号 ( 才停止
                // 因为验证过括号的正确性, 有 ) 一定有 (
                // 因此在 pop 的过程中栈不会为空, while 不需要再判断
                while ('(' != s.top()) {
                    string temp = "";
                    temp.append(1, s.top());
                    p->push_back(temp);
                    s.pop();
                }
                s.pop(); // 顺便把左括号也出栈
            }
            // 遇到 * / %
            else if ('*' == infix[i] || '/' == infix[i] || '%' == infix[i]) {
                // 如果栈顶是空或者 + - 直接入栈
                if (s.empty() || s.top() == '+' || s.top() == '-') {
                    s.push(infix[i]);
                }
                else {
                    // 否则一直出栈到左括号或者 + - 或者栈空才停止
                    while (!s.empty() && '(' != s.top() && '+' != s.top() && '-' != s.top()) {
                        string temp = "";
                        temp.append(1, s.top());
                        p->push_back(temp);
                        s.pop();
                }
                s.push(infix[i]); // 最后还要把 * / % 入栈
            }
        }
        // 遇到 ( 或 ^ 直接入栈,因为优先级最高
        else if ('(' == infix[i] || '^' == infix[i]) {
            s.push(infix[i]);
        }
    }

    while (!s.empty()) {
        // 把剩余的运算符出栈
        string temp = "";
        temp.append(1, s.top());
        p->push_back(temp);
        s.pop();
    }
}

// 计算表达式的值
double calcExpr(string expr) {
    // 保存后缀表达式的向量
    vector<string> p;

    changeSuffix(&p, expr);

    stack<double> res;
    for (int i = 0; i < p.size(); i++) {
        string t = p[i];
        // 如果是运算符
        if (t == "+" || t == "-" || t == "/" || t == "*" || t == "^" || t == "%") {
            // 取出栈中两个元素
            double n1 = res.top();
            res.pop();
            double n2 = res.top();
            res.pop();

            if (t == "+") {
                res.push(n2 + n1);
            }
            else if (t == "-") {
                res.push(n2 - n1);
            }
            else if (t == "/") {
                res.push(n2 / n1);
            }
            else if (t == "*") {
                res.push(n2 * n1);
            }
            else if (t == "^") {
                res.push(pow(n2, n1));
            }
            else if (t == "%") {
                res.push((int)n2 % (int)n1);
            }
        }
        else {
            // 如果是数字字符串, 则入栈
            res.push(stod(t));
        }
    }

    return res.top();
}

int main() {
    string expr;
    cin >> expr;

    if (!illegal(expr)) {
        // 表达式不正确
        cout << "ERROR IN INFIX NOTATION";
    }
    else {
        double result = calcExpr(expr);

        cout << fixed << setprecision(2); // 两位小数输出
        cout << result;
    }

    return 0;
}

n 皇后

image.png

国际象棋的皇后可以走斜线和直线进行攻击, n 皇后问题在于在棋盘上摆放 n 个皇后, 使她们互相不能吃掉对方, 有多少种方法

DFS(深度优先)

具体参考 回溯算法之N皇后问题

具体逻辑:

  1. 创建一个数组 used 保存列的使用情况(used[i] 代表第 i 列被使用)
  2. 创建一个数组 putInf 保存皇后放置的位置(putInf[i] 代表第 i 行皇后被放在 putInf[i] 列上)
  3. 判断第 curRow 行第 i 列能否放置皇后, 如果能, 则 used[i] 标记使用, 再 queensAssign(curRow + 1) 递归下一行, 直到 curRow>=N 后保存 putInf 的情况
  4. 递归结束后 used[i] 又被标记为没使用过, 继续递归第 curRow 行第 i+1
  5. 判断是否可以放置皇后用到了斜率公式, 在同一条斜线上, 其斜率等于 1-1
#include <iostream>
#include <vector>
using namespace std;

#define N 4 // 皇后数

vector<int> putInf; // 每一行皇后的放置位置

vector<int> used(N, 0); // 记录 N 列的皇后位置

vector<vector<int>> ans; // 存储可行方案

// 判断在 curRow 行的 col 列放置皇后是否合法
bool judgeLegalPut(int curRow, int col) {
    for (int i = curRow - 1; i >= 0; i--) {
        // 计算斜率
        if (curRow - i == abs(col - putInf[i])) {
            // 当前位置与之前的皇后处于同一斜线上
            return false;
        }
    }
    return true;
}

void queensAssign(int curRow) {
    if (curRow >= N) {
        // 递归到叶子节点
        ans.push_back(putInf);
        return;
    }

    // i: 当前行皇后准备放的列数
    for (int i = 0; i < N; ++i) {
        if (used[i]) continue; // 位置被使用过, 直接跳过 

        // 第 curRow 行第 i 列合法
        if (judgeLegalPut(curRow, i)) {
            used[i] = true; // 第 i 列标记使用过
            putInf.push_back(i);

            queensAssign(curRow + 1);
            used[i] = false; // 撤销之前的状态
            putInf.pop_back();
        }
    }
}

// 输出棋盘
void printChessBoard(vector<int> vec) {
    cout << endl;
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            // vec[i]: 第 i 行皇后放置的位置
            if (j != vec[i])
                cout << "○";
            else
                cout << "●";
        }
        cout << endl;
    }
    cout << endl;
}

int main() {
    queensAssign(0);
    cout << N << " 皇后问题, 方案如下:" << endl << endl;

    int n = 1;
    for (int i = 0; i < ans.size(); i++) {
        cout << "第" << n++ << "种方案" << endl;
        printChessBoard(ans[i]);
    }
    return 0;
}

位运算法

具体参考: 用位运算速解 n 皇后问题, 有些目前没看懂

实质上将 32 位 int 整数当作长度为 32 的布尔数组, 1 表示能放皇后, 0 表示不可以放

一些位运算的意义: 
把第 i 位变成 1:a |= (1 << i)
把第 i 位变成 0:a &= ~(1 << i)
把第 i 位取反:a ^= (1 << i)
读取第 i 位的值:(a >> i) & 1
// 获取最右边的 1 的位置:
a & -a // (00110100 & 11001100 = 00000100)

// 遍历 a 中所有的 1
while a != 0:
    p = a & -a
    a ^= p // 把最右的 1 取消掉
    // Do something with p
完整代码
#include<iostream>
#include <ctime>
using namespace std;

int n = 4; // 皇后数
int sum = 0; // 方案数

// left \ right / col | row —
void queen(int row, int col, int left, int right) {
    /*
    * 获取能放皇后的位置
    * 一开始 left/right/col 都是 0, ~(0|0|0)=1
    * (1<<n)-1 有 n 个 1 (111...111)
    * pos 就代表一开始全部位置都可以放皇后
    * 然后 while 循环不断获取最右边能放皇后的位置
    * 试过一次后就标记这个位置, 继续试下一个位置
    */
    int pos = ((1 << n) - 1) & ~(col | left | right);
    while (pos) {
        // 获取最右边为 1 的位置
        int p = pos & -pos;
        pos ^= p; // 将 p 位置取反(标记已经试过)

        if (row == n - 1) {
            // row 从 0 开始, 因此 n-1 为结束
            // 能进入 while 说明 pos 还有位置
            sum++;
        }
        else {
            /*
            * row + 1 代表下一行(一开始 row = 0)
            * 这里把 col 的 p 位标记为 1
            * 这样在 ~(col|left|right) 的 p 位就会变成 0, 标记成不能用
            */
            queen(row + 1, col | p, (left | p) << 1, (right | p) >> 1);
        }
    }
}

int main() {
    time_t tm;

    tm = time(0);

    queen(0, 0, 0, 0);

    cout << "皇后数: " << n << endl
        << "共有" << sum << "方案" << endl
        << "用时" << (time(0) - tm) << "s" << endl;
}

栈实现

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;

struct Queen {
    int x, y; // 位置坐标
    Queen(int x = 0, int y = 0) : x(x), y(y) {};
    bool operator ==(Queen const& q) const {
        return (x == q.x) // 行冲突
            || (y == q.y) // 列冲突
            || (x + y == q.x + q.y) // 沿正对角线冲突
            || (x - y == q.x - q.y); // 沿反对角线冲突
    };
    bool operator !=(Queen const& q) const {
        return !(*this == q);
    };
};

ostream& operator<<(ostream& out, Queen& q) {
    out << "(" << q.x << "," << q.y << ")";
    return out;
};

// 回溯法
void placeQueens(int N) {
    vector<Queen> solu; // 存放(部分)解的栈
    Queen q(0, 0);

    int nSolu = 0; // 解数

    do { // 反复试探回溯
        if (solu.size() >= N || q.y >= N) {  // 若出界
            q = solu.back(); // 回溯一行
            solu.pop_back();
            q.y++; // 试探下一列
        }
        else {
            // 使用 find, 若找到元素 q, 则 solu.end() != iterator 成立
            // 否则使用 find 迭代到 end 还没找到, solu.end() != solu.end() 就不成立
            while (q.y < N && solu.end() != find(solu.begin(), solu.end(), q)) {
                // 存在冲突皇后, 则尝试下一列
                q.y++;
            }
            // 不存在冲突皇后, 并且该皇后在区域内
            if (N > q.y) {
                solu.push_back(q); // 摆上当前皇后
                // 栈中元素 >= N 说明找到解
                if (solu.size() >= N) {
                    nSolu++;
                    cout << "解" << nSolu << ": ";
                    for (int i = 0; i < solu.size(); i++) {
                        cout << solu[i];
                    }
                    cout << endl;
                };
                q.x++;
                q.y = 0;
            }
        }
    } while ((0 < q.x) || (q.y < N));
}

int main() {
    placeQueens(6);
    return 0;
}