算法学习日记1:21-11-23(深度优先遍历、暴力枚举)

143 阅读7分钟

目录

深度优先遍历——迷宫问题

暴力枚举

 P2241 统计方形个数

 1706 全排列问题

 1618 按比例组合数(三连击)

 结语


​​

  • 深度优先遍历——迷宫问题

在一个n*m的矩形区域内,寻找从一个格子到另一个格子的路线。

可以创建一个二维数组,并且将数组增加一行一列,不管数组的0行0列,以顺应人从1开始计数的习惯。进行如下初始化操作:

#include <iostream>
#include <cstdio> 
//迷宫问题,一个4*4迷宫寻找走出的路线 
using namespace std;
int map[110][110],n;//定义地图为二维数组,假如规定满足100(则要满足以上容量,防止溢出)
int main()
{
	scanf("%d",&n);//实际容量4×4 
	for(int i=1;i<=n;i++)//(从1开始赋值)定义的时候加一行加一列,第0行列不管 n实际容量 
	for(int j=1;j<=n;j++)
	//cin>>map[i][j]运行过慢,改用scanf,需要c语言库<cstdio> 
	scanf(%d,&map[i][j]); 
	return 0;
 } 

设计一个递归函数:

int dx[4]={0,1,-1,0},dy[4]={1,0,0,-1} ;//方向 
bool vis[110][110];//标记数组,默认值是0 
void dfs(int x,int y){
	//递归思想,设计一个递归,考虑:递归终止条件 
	if(x==n && y==n)
	{
		printf("yes"\n);
		return;
	}
	else//尝试四个方向走一遍 
	{
		for(int i=0;i<4;i++)//四个方向 
		{
			int x1=x+dx[i],y1=y+dy[i]//通过改变xy坐标确定移动方向
			if(x1>n||x1<1||y1>n||y1<1||map[x1][y1]==1)//考虑范围和特殊情况 
			dfs(x1,y1);//找四个方向邻接点,若存在则继续走 
		}
	}
}

将其变得完整: ​

\

#include <iostream>//深度优先搜索 
#include <cstdio> 
//迷宫问题,一个4*4迷宫寻找走出的路线 
using namespace std;
int map[110][110],n;//定义地图为二维数组,假如规定满足100(则要满足以上容量,防止溢出)
int dx[4]={0,1,-1,0},dy[4]={1,0,0,-1} ;//方向 
bool vis[110][110];// 标记数组,默认值为0 
void dfs(int x,int y){
	//递归思想,设计一个递归,考虑:1、递归终止条件 ,2、让程序数据规模递减 
		vis[x][y]=true;//走过的点被标记访问 
	if(x==n && y==n)//1、递归终止条件 
	{
		printf("yes\n");
		return;
	}
	else//尝试四个方向走一遍 
	{
		for(int i=0;i<4;i++)//四个方向 
		{
			int x1=x+dx[i],y1=y+dy[i];//通过改变xy坐标确定移动方向
			if(x1>n||x1<1||y1>n||y1<1||map[x1][y1]==1)//考虑范围和特殊情况 
			    continue;//非法情况排除 
			dfs(x1,y1);//合法情况继续,找四个方向邻接点,若存在则继续走 
		}
	}
}
int main()
{
	scanf("%d",&n);//实际容量4×4 
	for(int i=1;i<=n;i++)//(从1开始赋值)定义的时候加一行加一列,第0行列不管 n实际容量 
	for(int j=1;j<=n;j++)
	//cin>>map[i][j]运行过慢,改用scanf,需要c语言库<cstdio> 
	scanf("%d",&map[i][j]); 
	dfs(1,1);//深度优先遍历,(1,1)为起点 
	return 0;
 } 
 /*
 4
 0 0 1 0
 0 0 0 0
 0 0 0 0
 0 0 0 0
 */

 最终编写成题目要求的形式,方法一:

#include <iostream>//深度优先搜索 
#include <cstdio> 
//迷宫问题,一个4*4迷宫寻找走出的路线 
using namespace std;
int map[110][110],n;//定义地图为二维数组,假如规定满足100(则要满足以上容量,防止溢出)
int dx[4]={0,1,-1,0},dy[4]={1,0,0,-1} ;//方向 
bool vis[110][110],flag;// 标记数组vis,默认值为0 ,增加个逻辑变量flag ,默认也为false 
void dfs(int x,int y){
	//递归思想,设计一个递归,考虑:1、递归终止条件 ,2、让程序数据规模递减 
	vis[x][y]=true;//走过的点被标记访问 
	if(x==n && y==n)//1、递归终止条件 
	{
		flag=true;
		return;
	}
	else//尝试四个方向走一遍 
		for(int i=0;i<4;i++)//四个方向 
		{
			int x1=x+dx[i],y1=y+dy[i];//通过改变xy坐标确定移动方向
			if(x1>n||x1<1||y1>n||y1<1||map[x1][y1]==1||vis[x][y]==1)//考虑范围和特殊情况 
			    continue;//非法情况排除 
			dfs(x1,y1);//合法情况继续,找四个方向邻接点,若存在则继续走 
		}
}
int main()
{
	scanf("%d",&n);//实际容量4×4 
	for(int i=1;i<=n;i++)//(从1开始赋值)定义的时候加一行加一列,第0行列不管 n实际容量 
	for(int j=1;j<=n;j++)
	//cin>>map[i][j]运行过慢,改用scanf,需要c语言库<cstdio> 
	scanf("%d",&map[i][j]); 
	dfs(1,1);//深度优先遍历,(1,1)为起点 
	if(flag)printf("yes\n");
			else printf("false\n");
	return 0;
 } 

方法2

#include <iostream>//深度优先搜索 
#include <cstdio> 
//迷宫问题,一个4*4迷宫寻找走出的路线 
using namespace std;
int map[110][110],n;//定义地图为二维数组,假如规定满足100(则要满足以上容量,防止溢出)
int dx[4]={0,1,-1,0},dy[4]={1,0,0,-1} ;//方向 
bool flag;// 标记数组vis,默认值为0 ,增加个逻辑变量flag ,默认也为false 
void dfs(int x,int y){
	//递归思想,设计一个递归,考虑:1、递归终止条件 ,2、让程序数据规模递减 
	map[x][y]=1;//此为方法2,同样起到标记作用(形成障碍物),相比vis减少了空间时间 
	if(x==n && y==n)//1、递归终止条件 
	{
		flag=true;
		return;
	}
	else//尝试四个方向走一遍 
		for(int i=0;i<4;i++)//四个方向 
		{
			int x1=x+dx[i],y1=y+dy[i];//通过改变xy坐标确定移动方向
			if(x1>n||x1<1||y1>n||y1<1||map[x1][y1]==1)//考虑范围和特殊情况 
			    continue;//非法情况排除 
			dfs(x1,y1);//合法情况继续,找四个方向邻接点,若存在则继续走 
		}
}
int main()
{
	scanf("%d",&n);//实际容量4×4 
	for(int i=1;i<=n;i++)//(从1开始赋值)定义的时候加一行加一列,第0行列不管 n实际容量 
	for(int j=1;j<=n;j++)
	//cin>>map[i][j]运行过慢,改用scanf,需要c语言库<cstdio> 
	scanf("%d",&map[i][j]); 
	dfs(1,1);//深度优先遍历,(1,1)为起点 
	if(flag)printf("yes\n");
			else printf("false\n");
	return 0;
 } 

  • 暴力枚举

暴力是算法题目中一种常用的手段,暴力就是枚举,从所有可能解的集合中枚举元素,根据题目检验条件判定那些有用,是题目成立的就是答案,简单一项,局限性比较大,数据量大的效率低速度慢,所以使用之前计算时间复杂度。

估计算法需要执行的运算次数O(f(n)),

 根据大小选择是否暴力解,是否进行算法上的优化 

暴力例题:

百钱百鸡问题:利用方程组,三重for循环,枚举xyz的值,较为简单。可以优化 

优化:三重可以不可以减少为两重,用差值表示第三个变量,对于此题较为复杂

优化:一个循环枚举x,yz用x表示

  •  P2241 统计方形个数

本题需要在一个限定面积的区域内,统计其包含的不同边长的长方形和正方形的和。

本题可以如下思路求解:在n*m的矩形区域内,先求出其中所有矩形的总数,再求出其中一种矩形的数量,最后相减得出另一种矩形的数量。

首先从n*m区域内判断矩形数量

先从一行来看,从左开始,11的区域只有一个11的矩形x11(+1个),12的区域内多出一个11的矩形x12以及整个12的矩形x11x12(+2个),13的区域里面多出出一个11的矩形x13、一个12的矩形x12x13、整个1*3的矩形x11x12x13(+3个),注意括号内的表示多出去与以后增加的数量,这样推导使得更容易看出其等差数列般的分布规律。

根据其中的规律,不难推测出:在m取任意值a的情况下,n*a区域内,矩形总数随着n++的规律为1+2+...+n个。

 注意:多出来的颜色即代表着多出来的矩形(及其个数),设m为任意值a,此处为了方便看懂,在图中就设为1格长度。

再从一列来看,也就是忽略了n的情况下,同样可以推理出类似的结论:在n取任意值a的情况下,m*a区域内,矩形总数随着m++的规律为1+2+...+m个。

 这样以来,将n和m都算进去以后,整个区域的个数总和,自然就是两种情况的积值:(1+2+...+n)*(1+2+...+m) 个,等于 [n(n+1)/2][m*(m+1)/2]** 个(等差数列)。

矩形总量求出来了,接下来就是求其中一种矩形的数量,正方形相比长方形来说形状单一,比较好入手,于是可以观察得出以下求法:在n*m的区域内,找出n和m中的最值max和min,因为这决定了正方形的边长以及数量限制。

无论是根据图形的分布律,还是根据每行每列可以占的格子数的积值,它们所推导出的个数最终都可以推算出这个式子:设正方形边长为i,则正方形个数为 [n-(i-1)]*[m-(i-1)] 个,进一步推导为易于计算机识别的语句:i(i-min+max)*

推导过程如下:

 最终代码:

#include <bits/stdc++.h> 
using namespace std;
int main(){
	long long n,m,Min,Max,sum,k=0;//此处处理数值可能会很大,需要定义为long型
	cin>>n>>m;
	sum=(n*(n+1)/2)*(m*(m+1)/2);//矩形个数和 
	Min=min(n,m);
	Max=max(n,m);
	for(int i=1;i<=Min;i++){
		k+=i*(i-Min+Max);//正方形个数 
	} 
	cout<<k<<" "<<sum-k<<endl;
	return 0;
}

  •  1706 全排列问题

比较经典的暴力例题。讲的很快以至于刚开始甚至没听懂,课后多看几遍代码理解了。

\

 利用树的思想,从第一个数字开始向下延伸树结点,重复的忽略,得出树的路径就是全排列的顺序。可以用纯暴力枚举的方式求解。

#include <bits/stdc++.h> 
using namespace std;
int main(){
	int a[10];
	for(int i=1;i<10;i++) a[i]=i; //定义一个数组a[i],里面抽取i作为第几个数 
	int n;//定义整数n 
	cin>>n;
	for(int i1=1;i1<=n;i1++){//把第一个数从1到n枚举( 多次循环) 
		if(n==1){//如果总数n为1,排序停止,输出排序结果 
			cout<<"     "<<i1;//5个厂宽 
			continue;//跳过之后几个数的枚举 ,返回本次循环(再i++直接结束了),输出的就是现有的排序结果 
		}
		for (int i2=1;i2<=n;i2++){//此时n肯定大于1,所以将第二个数进行枚举 
			if(i2==i1)//出现重复的数字,中断并返回,重新选取枚举的数字 
		    continue;
			if(n==2){
				cout<<"     "<<i1<<"     "<<i2<<"\n";
				continue;
			}//以此类推 
			for(int i3=1;i3<=n;i3++){
				if(i3==i1||i3==i2)
				continue;
				if(n==3){
					cout<<"     "<<i1<<"     "<<i2<<"     "<<i3<<"\n";
					continue;
				}
				for(int i4=1;i4<=n;i4++){
					if(i4==i1||i4==i2||i4==i3)
					continue;
					if(n==4){
						cout<<"     "<<i1<<"     "<<i2<<"     "<<i3<<"     "<<i4<<"\n";
						continue;
					}
					for(int i5=1;i5<=n;i5++){
						if(i5==i4||i5==i3||i5==i2||i5==i1)
						continue;
						if(n==5){
							cout<<"     "<<i1<<"     "<<i2<<"     "<<i3<<"     "<<i4<<"     "<<i5<<"     "<<"\n";
							continue;
						}
						for(int i6=1;i6<=n;i6++){
							if(i6==i5||i6==i4||i6==i3||i6==i2||i6==i1)
							continue;
							if(n==6){
								cout<<"     "<<i1<<"     "<<i2<<"     "<<i3<<"     "<<i4<<"     "<<i5<<"     "<<i6<<"     "<<"\n";
							    continue;
							}
							for(int i7=1;i7<=n;i7++){
								if(i7==i6||i7==i5||i7==i4||i7==i3||i7==i2||i7==i1)
								continue;
								if(n==7){
									cout<<"     "<<i1<<"     "<<i2<<"     "<<i3<<"     "<<i4<<"     "<<i5<<"     "<<i6<<"     "<<i7<<"     "<<"\n";
									continue;
								}
								for(int i8=1;i8<=n;i8++){
									if(i8==i7||i8==i6||i8==i5||i8==i4||i8==i3||i8==i2||i8==i1)
									continue;
									if(n==8){
										cout<<"     "<<i1<<"     "<<i2<<"     "<<i3<<"     "<<i4<<"     "<<i5<<"     "<<i6<<"     "<<i7<<"     "<<i8<<"     "<<"\n";
										continue;
									}
									for(int i9=1;i9<=n;i9++){
										if(i9==i8||i9==i7||i9==i6||i9==i5||i9==i4||i9==i3||i9==i2||i9==i1)
										continue;
										if(n==9){
											cout<<"     "<<i1<<"     "<<i2<<"     "<<i3<<"     "<<i4<<"     "<<i5<<"     "<<i6<<"     "<<i7<<"     "<<i8<<"     "<<i9<<"     "<<"\n";
											continue;
										}
									}
								}
							}
						}
					} 
				}
			}
		}
	}
	return 0;
}

  暴力的方法虽然可以运行成功,但是运算的数据量过大,在某谷上也显示Unaccepted,只适用于帮助初学算法的人理解一下暴力算法,优化算法:深度搜索。(目前还不会,只得到了标准答案)

#include<bits/stdc++.h>
using namespace std;//深度搜索 
int n,pd[100],used[100];//pd判断是否用过这个数
void print()//输出函数
{
	int i;
	for(i=1;i<=n;i++)
	printf("%5d",used[i]);
	cout<<endl; 
 } 
 void dfs(int k)//深搜函数,当前是第k格 
{
	int i;
	if(k==n) //填满了的时候
	{
		print();//输出当前解
		return;
	}
	for(i=1;i<=n;i++)//1-n循环填数
	{
		if(!pd[i])//如果当前书没有用过
		{
			pd[i]=1;//标记一下
			used[k+1]=i;//把这个数填进数组
			dfs(k+1);//填下一个
			pd[i]=0;//回溯 
		 } 
	 } 
}
int main()
{
	cin>>n;
	dfs(0);//从第0格开始
	return 0; 
 } 

  •  1618 按比例组合数(三连击)

 

暴力计算会使9个数暴力,如上题一样数据量过大。因此可以尝试反推,不一定组成三个成比例的数,而是根据比例推出来有多少个数满足,并且从中挑选符合题意的数。三个数乘同一个数依然成比例。

#include<iostream>
using namespace std;
int a[10],b1,b2,b3,l,k1,k2,k3,ans;
int main ()
{
    cin >>k1>>k2>>k3;//比例中的A、B、C 
    for (int b=1;b<=1000/k3;++b)//1000/k3是输入数最大值,不能超过三位数 
    {
        b1=b*k1;//求出三个数
        b2=b*k2;
        b3=b*k3; 
        if (b2>999||b3>999)break;
        for (int c=1;c<=3;++c)//将三个数进行分解,判断是否重复
        {
            a[b1%10]++;///分出一个三位数的三个位数 
            b1/=10;
        }
        for (int c=1;c<=3;++c)
        {
            a[b2%10]++;
            b2/=10;
        }
        for (int c=1;c<=3;++c)
        {
            a[b3%10]++;
            b3/=10;
        }
        for (int c=1;c<=9;++c)if (a[c]!=1){l=1;break;}//l相当于布尔值 判断是否成立 
        for (int c=1;c<=9;++c)a[c]=0;
        if (!l){cout <<b*k1 <<" " <<b*k2 <<" " <<b*k3 <<endl;ans++;}//将解输出,并做标记
        else l=0;
    }
    if (!ans)cout <<"No!!!";//判断无解情况
    return 0;
}

  •  结语

作为一个大三计科在读生,感觉自己的基础非常不扎实,之前学过所记得的只有c/c++一些基础语法极其数据结构,于是找来校内的老师同学的算法课程及某谷进行学习,因为目前暂且没有十分明确的方向,所有只是想以做题促学,领略代码之美。准备在这里记叙继续自己学习的历程,希望自己能早日找到方向并提升技术,欢迎各位大佬提出有关学习路线、课程书籍推荐、学习心得、以及本次博客所反映的不足之处等相关建议意见。