搜索使用的算法是BFS和DFS,BFS用队列,DFS用递归具体实现。在BFS和DFS的基础上可以扩展出A*算法、双向广搜算法、迭代加深算法、IDA*等技术。
暴力法:把所有可能的情况都罗列出来,然后逐一检查,从中找出答案。
思路:
1.找到所有可能的数据,并用数据结构表示和存储
2.剪枝。尽量多地排除不符合条件的数据,以减少搜索的空间
3.用某个算法快速检索
目录
递归和排列
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₂ |
二进制数 | 000 | 001 | 010 | 011 | 100 | 101 | 110 | 111 |
所以,每个子集对应一个二进制数,这个二进制数的每个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);
}