【C++学习笔记1】搜索与回溯算法的实际应用指南:全排列问题与优化技巧

哈喽大家好!今天小鹅来给大家讲一讲C++的算法——搜索与回溯。

目录:

一、今日干货?

二、正文 

1.引入

2.搜索与回溯

1️⃣算法分析

2️⃣剪枝优化

3.N皇后问题

1️⃣分析

2️⃣实现

 一、今日干货?

1.了解并掌握搜索和回溯算法的内涵、解题步骤和代码框架✅

2.搜索树和剪枝优化✅

3.全排列与N皇后问题✅

二、正文 

1.引入

先看一个问题:

给定一个正整数N(1≤N≤15)请你从小到大生成1-N的全排列。

举个栗子:

N=3时,1-3的全排列为:

[1,2,3]  [1,3,2]  [2,1,3]  [2,3,1]  [3,2,1]  [3,1,2] 

面对这种问题,很多人或许第一反映是用for循环枚举

for(int i=1;i<=3;i++){//枚举第一位 
    for(int j=1;j<=3;j++){//枚举第二位 
        for(int k=1;k<=3;k++){//枚举第三位 
        	if(i!=j&&j!=k&&i!=k){//如果互不相同 
        		cout<<i<<' '<<j<<' '<<k<<endl;//输出 
			}
		}
	}
}

这种方法虽然可以,但有以下缺点

1.如果N很大,容易超时

2.N不一定就是3。如果N是4、5、6呢?不可能每个N都写一个枚举

所以我们引入一个算法——搜索与回溯 

2.搜索与回溯

1️⃣算法分析

上面题目出现一个问题:

因为N不是一定的,所以循环次数元素数量也是不确定的,所以无法进行for循环。

由于每一位都要进行一次遍历,所以我们可以把遍历的内容写成一个子程序。进行递归求解

所以我们来分析一下递归的内容

我们可以定义一个叫cur的变量表示当前放置数字的位置,定义一个ans数组存储全排列

1.递归边界:

cur>n时,说明已经构造好了一个全排列。

Q:为什么不是cur==n?

A:因为第n个位置也要枚举。

此时就要输出ans数组并退出。

2.内容

由于要枚举第cur个位置上的数字,所以要对1到n进行for循环

for循环里要存储枚举的数:ans[cur]=i;

随后遍历下一位:solve(cur+1);


实现代码:

const int N=20;
int n,ans[N];
void solve(int cur){
	if(cur>n){//说明已经填完一个排列
		for(int i=1;i<=n;i++)cout<<ans[i]<<' ';
		printf("\n");
	}
	for(int i=1;i<=n;i++){//否则遍历
		ans[cur]=i;
		solve(cur+1);
	}
}

这时运行会出现问题:

无限输出1 1 1??


分析代码,我们发现原来没有判断重复。该如何解决?

这时就要引入一个小技巧——剪枝优化

2️⃣剪枝优化

通过分析代码运行过程,我们会发现过程很像一颗树

如图,当n=3时:

所以全排列的搜索其实就是解树的dfs,即从根节点到叶子节点构成一个解。

上述代码有N^N种可能,然而全排列其实只有N!(N*(N-1)*(N-2)*……*1)种可能!

所以在遍历过程中遇到不可能的解就可以停止搜索,降低搜索量!

这就是——剪枝优化。

所以我们可以定义一个bool类型的vis数组,用来标记此数字是否已经使用

算法框架代码:

const int N=20;
int n,ans[N];
bool vis[N];
void solve(int cur){
	if(cur>n){//说明已经填完一个排列
		for(int i=1;i<=n;i++)cout<<ans[i]<<' ';
		printf("\n");
	}
	for(int i=1;i<=n;i++){//否则遍历
		if(!vis[i]){//如果此数字没使用过 
			vis[i]=true;//标记成已使用 
			ans[cur]=i;
			solve(cur+1);
			vis[i]=false;//记得恢复 
		}	
	}
}

运行结果:

3.N皇后问题

题目:

N皇后问题,即在N×N格的国际象棋上摆放N个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,请问有多少种摆法。图所示即是摆法的一种。3<N≤12

1️⃣分析

按照平常的思路,看到题目第一个思路就是从N*N个格子中挑出N个格子,使得任意两个格子都不能处于同一行、同一列或同一斜线上。当然这肯定很麻烦。

通过观察题目,由于N个皇后不可能处于同一行,所以一定是一行放一个皇后!

那么这就变成一个搜索问题:搜索每一行的每一列,尝试放置皇后,通过判断突进行剪枝

2️⃣实现

定义一个cur变量,表示当前在第cur行放置皇后,ans数组存储每个皇后处于的列

同时还要写一个check函数,用来检查此解是否成立

什么时候成立呢?任意两个皇后都不能处于同一行、同一列或同一斜线上。

同一行:

不需要判断,因为递归程序已经控制每个皇后单独一行


同一列:

传进来两个参数x,y,表示当前皇后的位置。遍历1到x-1,即之前所有皇后的列。如果y==ans[i],说明处于同一列。


同一斜线:

斜线分左斜线右斜线。该如何判断?


左斜线:

由图可知,左斜线特点为行下标-列下标相等

所以如果x-y==i-ans[i],说明同处于左斜线


右斜线:

由图可知,右斜线特点为行下标+列下标相等

所以如果x+y==i+ans[i],说明同处于右斜线

check函数代码:

bool check(int x,int y){
	for(int i=1;i<x;i++){//遍历所有列
		if(y==ans[i]||x-y==i-ans[i]||x+y==i+ans[i])return false;
	}
	return true;
}

总代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cstring>
#include<queue>
using namespace std;
const int N=15;
int n,ans[N],cnt=0;
bool check(int x,int y){
	for(int i=1;i<x;i++){
		if(y==ans[i]||x-y==i-ans[i]||x+y==i+ans[i])return false;
	}
	return true;
}
//cur--当前在第cur行放置皇后 
void dfs(int cur){
	if(cur>n){
		cnt++;
		return ;
	}
	for(int j=1;j<=n;j++){
		if(check(cur,j)){
			ans[cur]=j;
			dfs(cur+1);
		}
	}
}
int main()
{
	cin>>n;
	dfs(1);
	cout<<cnt;
	return 0;
}

好了,今天的算法就讲到这里。如果文章有什么问题还希望大家多多指教!有知识上的问题可以在评论区问我。记得点个赞哦~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值