序文
みなさん、こんにちは。私はアルゴリズム愛好家のバブルです。アルゴリズムの学習の道には浮き沈みと「大きな山」がたくさんあります。誰もが深い理解を持っていると思います。アルゴリズムの学習にはいくつかの非常に難しい点があると思います。深さ優先探索と幅優先探索です。意味はわかりますが、コードをまったく入力できず、基本的な質問も書けないので、とても戸惑います。これは私が以前に学んだソートには当てはまりません。なぜですか?実際、アルゴリズムを理解しているときにテンプレートを覚えていなかったためです。yには常に非常に良いことわざがあります。アルゴリズムを構築しているのではなく、テンプレートの使用法を学んでいます。はい、これは数十です。人類の歴史の中で時代。何年も、あるいは何百年もの間、さまざまな科学者の知恵を結晶化することは、初心者にとって間違いなく難しいので、今日の記事はここにあります。
今日の記事は、まだ深さ優先探索にとらわれていて、実際にはコードを記述できない学生を支援するためのものです。この記事を読むと、別の体験が得られる可能性があります。ここでは、Yanスタイルのdp分析方法を模倣して独自に作成します。単純なdfsの4ステップの方法、4つのステップ、迷路の問題、配置の問題はそれをまったく行わないことを恐れていないことを覚えておいてください!
コンテンツ
アルゴリズムの概要
深さ優先探索:
これは一種のグラフ走査法であり、スタックによって実装されます。深さ優先探索は、ツリーの事前順序探索に似ています。
人間の言葉で言えば、その考えは南の壁にぶつかったり、振り返ったりせず、行けなくなるまで歩き続け、すべてのポイントが通過するまで他の方向を検討することです。明らかに、深さ優先探索は再帰的なプロセスです。
言っただけでは彼の魅力を感じられないかもしれませんが、上の写真を見てみましょう。
ブロガーの絵が醜いので、浙江大学のデータ構造を借りました。これが写真ですが、すべてのポイントを訪れたわけではありません。
次に、最初のポイントから開始します。コードで移動したパスを追跡するための配列が必要な場合は、電球がオンになっています。
それから私たちは右に行って、私たちが通過しなかったことを見つけました、そしてそれから私たちはここに行きました
ここに着いた後、あなたは左に行くことしかできないことに気づき、それから左に行き続けます
現時点では、2つの道がありますが、迷いはありませんか?いいえ、最終的には全員がトラバースしたので、完了です。上記を選択して、実行してください。
歩いてみると、行く道がないことがわかったので、同じように戻って、選択した場所に戻りました。
次に、今まで行ったことのない場所を選び、歩き続けます
終了
写真を見ると、誰もが深さ優先探索の仕組みをはっきりと理解できると思いますが、実際のアプリケーションでコードをどのように記述する必要がありますか?深さ優先探索をより深く理解するために、いくつかの例を紹介します。
私たちの4ステップの方法、最初のステップに従い、地図、方向を保存し、配列にマークを付けます
画像配列、方向配列、マーク配列を何に格納するかを決めるには、誰もが質問を読む必要があります。ここでは、通常の2次元配列として、上下左右に記述します。
const int N = 10001;
int dx[] = {0,1,-1,0};
int dy[] = {1,0,0,-1};
int s[N][N];
bool vis[N][N];
最初のレイヤーはデータ範囲、2番目のレイヤーと3番目のレイヤーは方向です。この時点で1 1の場合、+ 0+1は12になり、正しいものになります。+1+0は21そして1つ下がる。4方向ともこんな感じです。8方向ならもっと必要です
int dx[] = {1,0,0,-1,1,-1,1,-1}
int dy[] = {0,1,-1,0,1,-1,-1,1}
最初のステップが解決されました。2番目のステップを開始しましょう
ステップ2:開始点、タイトル要件を見つけ、dfsを開始します
たとえば、接続されたブロックを見つけるように求められた場合、グラフ全体をループし、2つのforを入力してから、グラフの位置が接続されたブロックの文字または数と等しい場合は検索を入力できます。コードは次のとおりです。次のように
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(s[i][j]=='*')
{
dfs(i,j);
}
}
}
次に、3番目のステップであるdfs関数の記述があります。
dfs関数の記述方法、最初に現在の位置が渡されたことをマークし、次にループを記述します。いくつかのレイヤーをループするいくつかの方向があり、ループに書き込み、境界を超えたかどうか、および要件を満たしているかどうかを判断します。そうでない場合は、繰り返します。コードは次のとおりです。
bool pd(int x,int y)
{
if(x<1||x>n||y<1||y>m)
{
return false;
}
if(vis[x][y])
{
return false;
}
return true;
}
void dfs(int x, int y)
{
vis[x][y] = 1;
for(int i=0;i<4;i++)
{
int xx = x+dx[i];
int yy = y+dy[i];
if(pd(xx,yy)&&s[xx][yy]=='.')
{
dfs(x+xx[i],y+yy[i]);
}
}
}
4番目のステップは、回答の出力を保存することです。これは非常に単純で、コードは配置しません。
要約すると、Unicomブロックdfsのコードテンプレートを学習しました。Unicomブロックの問題が発生した場合は、これらの4つの手順を使用して適切に解決できます。まず、これらの4つの手順を確認して記録してから、次の手順を実行します。それを統合するために2つを実行してください!
演習1:海戦
トピックの説明
サミットの間、軍隊は非常に警戒している。警察はすべての街路を監視し、軍隊は建物を警備し、空域はF-2003航空機で満たされます。さらに、海岸線を保護するために巡洋艦と艦隊が派遣されます。残念ながら、さまざまな理由から、防衛提督には大規模な海戦を指揮できる将校が数人しかいません。そこで、彼らはいくつかの新しい海軍司令官を訓練することを検討し、彼らの学習を助けるために「海戦」ゲームを選びました。
この有名なゲームでは、一定の数と形の船が正方形のプレートに配置され、それぞれが他の船に触れることはできません。この問題では、ボートは正方形であると見なすだけで、すべてのボートは図形で構成された正方形です。ボードに配置された船の総数を見つけるためのプログラムを作成します。
入力フォーマット
入力ファイルの最初の行は、スペースで区切られた2つの整数RとC、1 <= R、C <= 1000で構成され、これら2つの数値は、それぞれゲームボードの行と列の数を表します。次のR行には、それぞれC文字が含まれています。各文字は「#」または「。」のいずれかです。「#」はボートの一部を表し、「。」は水を表します。
出力フォーマット
段落ごとに1行の解を出力します。船の位置が正しい場合(つまり、ボード上に互いに接触できない正方形しかない場合、2つの「#」記号が上下または左右に並んでいるが、2つの異なる船に属している場合、 2隻の船は互いに接触していると呼ばれます)。「S隻あります」という文を出力するだけで、Sは船の数を表します。それ以外の場合は、「不適切な配置」を出力します。
6 8 .....#。### .....### .....# .......## ...... ##..#...#
上記は、接続されたブロックを判断する最も簡単な問題です。4ステップの方法を直接適用します。最初のステップは、マップ、方向、およびマークを保存することです。
タイトルには4方向、2桁の配列が必要なため、上記を押すだけで開きます。
コード:
#include<bits/stdc++.h>
using namespace std;
const int N = 1001;
int dx[] = {1,0,0,-1};
int dy[] = {0,1,-1,0};
char s[N][N];
bool vis[N][N];
int main()
{
int r,c;
cin>>r>>c;
for(int i=1;i<=r;i++)
{
for(int j=1;j<=c;j++)
{
cin>>s[i][j];
}
}
return 0;
}
次に、2番目のステップです。開始点、サブジェクト要件を見つけ、dfsを開始します。
タイトルは、隣接する船が存在しないことを要求します。タイトルの意味は、2 * 2グリッドに3つの船体がある場合、隣接しているため、間違っています。独立して判断する関数を作成する必要があります。出発点は遭遇した人です。船体を歩きました。
bool pd(int a,int b)
{
int sum = 0;
if(s[a][b]=='#')
{
sum++;
}
if(s[a+1][b]=='#')
{
sum++;
}
if(s[a][b+1]=='#')
{
sum++;
}
if(s[a+1][b+1]=='#')
{
sum++;
}
if(sum==3)
{
return false;
}
return true;
}
これは、爆弾があるかどうかを判断するために、右下と右の4方向の判断機能の下にあります。
次に、開始点を見つけるためのコードがあります
for(int i=1;i<=r;i++)
{
for(int j=1;j<=c;j++)
{
if(s[i][j]=='#'&&vis[i][j]==0)
{
dfs(i,j);
}
}
}
船体番号を見つけて検索を開始します。
次に、dfsを作成する3番目のステップがあります。
dfsの書き方、上で言ったことを覚えておく、国境を越える、歩くかどうか、サイクリングする。
bool yuejie(int x,int y)
{
if(x<1||x>r||y<1||y>c)
{
return false;
}
if(vis[x][y])
{
return false;
}
return true;
}
void dfs(int x,int y)
{
vis[x][y] = 1;
for(int i=0;i<4;i++)
{
int xx = x+dx[i];
int yy = y+dy[i];
if(yuejie(xx,yy)&&s[xx][yy]=='#')
{
dfs(xx,yy);
}
}
}
4番目のステップは私たちの答えです。最初に船が爆撃されたかどうかを判断し、次に変数を定義します。チャイナユニコムブロックに入るたびに+1になります。船の爆撃が直接終了する場合は必要ありません。判断する。
完全なコードは次のとおりです
#include<bits/stdc++.h>
using namespace std;
const int N = 1001;
int dx[] = {1,0,0,-1};
int dy[] = {0,1,-1,0};
char s[N][N];
bool vis[N][N];
int r,c;
int num;
bool yuejie(int x,int y)
{
if(x<1||x>r||y<1||y>c)
{
return false;
}
if(vis[x][y])
{
return false;
}
return true;
}
void dfs(int x,int y)
{
vis[x][y] = 1;
for(int i=0;i<4;i++)
{
int xx = x+dx[i];
int yy = y+dy[i];
if(yuejie(xx,yy)&&s[xx][yy]=='#')
{
dfs(xx,yy);
}
}
}
bool pd(int a,int b)
{
int sum = 0;
if(s[a][b]=='#')
{
sum++;
}
if(s[a+1][b]=='#')
{
sum++;
}
if(s[a][b+1]=='#')
{
sum++;
}
if(s[a+1][b+1]=='#')
{
sum++;
}
if(sum==3)
{
return false;
}
return true;
}
int main()
{
cin>>r>>c;
for(int i=1;i<=r;i++)
{
for(int j=1;j<=c;j++)
{
cin>>s[i][j];
}
}
for(int i=1;i<=r;i++)
{
for(int j=1;j<=c;j++)
{
if(pd(i,j)==false)
{
cout<<"Bad placement.";
return 0;
}
}
}
for(int i=1;i<=r;i++)
{
for(int j=1;j<=c;j++)
{
if(s[i][j]=='#'&&vis[i][j]==0)
{
num++;
dfs(i,j);
}
}
}
printf("There are %d ships.",num);
return 0;
}
ダイレクトAC!実際、このコードはvisなしで使用できるため、コードの量は少なくなります。自分で作成することも考えられます。
ps:元の画像の値を変更します
従うと、チャイナユニコムブロックのdfsを解決する場合、いくつかの質問を用意しました。私に連絡し、私の4段階の方法を覚えて、自分で解決する必要があります。dfsは自分で解決します。 !!
Luogu :P1451セルの数を見つける-Luogu |コンピュータサイエンス教育の新しいエコロジー(luogu.com.cn)
P1596 [USACO10OCT] Lake Counting S-Luogu |コンピュータサイエンス教育の新しいエコロジー(luogu.com.cn)
質問は難しくありません。自分で入力するだけです。
そして、私たちの完全な配列があります。
完全順列も4段階の方法です。これが、説明する最も古典的な完全順列の問題です。
演習2:配置
トピックの説明
自然数1からnのすべての繰り返されない順列を辞書式順序で出力します。つまり、nの完全な順列であり、生成された数列に繰り返される数が表示されないようにする必要があります。
入力フォーマット
整数nn。
出力フォーマット
1〜n、1行に1つのシーケンスで構成されるすべての一意の番号のシーケンス。
番号ごとに5つのフィールド幅が予約されています。
この質問を見て、多くの人が笑いました。この質問も非常に簡単です。4段階の方法を次に示します。
最初のステップは、方向配列がないため、使用されているかどうかを判断し、値配列を格納することです。
int n,pd[100],a[100];
2番目のステップでは、開始点、タイトル要件を見つけ、dfsを開始します
開始点は当然0であり、直接検索します。
int main()
{
cin>>n;
dfs(0);
return 0;
}
3番目のステップ、dfs関数を記述します
質問の要件はnであるため、最初に質問の要件が満たされているかどうかを判断し、満たされていない場合は出力して返す必要があります。質問にはnが必要であるため、ループは= nで終了し、判断するたびに検索します。数字が役に立つかどうかしかし、使わない場合は保存してから、使用後に戻ったときにシーンをクリーンアップしないと面倒なので、簡単なメモ化検索を作成します。
たとえば、123456を使用している場合、0に戻らないと、次のループで配列がまだ123456であると判断され、データエラーが発生します。
void dfs(int k)
{
if(k==n)
{
for(int i=1;i<=n;i++)
{
printf("%5d",a[i]);
}
printf("\n");
return ;
}
for(int i=1;i<=n;i++)
{
if(!pd[i])
{
pd[i]=1;
a[k+1]=i;
dfs(k+1);
pd[i]=0;
}
}
}
4番目のステップは私たちのものです!ハハハッハッハ
完全なコードは次のとおりです
#include<bits/stdc++.h>
using namespace std;
int n,pd[100],a[100];
void dfs(int k)
{
if(k==n)
{
for(int i=1;i<=n;i++)
{
printf("%5d",a[i]);
}
printf("\n");
return ;
}
for(int i=1;i<=n;i++)
{
if(!pd[i])
{
pd[i]=1;
a[k+1]=i;
dfs(k+1);
pd[i]=0;
}
}
}
int main()
{
cin>>n;
dfs(0);
return 0;
}
ここにはまだ2つの練習問題があります。授業後に自分で練習することができ、1つをマスターすることができます。
Luogu :P1036[NOIP2002普及グループ]選択-Luogu|コンピュータサイエンス教育の新しいエコロジー(luogu.com.cn)
P1157組み合わせ出力-Luogu|コンピュータサイエンス教育の新しいエコロジー(luogu.com.cn)
要約する
今日、私たちはdfsの接続ブロック判断と完全配置判断について学びました。これらは一般的に使用されます。他のバリアントには柔軟性が必要です。書き方4.回答出力の完全配置は次のとおりです。1。回答を保存して判断する2.開始点dfs3。dfs関数の記述方法4.回答出力。これらの2つの方法は実際には似ています。つまり、配置全体に方向性がなく、番号を使用するかどうかの判断が重要になります。dfsを壊す前に、すべての人がそれを覚えておく必要があります。