【笔记】搜索

版权声明:欢迎评论交流,转载请注明原作者。 https://blog.csdn.net/m0_37809890/article/details/83654146

参考资料:紫书第7章
配套例题:紫书第7章:搜索(例题部分)

0. 搜索

搜索,暴力,暴搜,意思就是,把所有可能都列举出来,一一实验。

1. 直接枚举

折半枚举:枚举一半,计算另一半。
选择合适的枚举对象,缩小枚举范围

2. 枚举排列

测试了三种生成排列的方法。
std::next_permutation效率很高,而且方便写。
基于交换的序列生成方法效率也很高,但交换可能会成为复杂对象序列的瓶颈。
刘老师的方法说实话没学懂=_=

几个结论:

  1. 总排列数 = n ! ( a i ! ) \frac {n!}{\prod (a_i!)} ,n为总元素个数, a i a_i 为每个元素的个数。
  2. 复杂度几乎全部来源于最后两层。
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

6. 迭代加深搜索

7. 其它

猜你喜欢

转载自blog.csdn.net/m0_37809890/article/details/83654146