使用线性表实现
时间复杂度
pop()
push()
栈长度不变时:
栈长度增加时:
top()
完整代码
#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 指针指向上一个元素(即从栈顶一直指向栈底)
时间复杂度
都是
完整代码
#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;
}
车厢重排
步骤
- 初始时符合条件的出轨车厢是 1 号
- 3 号车厢不符合条件, 入栈 H1, 同理 6 号 和 9 号也不符合
- 继续入栈, 要求入栈的车厢不能比栈顶元素大
- 2 号车厢找一个栈顶车厢号最接近它的入栈
- 以此类推
- 1 号车厢满足条件, 输出, 并将符合条件的车厢号变成 2 号
- 遍历所有缓冲轨道, 查找有没有 2 号, 如果有, 则 2 出轨, 符合条件的车厢又变成 3 号
- 以此类推, 直到缓冲轨道中没有符合条件的车厢, 此时符合条件的车厢是 5 号
- 重新入栈, 最后 5 号符合条件, 直接输出
- 同理, 缓冲轨道出栈
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 / −
- 输出 a 后遇到 + 号, 栈顶无元素可以比较, 直接入栈
- 输出 b 后遇到 × 号, 判断优先级高于栈顶的 + 号, 因此入栈
- 输出 c 后遇到 − 号, 判断优先级低于栈顶的 × 号, 因此栈顶出栈
- 判断优先级与栈顶的 + 号相同, 也出栈, 最后 − 号没有元素可以比较了, 直接入栈
- 输出 d 后遇到 / 号, 优先级高于 − 号, 入栈
- 输出 e 后没有运算符了, 把栈元素依次输出
有括号的情况
中缀表达式: a × ( b + c ) / d
后缀表达式: a b c + × d /
- 输出 a 后遇到 × 号, 入栈
- 遇到左括号 (, 入栈
- 输出 b 后遇到 + 号, 入栈
- 输出 c 后遇到右括号 ), 把上一个左括号之后的全部出栈
- 遇到 / 号优先级等于栈顶的 × 号, × 号出栈, / 入栈
- 输出 d 后全部出栈
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;
}
时间复杂度:
方案二: 栈
#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 为非互斥, 可以删除
自己瞎写的, 时间复杂度 , 不知道更好的方法
#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;
}
表达式求值
步骤
- 判断表达式是否正确
- 表达式转成后缀形式
- 使用栈计算结果
完整代码
#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 皇后
国际象棋的皇后可以走斜线和直线进行攻击, n 皇后问题在于在棋盘上摆放 n 个皇后, 使她们互相不能吃掉对方, 有多少种方法
DFS(深度优先)
具体参考 回溯算法之N皇后问题
具体逻辑:
- 创建一个数组
used保存列的使用情况(used[i]代表第i列被使用) - 创建一个数组
putInf保存皇后放置的位置(putInf[i]代表第i行皇后被放在putInf[i]列上) - 判断第
curRow行第i列能否放置皇后, 如果能, 则used[i]标记使用, 再queensAssign(curRow + 1)递归下一行, 直到curRow>=N后保存putInf的情况 - 递归结束后
used[i]又被标记为没使用过, 继续递归第curRow行第i+1列 - 判断是否可以放置皇后用到了斜率公式, 在同一条斜线上, 其斜率等于
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;
}