目录
-
深度优先遍历——迷宫问题
在一个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++一些基础语法极其数据结构,于是找来校内的老师同学的算法课程及某谷进行学习,因为目前暂且没有十分明确的方向,所有只是想以做题促学,领略代码之美。准备在这里记叙继续自己学习的历程,希望自己能早日找到方向并提升技术,欢迎各位大佬提出有关学习路线、课程书籍推荐、学习心得、以及本次博客所反映的不足之处等相关建议意见。