摘要 :本文详细讲解回溯法两大经典入门问题 ------0/1 全排列生成 与N 皇后问题 ,分别用递归和 ** 非递归(迭代)** 两种方式实现 C++ 代码,对比两种写法的核心逻辑,帮助彻底掌握回溯法思想。
一、前言:回溯法核心思想
回溯法本质是试探 + 回退的暴力搜索优化:
- 按路径尝试所有可能解;
- 发现当前路径不合法 / 无解时,回退到上一步重新选择;
- 直到找到所有合法解。
本文用两个经典案例落地:
- 基础案例:生成 n 位 0/1 全排列
- 经典案例:N 皇后问题(算法必考题)
二、案例 1:0/1 全排列生成
1. 问题描述
生成长度为 n 的所有 0/1 组合,例如 n=3 时,输出:
000、001、010、011、100、101、110、111
2. 递归实现(直观易写)
递归思路:每一位选 0 或选 1,直到填满所有位。
cpp
// 递归生成0/1全排列
void PrintO1(int* X, int i, int n)
{
// 递归终止:填满所有位,打印结果
if (i > n)
{
for (int j = 1; j <= n; ++j)
{
printf("%2d", X[j]);
}
printf("\n------------\n");
}
else
{
X[i] = 1; // 第i位选1
PrintO1(X, i + 1, n);
X[i] = 0; // 回溯:第i位选0
PrintO1(X, i + 1, n);
}
}
3. 非递归实现(效率更高)
用循环 + 栈思想模拟递归回溯,避免递归栈溢出。
cpp
// 打印数组
void Print(const int* X, int n)
{
for (int i = 1; i <= n; ++i)
{
printf("%3d", X[i]);
}
printf("\n----------------------\n");
}
// 非递归生成0/1全排列
void Print01(int* X, int n)
{
int k = 1;
X[k] = 2; // 初始值设为2,通过减1进入0/1范围
while (k > 0)
{
X[k] -= 1;
if (X[k] >= 0) // 当前位为0/1,合法
{
if (k == n)
{
Print(X, n); // 填满所有位,输出
}
else
{
++k; // 处理下一位
X[k] = 2;
}
}
else // 当前位无合法值,回溯
{
--k;
}
}
}
4. 测试代码
cpp
int main()
{
int X[] = { 0,0,0,0 };
Print01(X, 3); // 生成3位0/1全排列
return 0;
}
三、案例 2:N 皇后问题(算法面试高频)
1. 问题描述
在n×n棋盘上放n个皇后,满足:
- 任意两个皇后不同行、不同列、不同对角线。
2. 核心判断函数
判断第t行皇后位置是否合法:
cpp
bool Place(int t)
{
// 检查与前t-1行是否冲突
for (int j = 1; j < t; ++j)
{
// 同列 或 同对角线
if (x[j] == x[t] || abs(t - j) == abs(x[t] - x[j]))
{
return false;
}
}
return true;
}
3. 递归回溯实现
cpp
class Queen
{
private:
int n = 0;
vector<int> x; // x[i]表示第i行皇后所在列
int sum = 0; // 解的总数
void PrintQueen(); // 打印棋盘
bool Place(int t); // 位置合法性判断
void Backtrack(int i); // 递归回溯
public:
int nQueen(int qn)
{
n = qn;
if (n <= 3) return 0; // n≤3无解
x.resize(n + 1, 0);
Backtrack(1);
return sum;
}
};
// 递归回溯核心
void Queen::Backtrack(int i)
{
if (i > n) // 找到一组解
{
sum++;
PrintQueen();
}
else
{
// 遍历第i行所有列
for (int j = 1; j <= n; ++j)
{
x[i] = j;
if (Place(i)) // 位置合法,继续下一行
{
Backtrack(i + 1);
}
}
}
}
4. 非递归回溯实现(推荐)
用循环模拟递归,性能更优、无栈溢出风险:
cpp
class Queen2
{
private:
int n = 0;
vector<int> x;
int sum = 0;
void PrintQueen();
bool Place(int t);
void Backtrack(); // 非递归回溯
public:
int nQueen(int qn)
{
n = qn;
if (n <= 3) return 0;
x.resize(n + 1, 0);
Backtrack();
return sum;
}
};
// 非递归核心
void Queen2::Backtrack()
{
x[1] = 0;
int k = 1; // 当前处理第k行
while (k > 0)
{
x[k]++; // 尝试下一列
// 跳过非法列
while (x[k] <= n && !Place(k)) x[k]++;
if (x[k] <= n) // 找到合法列
{
if (k == n) // 找到完整解
{
sum++;
PrintQueen();
}
else // 处理下一行
{
k++;
x[k] = 0;
}
}
else // 当前行无合法解,回溯
{
k--;
}
}
}
5. 测试 4 皇后问题
cpp
int main()
{
Queen2 qu;
int num = qu.nQueen(4);
cout << "4皇后解的个数:" << num << endl;
return 0;
}
输出结果 :4 皇后有2 个合法解。
四、递归 vs 非递归 对比
| 对比维度 | 递归实现 | 非递归实现 |
|---|---|---|
| 代码简洁度 | 极高,逻辑直观 | 稍复杂,需手动控制回溯 |
| 性能 | 有函数调用开销 | 无开销,效率更高 |
| 稳定性 | n 过大可能栈溢出 | 无栈溢出风险 |
| 适用场景 | 算法学习、小规模数据 | 工程落地、大规模数据 |
五、总结
- 回溯法核心:试探→验证→回溯→再试探;
- 0/1 全排列是回溯法入门模板,掌握选 / 不选逻辑;
- N 皇后是回溯法经典应用,重点理解位置合法性判断;
- 递归适合写算法题,非递归适合实际工程使用。
文末福利
完整可运行代码已整合,直接复制即可编译运行:
cpp
// 全文整合代码(含0/1全排列+N皇后双实现)
#include<stdio.h>
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
// 递归0/1全排列
void PrintO1(int* X, int i, int n)
{
if (i > n)
{
for (int j = 1; j <= n; ++j)
printf("%2d", X[j]);
printf("\n------------\n");
}
else
{
X[i] = 1; PrintO1(X, i+1, n);
X[i] = 0; PrintO1(X, i+1, n);
}
}
// 打印函数
void Print(const int* X, int n)
{
for (int i=1; i<=n; ++i)
printf("%3d", X[i]);
printf("\n----------------\n");
}
// 非递归0/1全排列
void Print01(int* X, int n)
{
int k=1; X[k]=2;
while(k>0)
{
X[k]--;
if(X[k]>=0)
{
if(k==n) Print(X,n);
else {k++; X[k]=2;}
}
else k--;
}
}
// N皇后非递归实现
class Queen2
{
private:
int n; vector<int> x; int sum;
bool Place(int t)
{
for(int j=1; j<t; j++)
if(x[j]==x[t] || abs(t-j)==abs(x[t]-x[j]))
return false;
return true;
}
void PrintQueen()
{
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
printf(x[i]==j?" Q":" #");
printf("\n");
}
printf("\n----------------\n");
}
void Backtrack()
{
x[1]=0; int k=1;
while(k>0)
{
x[k]++;
while(x[k]<=n && !Place(k)) x[k]++;
if(x[k]<=n)
{
if(k==n) {sum++; PrintQueen();}
else {k++; x[k]=0;}
}
else k--;
}
}
public:
int nQueen(int qn)
{
n=qn; sum=0;
if(n<=3) return 0;
x.resize(n+1,0);
Backtrack();
return sum;
}
};
int main()
{
cout<<"===== 3位0/1全排列 =====\n";
int X[]={0,0,0,0};
Print01(X,3);
cout<<"\n===== 4皇后问题 =====\n";
Queen2 qu;
cout<<"解的个数:"<<qu.nQueen(4)<<endl;
return 0;
}
💡 原创不易,欢迎点赞、收藏、关注,后续会更新更多算法实战干货!