一、全排列——基于数值交换(结果不按字典序)
1、全排列
这种全排列会打乱顺序,如果原始序列有序,则生成的结果不会完全按照字典序排列。如下输出中的第5行(3 2 1)和第6行(3 1 2)就是着这种情况。
/*
输入:
3
1 2 3
输出:
1 2 3
1 3 2
2 1 3
2 3 1
3 2 1
3 1 2
*/
//打印数组
void print_num(vector<int> &num)
{
int n = num.size();
for (int k = 0; k < n; k++)
{
if (k == 0)
cout << num[k];
else
cout << ' ' << num[k];
}
cout << endl;
}
//第i之前的数已经排好
void dfs(vector<int> &num, int i)
{
//局部终止条件
if (i == num.size())
{
print_num(num);
return;
}
//分别将i及其之后的数与i交换,相当于放在了第i层
for (int j = i; j < (int)num.size(); j++)
{
swap(num[i], num[j]);
dfs(num, i + 1); //放下一层
swap(num[i], num[j]);
}
}
int main()
{
int n;
cin >> n;
vector<int> num(n);
for (int i = 0; i < n; i++)
{
cin >> num[i];
}
dfs(num, 0); //从第0层开始排,或者说,从第0个数开始排
return 0;
}
2、去重全排列
这种全排列会打乱顺序,如果原始序列有序,则生成的结果不会完全按照字典序排列。如下输出中的第5行和第6行就是着这种情况。
每次交换的时候,当前是第 i 个额数字。
(1)将 j 位置与 i 位置交换,如果 j = i,则是自己与自己交换,相当于不交换。
(2)将 j 位置与 i 位置交换,如果 j != i,但是 num[j] = num[i],则交换之后 num 并没有发生实质变化,相当于不交换,即重复了一次不交换的行为,重复了(1),因此会产生重复的结果,需要跳过。
#include <iostream>
#include <vector>
using namespace std;
/*
输入:
3
1 1 2
输出:
1 1 2
1 2 1
2 1 1
*/
void print_num(vector<int> &num)
{
int n = num.size();
for (int k = 0; k < n; k++)
{
if (k == 0)
cout << num[k];
else
cout << ' ' << num[k];
}
cout << endl;
}
//第i之前的数已经排好
void dfs(vector<int> &num, int i)
{
//局部终止条件
if (i == num.size())
{
print_num(num);
return;
}
//分别将i及其之后的数与i交换,相当于放在了第i层
for (int j = i; j < (int)num.size(); j++)
{
if (j != i && num[j] == num[i]) //除了当前数字,如果后面有相同的数字,则不放在这一层,跳过
continue;
swap(num[i], num[j]);
dfs(num, i + 1); //放下一层
swap(num[i], num[j]);
}
}
int main()
{
int n;
cin >> n;
vector<int> num(n);
for (int i = 0; i < n; i++)
{
cin >> num[i];
}
dfs(num, 0); //从第0层开始排,或者说,从第0个数开始排
return 0;
}
3、从根开始的深度优先搜索
vector<int> num = {1,2,3};
dfs(num, 0); //代表是全排列
1 2 3
1 3 2
2 1 3
2 3 1
3 2 1
3 1 2
dfs(num, 1); //代表是从根(第0位置的1)开始的深度优先搜索
1 2 3
1 3 2
二、全排列——基于数值插入(结果按字典序)
第一种全排列会打乱顺序,如果原始序列有序,则生成的结果不会完全按照字典序排列。如一、1、输出中的第5行(3 2 1)和第6行(3 1 2)就是着这种情况。
原因在于原序列本来是(1 2 3),交换了 1 和 3 ,因此变成了(3 2 1),因此 3 在前面的情况下,输出的第一个结果就是后续都不交换的结果(3 2 1)。
如何避免这种情况呢,可以改交换为插入,将 3 插入到 1 位置,则原序列(1 2 3)变为(3 1 2),因此 3 在前面的情况下,第一个结果即为(3 1 2),满足字典序。
不过插入操作相比于交换操作需要移动很多元素,开销大。
三、全排列——基于访问状态(结果按字典序)
实现结果按字典序排列的另一种做法是给每个数值都赋予一个访问状态,0表示未访问,1表示访问过。纯粹的深度优先方法。写法很多。
1、写法一
(1)全排列
#include <iostream>
#include <vector>
using namespace std;
//打印数组
void print_num(vector<int> &num)
{
int n = num.size();
for (int k = 0; k < n; k++)
{
if (k == 0)
cout << num[k];
else
cout << ' ' << num[k];
}
cout << endl;
}
vector<int> temp; //临时栈,记录当前访问已访问的元素
//第i之前的数已经排好
void dfs(vector<int> &num, vector<bool> &state, int i)
{
//进栈
temp.push_back(num[i]); //记录当前访问已访问的元素
state[i] = 1; //正在访问
//局部终止判断
if (temp.size() == num.size())
{
print_num(temp);
temp.pop_back(); //出栈
state[i] = 0; //访问完毕
return;
}
//dfs
for (int j = 0; j < (int)num.size(); j++)
{
if (state[j] == 1) continue;
dfs(num, state, j);
}
//回溯/出栈
temp.pop_back(); //出栈
state[i] = 0; //访问完毕
return;
}
/*
输入:
3
1 1 2
输出:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
*/
int main()
{
int n;
cin >> n;
vector<int> num(n);
for (int i = 0; i < n; i++)
{
cin >> num[i];
}
vector<bool> state(n, 0); //访问状态
for (int i = 0; i < n; i++)
dfs(num, state, i); //从第0层开始排,或者说,从第0个数开始排
return 0;
}
(2)从根开始的深度优先搜索(在 main() 种修改)
//全排列
for (int i = 0; i < n; i++)
dfs(num, state, i); //从第0层开始排,或者说,从第0个数开始排
输入:
3
1 1 2
输出:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
//从0位置的根开始的深度优先搜索
dfs(num, state, 0); //从第0层开始排,或者说,从第0个数开始排
输入:
3
1 1 2
输出:
1 2 3
1 3 2
2、写法二
(1)全排列
#include <iostream>
#include <vector>
using namespace std;
//打印数组
void print_num(vector<int> &num)
{
int n = num.size();
for (int k = 0; k < n; k++)
{
if (k == 0)
cout << num[k];
else
cout << ' ' << num[k];
}
cout << endl;
}
vector<int> temp; //临时栈,记录当前已经访问过的元素
void dfs(vector<int> &num, vector<bool> &state)
{
//局部终止判断
if (temp.size() == num.size())
{
print_num(temp);
return;
}
//dfs
for (int j = 0; j < (int)num.size(); j++)
{
if (state[j] == 1) continue;
//进栈
temp.push_back(num[j]); //记录当前访问已访问的元素
state[j] = 1; //正在访问
dfs(num, state);
//回溯/出栈
temp.pop_back(); //出栈
state[j] = 0; //访问完毕
}
return;
}
/*
输入:
3
1 2 3
输出:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
*/
int main()
{
int n;
cin >> n;
vector<int> num(n);
for (int i = 0; i < n; i++)
{
cin >> num[i];
}
vector<bool> state(n, 0); //访问状态
dfs(num, state); //从第0层开始排,或者说,从第0个数开始排
return 0;
}
(2)从根开始的深度优先搜索(在 main() 种修改)
//全排列
dfs(num, state); //
输入:
3
1 2 3
输出:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
//从0位置的根开始的深度优先搜索
temp.push_back(num[0]); //先放入根,并作标记
state[0] = 1;
dfs(num, state); //
输入:
3
1 2 3
输出:
1 2 3
1 3 2