搜索技术——排列和组合问题

搜索使用的算法是BFS和DFS,BFS用队列,DFS用递归具体实现。在BFS和DFS的基础上可以扩展出A*算法、双向广搜算法、迭代加深算法、IDA*等技术。

暴力法:把所有可能的情况都罗列出来,然后逐一检查,从中找出答案。

思路:

1.找到所有可能的数据,并用数据结构表示和存储

2.剪枝。尽量多地排除不符合条件的数据,以减少搜索的空间

3.用某个算法快速检索

目录

递归和排列

n个数的全排列,共n!个

打印n个数中任意m个数的全排列

子集生成和组合问题

打印n个数的组合

打印n个数中任意m个数的组合


递归和排列

n个数的全排列,共n!个

1)用STL输出全排列 直接用C++STL的库函数next_permutation(),按字典序输出下一个排列。在使用之前,先用sort()给数据排序,得到最小排列,然后每调用next_permutation()一次,就得到一个大一点的排列。

next_permutation()优点:从小到大的顺序输出排列。

#include <bits/stdc++.h>
using namespace std;

//1)利用STL输出全排列
int main()
{
	int data[] = { 5,2,1,4 };//排列数组
	int n = 0;//计数
	sort(data, data + 4);//排序,得到最小排列
	do {
		for (int i = 0; i < sizeof(data) / sizeof(int); i++) {
			cout << data[i] << ' ';//打印排列
		}
		cout << endl;
		n++;
	} while (next_permutation(data, data + 4));//把下一个排列放在data中
	cout << n << endl;
    return 0;
	
}

2)递归打印全排列

1.让第一个数不同,得到n个数列(方法:把第一个数和后面的数交换)

2.在上面的每个数列去掉第一个数,对后面的n-1个数进行类似的排列

3.重复以上步骤

#include <bits/stdc++.h>
using namespace std;

int num = 0;//计数
int number[] = { 5,2,1,4 };
void Perm(int begin, int end)
{
	int i;
	if (begin == end) {
		num++;//递归结束,产生一个全排列
		for (int j = 0; j < 4; j++)
			cout << number[j] << ' ';//可打印该排列
		cout << endl;
	}
	else {
		for (i = begin; i <= end; i++)
		{
			swap(number[begin], number[i]);//把当前数与后面数交换
			Perm(begin + 1, end);
			swap(number[begin], number[i]);//恢复,完成下一次交换
		}
	}
}

int main()
{
	Perm(0, 3);//全排列
	cout << num << endl;
	return 0;
}

打印n个数中任意m个数的全排列

方法:只需对前面递归全排列if (begin == end)修改if (begin == m)成即可。

void Perm(int begin, int end)
{
	int i;
	if (begin == 2) {
		num++;//递归结束,产生一个全排列
	}
	else {
		for (i = begin; i <= end; i++)
		{
			swap(number[begin], number[i]);
			Perm(begin + 1, end);
			swap(number[begin], number[i]);//恢复,完成下一次交换
		}
	}
}

子集生成和组合问题

打印n个数的组合

组合:即子集内部的元素是没有顺序的。

例如:{a₀,a₁,a₂}。子集有:{Φ},{a₀},{a₁},{a₂},{a₀,a₁},{a₀,a₂},{a₁,a₂};{a₀,a₁,a₂}

用二进制的概念进行对照:

子集Φa₀a₁a₀,a₁a₂a₀,a₂a₁,a₂a₀,a₁,a₂
二进制数000001010011100101110111

所以,每个子集对应一个二进制数,这个二进制数的每个1都对应着这个子集中的某个元素,而且子集中的元素是没有顺序的。【子集的数列 = 二进制数的总个数2^n】

代码:

#include <bits/stdc++.h>
using namespace std;

void print_subset(int n)
{
	for (int i = 0; i < (1 << n); i++) {
		//i:0 - 2^n,每个i的二进制数对应一个子集,
		for (int j = 0; j < n; j++) 
			if (i & (1 << j))//从i的最低位开始逐个检查每一位,是1则打印
				cout << j << ' ';
		cout << endl;
	}
}

int main()
{
	int n;
	cin >> n;
	print_subset(n);
}

打印n个数中任意m个数的组合

一个子集对应一个二进制数,那么一个有k个元素的子集,对应的二进制数就是有k个1。方法一是对这个n位二进制数逐位检查n次。

方法二是可以直接定位二进制数中1的位置,跳过中间的0。【a=a&(a-1):消除a的二进制数的最后一个1,全部消除的操作次数就是1的个数。】

例如:

1011 & (1011-1)=1011& 1010= 1010;

1010 & (1010-1)=1010& 1001= 1000;

1000 & (1000-1)=1000& 0111= 0000;//直到结果为0,消除1完毕

树状数组中有一个类似的操作lowbit(x)=x&(-1);功能:计算x的二进制数的最后一个1.

#include <bits/stdc++.h>
using namespace std;

void print_subset(int n, int k)
{
	for (int i = 0; i < (1 << n); i++) {//获得每个子集i
		int num = 0, a = i;
		while (a) {
			a = a & (a - 1);//清楚a中最后一个1
			num++;//统计1的个数
		}
		if (num == k) {//二进制数中的1有k个,符合条件
			for (int j = 0; j < n; j++)
				if (i & (1 << j))
					cout << j << ' ';
			cout << endl;
		}
	}
}

int main()
{
	int n, k;
	cin >> n >> k;
	print_subset(n, k);
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值