数据1~n的选择、全排序 — 回溯法蛮力法的应用
适用于:
for循环的层数不确定
解空间树的分枝数不确定
1 选择
可以选择为空
有一个含n个整数的数组a,所有元素均不相同,设计一个算法求其所有子集(幂集)。
例如,a[]={1,2,3},所有子集是:{},{3},{2},{2,3},{1},{1,3},{1,2},{1,2,3}(输出顺序无关)
显然本问题的解空间为子集树,每个元素只有两种扩展,要么选择,要么不选择。
采用深度优先搜索思路。解向量为x[],x[i]=0表示不选择a[i],x[i]=1表示选择a[i]。
用i扫描数组a,也就是说问题的初始状态是(i=0,x的元素均为0),目标状态是(i=n,x为一个解)。从状态(i,x)可以扩展出两个状态:
#include <stdio.h>
#include <string.h>
#define MAXN 100
void dispasolution(int a[],int n,int x[]) //输出一个解
{
printf(" {");
for (int i=0;i<n;i++)
if (x[i]==1)
printf("%d",a[i]);
printf("}");
}
void dfs(int a[],int n,int i,int x[]) //回溯算法
{
if (i>=n)
dispasolution(a,n,x);
else
{
x[i]=0;
dfs(a,n,i+1,x); //不选择a[i]
x[i]=1;
dfs(a,n,i+1,x); //选择a[i]
}
}
void main()
{
int a[]={1,2,3}; //s[0..n-1]为给定的字符串,设置为全局变量
int n=sizeof(a)/sizeof(a[0]);
int x[MAXN]; //解向量
memset(x,0,sizeof(x)); //解向量初始化
printf("求解结果\n");
dfs(a,n,0,x);
printf("\n");
}
用容器vector,不选择某个元素直接弹出:
#include <stdio.h>
#include <vector>
using namespace std;
void dispasolution(vector<int> path) //输出一个解
{
printf(" {");
for (int i=0;i<path.size();i++)
printf("%d",path[i]);
printf("}");
}
void dfs(int a[],int n,int i,vector<int> path) //回溯算法求子集path
{
if (i>=n)
dispasolution(path);
else
{
dfs(a,n,i+1,path); //不选择a[i]
path.push_back(a[i]);
dfs(a,n,i+1,path); //选择a[i]
}
}
void main()
{
int a[]={1,2,3}; //s[0..n-1]为给定的字符串,设置为全局变量
int n=sizeof(a)/sizeof(a[0]);
vector<int> path;
printf("求解结果\n");
dfs(a,n,0,path);
printf("\n");
}
运行结果:
可以重复选择
输入一个数n,代表每次可以从1~n中选择一个数,可以选n次,输出所有的选择结果
#include <iostream>
#include <vector>
void printChoices(const std::vector<int>& choices) {
for (int choice : choices) {
std::cout << choice << ' ';
}
std::cout << '\n';
}
void generateChoices(int n, std::vector<int>& choices, int remaining) {
if (remaining == 0) {
// 已经选择了 n 次,输出结果
printChoices(choices);
return;
}
for (int i = 1; i <= n; ++i) {
// 尝试选择数字 i
choices.push_back(i);
generateChoices(n, choices, remaining - 1);
choices.pop_back(); // 回溯
}
}
int main() {
int n;
std::cout << "输入一个整数 n: ";
std::cin >> n;
std::vector<int> choices;
generateChoices(n, choices, n);
return 0;
}
输出结果:
2 全排序
有一个含n个整数的数组a,所有元素均不相同,求其所有元素的全排列。
例如,a[]={1,2,3},得到结果是(1,2,3)、(1,3,2)、(2,3,1)、(2,1,3)、(3,1,2)、(3,2,1)。
回溯法
关键在于,控制分枝的条件,上面的条件均为选与不选
,尝试选择数字i
,这里的条件为在确定了第i个数之后,尝试将i与i后面的数一一交换
代码:
#include<iostream>
using namespace std;
void swap(int& x, int& y) //交换x、y
{
int tmp = x;
x = y; y = tmp;
}
void dispasolution(int a[], int n) //输出一个解
{
for (int i = 0; i < n - 1; i++)
cout << a[i] << " ";
cout << a[n - 1] << " ";
cout << endl;
}
void dfs(int a[], int n, int i) //求a[0..n-1]的全排列
{
if (i >= n) //递归出口
dispasolution(a, n);
else
{
for (int j = i; j < n; j++)
{
swap(a[i], a[j]); //交换a[i]与a[j]
dfs(a, n, i + 1);
swap(a[i], a[j]); //交换a[i]与a[j]:恢复
}
}
}
void main()
{
int a[] = { 1,2,3,4 };
int n = sizeof(a) / sizeof(a[0]);
printf("a的全排列\n");
dfs(a, n, 0);
printf("\n");
}
输出结果:
蛮力法
将问题转化为插空位。且利用vector的容器迭代器iterator,方面插入操作
代码:
//递归求解全排列问题
#include <stdio.h>
#include <vector>
using namespace std;
vector<vector<int> > ps; //存放全排列
void Insert(vector<int> s, int i, vector<vector<int> >& ps1)
//在每个集合元素中间插入i得到ps1
{
vector<int> s1;
vector<int>::iterator it;
for (int j = 0; j < i; j++) //在s(含i-1个整数)的每个位置插入i
{
s1 = s;
it = s1.begin() + j; //求出插入位置
s1.insert(it, i); //插入整数i
ps1.push_back(s1); //添加到ps1中
}
}
void Perm(int i, int n) //求1~n的全排列ps
{
vector<vector<int> >::iterator it; //全排列迭代器
if (i <= n)
{
vector<vector<int> > ps1; //临时存放子排列
for (it = ps.begin(); it != ps.end(); ++it)
Insert(*it, i, ps1); //在每个集合元素中间插入i得到ps1
ps = ps1;
Perm(i + 1, n); //继续添加整数i+1
}
}
void dispps() //输出全排列ps
{
vector<vector<int> >::reverse_iterator it; //全排列的反向迭代器
vector<int>::iterator sit; //排列集合元素迭代器
for (it = ps.rbegin(); it != ps.rend(); ++it)
{
for (sit = (*it).begin(); sit != (*it).end(); ++sit)
printf("%d", *sit);
printf(" ");
}
printf("\n");
}
void main()
{
int n = 3;
printf("1~%d的全排序如下:\n ", n);
vector<int> s;
s.push_back(1);
ps.push_back(s); //初始化ps为{{1}}
Perm(2, n);
dispps();
}
输出结果: