浅显易懂的几个回溯算法实例

一、枚举排列
目标:输入一个整数n,按字典序从大到小的顺序输出前n个数的所有排列
分析:尝试用递归的思想解决,先输出所有以1开头的序列,然后输出以2开头的序列……一直递归到序列长度为n时输出,然后再进行返回。
方法
     1)、递归回溯
     2)、使用STL中的内置函数next_permutation(),参数为数组的起始指针。
代码

int a[10] = { 0 };
int ans[10] = { 0 };
int num = 5;
int t = 0;

void print() {
	for (int i = 1; i <= num; i++) {
		cout << ans[i] << " ";
	}
	t++;
	cout << endl;
}                                         //输出函数
void dfs(int count) {
	if (count >= num+1) {
		print();
		return;
	}
	for (int i = 1; i <= num; i++) {
		if (!a[i]) {                     //i在之前未使用过,故可填入
			ans[count] = i;
			a[i] = 1;
			dfs(count + 1);
			a[i] = 0;                    //恢复现场,全局变量的修改需谨慎
		}
	}
}
int A[10] = { 0 };
void print_permutation(int n, int * A,int cur) {
	if (cur == n) {
		for (int i = 0; i < cur; i++)
			cout << A[i] << (i != cur - 1 ? ' ' : '\n');
		return;
	}
	else {
		for (int i = 1; i <= n; i++) {
			int ok = 1;
			for (int j = 0; j < cur; j++) {
				if (A[j] == i)
					ok = 0;
			}  
			if (ok) {
				A[cur] = i;
				print_permutation(n, A, cur + 1);
			}
		}
	}
}
	int n = 5;
	int p[10] = { 1,2,3,4,5 };
	sort(p, p + n);                 //排序的目的是为了定序,
	do {
		for (int i = 0; i < n; i++) {
			cout << p[i] << " ";
		}
		cout << endl;
	} while (next_permutation(p, p + n));
#include<iostream>
using namespace std;

int used[101] = { 0 };

void dfs(int n,int count,int*ans, int *used ) {
	if (count == n) {
		for (int i = 0; i < n; i++) {
			cout << ans[i];
		}
		cout << endl;
		return;
	}
	
	for (int i = 1; i < n+1; i++) {
		if (used[i] == 0) {
			ans[count] = i;
			used[i] = 1;
			dfs(n, count + 1, ans, used);
			used[i] = 0;
		}
	}

	return;
}


int main() {
	int num;
	int ans[101] = { 0 };
	cin >> num;
	dfs(num, 0, ans, used);
	system("pause");
}

 

二、子集生成
目标:给定一个集合,枚举它所有可能的子集
方法
1、增量构造法:
每一次选一个下标放进下标数组内。并且规定了集合中所有元素都是从小到大排列,使用了定序的技巧

void dfs(int n,int *p ,int *A,int cur) {
	for(int i =0 ; i< cur;i++) {
		cout << p[A[i]]<< (i == cur-1? '\n' :' ') ;  //A[]是下标序列
	}
	int s = cur? A[cur-1]+1 : 0;  //s是未放进序号序列元素中的最小值,即下一个准备放入序列的下标
	for(int i = s;i<n;i++) {
		A[cur] = i;
		dfs(n,p,A,cur+1);
	}
}

结果:

 

2、位向量法:
构造一个位向量B[],而不是直接构造子集本身,其中B[i] = 1,当且仅当i在子集A中。


/*B[]的值:
11111
11110
11101
11100 ……
*/
void print_subset(int n,int *B,int cur) {
	if(cur == n) {            /必须等B中元素全部赋值完毕,即所有元素都被参与选择
		for(int i =0 ;i < n ;i++) {
			if(B[i]) cout << i << " ";
		}
		cout << endl;
		t++;
		return ;
	}
	
	B[cur] = 1;
	print_subset(n,B,cur+1);
	B[cur] = 0;
	print_subset(n,B,cur+1);

}

3、二进制法:
可以用二进制串来表示子集。0代表该元素在子集内,1代表该元素不在子集内。

void print_subset(int n,int s){ //根据子集编号转换成二进制串,而后得到子集内元素
	for(int i =0 ;i < n ;i++) {
		if(s & (1<<i)) printf("%d ",i);
	}
	printf("\n");
	t++; 
}



for(int i =0 ;i < (1<<5) ;i++) 	{  //0到2^5共32个子集,以此循环
		print_subset(5,i);
}


三、回溯法
直接枚举法的优点是思路和程序都很简单,缺点在于无法简便地减少枚举量--必须先进行生成所有可能的解,然后一一检查。
而在递归构造中,可以将生成和检查有机地结合起来,从而减少不必要的枚举。
回溯法的应用范围很广,只要能把代求解的问题分成不太多的步骤,每个步骤又只有不太多的选择,都可以考虑回溯法。
回溯:当把问题分成若干步骤并递归求解时,如果当前步骤没有合法选择,则函数将返回上一层递归调用,这重现象称为回溯,正因为如此,递归枚举又被称为回溯法
 

#include<iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;
const int MAX = 10;
int row[MAX];   //行数作为下标,列数作为数组元素 key point
int a[MAX][MAX];
int num = 8;
int t = 1;

void print() {
	cout << "No. " << t++ << endl;
	for (int i = 1; i <=num; i++) {
		for (int j = 1; j <= num; j++) {
			cout << a[i][j] << " ";
		}
		cout << endl;
	}
	return;
}

bool judge(int count) {
	for (int i = 1; i < count; i++) {
		if (row[i] == row[count] || abs(row[i] - row[count]) == count - i ) 
			return 0;    //判断是否同列或者是否是对角线元素 
	}
	return 1;
}

void dfs(int count) {
	if (count >= num + 1) {
		print();
		return;
	}

	for (int i = 1; i <= num; i++) {    //逐列进行配对,符合便进入递归,不符合就回溯处理
		row[count] = i;
		if (a[count][i] != 1 && judge(count)) { 
			a[count][i] = 1;
			dfs(count + 1);
			a[count][i] = 0;    //回溯核心, 
		}
	}
}



int main() {
	dfs(1);
	system("pause");
}

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值