版权声明:欢迎评论交流,转载请注明原作者。 https://blog.csdn.net/m0_37809890/article/details/83654146
参考资料:紫书第7章
配套例题:紫书第7章:搜索(例题部分)
0. 搜索
搜索,暴力,暴搜,意思就是,把所有可能都列举出来,一一实验。
1. 直接枚举
折半枚举:枚举一半,计算另一半。
选择合适的枚举对象,缩小枚举范围
2. 枚举排列
测试了三种生成排列的方法。
std::next_permutation效率很高,而且方便写。
基于交换的序列生成方法效率也很高,但交换可能会成为复杂对象序列的瓶颈。
刘老师的方法说实话没学懂=_=
几个结论:
- 总排列数 = ,n为总元素个数, 为每个元素的个数。
- 复杂度几乎全部来源于最后两层。
void print_permutation(int arr[],int u,int n)
{
if(u==n)
{
for(int i=0;i<n;++i)
printf("%d ",arr[i]);
putchar('\n');
return;
}
for(int v=u;v<n;++v)
{
int fail = 0;
for(int i=u;i<v;++i)
if(arr[i]==arr[v]) fail = 1, i = v;
if(!fail)
{
swap(arr[u], arr[v]);
print_permutation(arr,u+1,n);
swap(arr[u], arr[v]);
}
}
}
void print_permutation(int arr[],int n)
{
sort(arr,arr+n);
do{
for(int i=0;i<n;++i)
printf("%d ",arr[i]);
putchar('\n');
}while(next_permutation(arr,arr+n));
}
int A[200];
void print_permutation(int n, int* P, int* A, int cur) {
if(cur == n) {
for(int i = 0; i < n; i++) printf("%d ", A[i]);
printf("\n");
} else for(int i = 0; i < n; i++) if(!i || P[i] != P[i-1]) {
int c1 = 0, c2 = 0;
for(int j = 0; j < cur; j++) if(A[j] == P[i]) c1++;
for(int j = 0; j < n; j++) if(P[i] == P[j]) c2++;
if(c1 < c2) {
A[cur] = P[i];
print_permutation(n, P, A, cur+1);
}
}
}
int main(void)
{
#ifdef _LITTLEFALL_
freopen("out.txt","w",stdout);
#endif
int arr[]={1,2,3,4,4,4,4,5,5,6};
print_permutation(10,arr,A,0);
double t3 = clock();
fprintf(stderr,"RUJIA time used = %.2fs\n",(t3)/CLOCKS_PER_SEC);
print_permutation(arr,0,10);
double t1 = clock();
fprintf(stderr,"SWAP time used = %.2fs\n",(t1-t3)/CLOCKS_PER_SEC);
print_permutation(arr,10);
double t2 = clock();
fprintf(stderr,"STD time used = %.2fs\n",(t2-t1)/CLOCKS_PER_SEC);
return 0;
}
结果
RUJIA time used = 1.80s
SWAP time used = 1.59s
STD time used = 1.58s
update:刘老师的代码实际上就是回溯法,因为next_permutation和交换法是专业求解排列问题所以才比它们慢,但用来求解其它问题是非常快的。
3. 枚举子集
int n = 9;
int arr[]={1,2,3,5,6,7,8,9,10};
for(int mask=0,all=(1<<n)-1;mask<=all;++mask)
{
for(int i=0;i<n;++i)
if(mask&(1<<i)) printf("%d ",arr[i]);
printf("\n");
}
4. 回溯法
回溯法又叫递归枚举算法,当探索到某一步发现没有合法或更优的选择时,返回上一级递归调用,这种现象称为回溯。
八皇后问题
在n*n的棋盘上放置n个国际象棋的皇后(攻击范围:同行,同列,同对角线),使得她们互不攻击。
这个问题在计算机发明前就有了,当时n一般取8,所以称为8皇后问题。
枚举排列,判断每个排列:
int ans = 0;
do{
int fail = 0;
int use[2][n<<1]={};
for(int i=0;i<n;++i)
{
if(use[0][i+arr[i]] || use[1][i-arr[i]+n])
fail = 1, i = n;
use[0][i+arr[i]] = use[1][i-arr[i]+n] = 1;
}
if(!fail) ++ans;
}while(next_permutation(arr,arr+8));
刘老师的标准回溯法代码:
void search(int u) //要填充的位置
{
if(u==n) ++ans;
else for(int i=1;i<=n;++i)
if(!use[0][i] && !use[1][u+i] && !use[2][u-i+n])
{
arr[u] = i;
use[0][i] = use[1][u+i] = use[2][u-i+n] = 1;
search(u+1);
use[0][i] = use[1][u+i] = use[2][u-i+n] = 0;
}
}
这份代码非常快,用来解14皇后只需要2.41s
5. 状态空间搜索
即广义的bfs