三道常见的回溯算法问题

数据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)可以扩展出两个状态:

image-20231209153432320

#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");
}

运行结果:

image-20231209153937245

可以重复选择

输入一个数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;
}

输出结果:

image-20231209154427347

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后面的数一一交换

image-20231209154846262

代码:

#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");
}

输出结果:

image-20231209155110677

蛮力法

将问题转化为插空位。且利用vector的容器迭代器iterator,方面插入操作

image-20231209155321012

代码:

//递归求解全排列问题
#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();
}

输出结果:

image-20231209155517316

  • 11
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值