1.DFS
非连通无向图
【模板题】一般都是给矩阵,两种不同字符表示不同含义。
比如下图,对连通的(8方向可走)的@字符组成的块进行搜索。
问题:连通的有几块?
#include<cstdio>
#include<cstring>
const int maxn = 100 + 5;
char pic[maxn][maxn];
int m, n, idx[maxn][maxn]; // visited数组
void dfs(int r, int c, int id) {
if(r < 0 || r >= m || c < 0 || c >= n) return; // 走到边界
if(idx[r][c] != 0 || pic[r][c] != '@') return; // 已被访问或进入非法区域
idx[r][c] = id; // 标记已访问
// 走8个方向 会有0,0的位移 但是无所谓
for(int dr = -1; dr <= 1; dr++)
for(int dc = -1; dc <= 1; dc++)
if(dr != 0 || dc != 0) dfs(r+dr, c+dc, id);
}
int main() {
while(scanf("%d%d", &m, &n) == 2 && m && n) {
for(int i = 0; i < m; i++) scanf("%s", pic[i]);
memset(idx, 0, sizeof(idx));
int cnt = 0;
// 遍历图 找未被访问的@
for(int i = 0; i < m; i++)
for(int j = 0; j < n; j++)
if(idx[i][j] == 0 && pic[i][j] == '@') dfs(i, j, ++cnt);
// 只要连通就一直找,不连通时退出dfs(),所以每在main里调用一次,就是找到了几个连通图
printf("%d\n", cnt);
}
return 0;
}
输入
Each test case contains a single integer N (N < 1000), the length and also the width of Hoenn. Next N lines will be the result of the battle. "o" means a landmass and "a" means a sea. The input is terminated when N = 0.
输出
For each test case, you must print the size of the biggest connnected seas filled by Kyogre.
输入样例 1
5
aaooo
oooao
oaaaa
aaaaa
ooooo
0
输出样例 1
10
问题:4方向(上下左右)可走,连通的a块最大字符个数是多少。
#include <bits/stdc++.h>
using namespace std;
char area[1005][1005];
int visited[1005][1005];
int n;
char tmp[1005];
void dfs(int i, int j, int *cnt){
if( i<0 || i>=n || j<0 || j>=n )
return;
if( visited[i][j]==1 || area[i][j]!='a' )
return;
visited[i][j] = 1;
(*cnt)++;
dfs(i-1, j, cnt);
dfs(i, j+1, cnt);
dfs(i+1, j, cnt);
dfs(i, j-1, cnt);
}
int main(){
while( scanf("%d", &n) && n!=0 ){
memset(area, 0, sizeof(area));
memset(visited, 0, sizeof(visited));
for(int i=0; i<n; i++){
scanf("%s", tmp);
for(int j=0; j<n; j++){
area[i][j] = tmp[j];
}
}
int cnt=0;
int max = 0;
for(int i=0; i<n; i++){
for(int j=0; j<n; j++){
if( area[i][j]=='a' && visited[i][j]==0 ){
cnt = 0;
dfs(i, j, &cnt);
}
if( cnt > max ){
max = cnt;
}
}
}
printf("%d\n", max);
}
}
连通有向图,用邻接矩阵表示(1到2,即第一行第二列为1)
拓展:拓扑排序 topological sorting
输出有向图1到n的一条路径 DFS(因为只找一条)
输入
There are several groups of input, and the first row in each group includes two numbers N (1<=N<=500), and M, where N represents the number of teams and M means there are M lines of input data following the first row of each group. In the next M rows, each row also includes two integers P1 and P2, meaning that team P1 wins against team P2.
输出
Give a ranking that meets the requirements. Output with a space between two team numbers and no space after the last one.
输入样例 1
4 3
1 2
2 3
4 3
输出样例 1
1 2 4 3
提示
topological sorting
Other notes: The ranking may not be unique, and you need to output the smallest ranking in numerical order, that is, the team with a smaller number is required to output first. The input data are guaranteed to be correct, which means the input data ensure that there will be a ranking which meets the requirements.(同条件下,字典序小的在前面)
#include<iostream>
using namespace std;
int G[501][501];
int visited[501];// 访问优先级数组 -1表示已被访问 >=0 表示该顶点入度个数
// visited[]==0 表示入度为0
int ans[501]; // 1到n的路径 即拓扑排序结果
int n,m;
void topo(){
int index = 1;
for (int k = 0; k < n; k++){
for (int i = 1; i <= n; i++){ // 遍历顶点 找入度为0的点
// 从小到大遍历符合字典序
if (visited[i] == 0){
ans[index] = i;
for (int j = 1; j <= n; j++){ // 找与i邻接的点
if (G[i][j] == 1){
visited[j]--; // i已被访问 删除其出度的边
// 所以j的入度减少
}
}
visited[i] = -1;
index++;
break;
}
}
}
}
int main(){
while (cin >> n >> m){
int a, b;
for (int i = 1; i <= n; i++){
for (int j = 1; j <= n; j++){
G[i][j] = 0;
}
visited[i] = 0;
}
for (int i = 0; i < m; i++){
cin >> a >> b; // a到b有边
if (G[a][b] == 0) // 不是重边
visited[b]++;
G[a][b] = 1; // (a,b)=1
}// 如果一个顶点没有被指向过,那它一定是最大的
topo();
for (int i = 1; i < n; i++){
cout << ans[i] << " ";
}
cout <<ans[n]<< endl;
}
return 0;
}
拓展:n皇后问题 n queens puzzle
n皇后问题是将n个皇后放置在n*n的棋盘上,皇后彼此之间不能相互攻击(任意两个皇后不能位于同一行,同一列,同一斜线)。问有多少种放法。
本质是n个数全排列问题。从第0行开始,对一行,枚举棋子放在每一列的情况。
回溯是一种优化的递归,它相当于深度优先,当某一个情况不满足要求时立即“剪枝” ——即退出当前栈,回到上一个递归的栈里。
// backtracking 递归完成回溯
//回溯通常比暴力枚举所有完整的候选项要快得多,因为它可以通过单个测试消除许多候选项。
#include <bits/stdc++.h>
using namespace std;
int C[50], tot = 0, n = 8, nc = 0;
// 一行仅一个皇后 可以将n*n的二维棋盘用一维数组表示
// C[i] 表示第i行的皇后放在第C[i]列
void search(int cur) {
int i, j;
nc++; // search函数调用次数
if(cur == n) { // recursive boundary
// 递归到最后一行 找到了符合要求的一个摆法 计数+1
tot++;
} else for(i = 0; i < n; i++) { //在第cur行 查找皇后放在该行的每一列的情况
int ok = 1;
C[cur] = i; // (cur,i) // 第cur行的皇后放在第i列
for(j = 0; j < cur; j++)
if(C[cur] == C[j] || cur-C[cur] == j-C[j] || cur+C[cur] == j+C[j]) {
// 同列 || 对角线
ok = 0;
break;
}
if(ok) search(cur+1);
}
}
int main() {
while( cin>>n ){
tot = 0;
search(0);
printf("%d\n", tot);
}
return 0;
}
一点到另一点的路径条数
DFS,找到终点总数加一。定点访问完了要复原。
#include <bits/stdc++.h>
using namespace std;
int n,m;
char maze[7][7];
int visited[7][7];
char tmp[7];
int cnt;
void dfs(int row, int col){
if( row<0 || row>=n || col<0 || col >=m ){
return;
}
if( visited[row][col]==1 || maze[row][col]=='F' ){
return;
}
// 当前单元可走
visited[row][col] = 1;
if( maze[row][col]=='E' ){
cnt ++;
visited[row][col] = 0;
return;
}
dfs(row-1, col);
dfs(row, col+1);
dfs(row+1, col);
dfs(row, col-1);
visited[row][col] = 0;
}
int main(){
while(scanf("%d %d", &n, &m)!=EOF ){
int Bi = 0;
int Bj = 0;
for(int i=0; i<n; i++){
scanf("%s", tmp);
for(int j=0; j<m; j++){
maze[i][j] = tmp[j];
if( tmp[j]=='B' ){
Bi = i;
Bj = j;
}
}
}
cnt = 0;
dfs(Bi, Bj);
printf("%d\n", cnt);
}
}
最短路径 边代价相等
连通 边代价不相等——需要用到迪杰斯特拉/旅行商(贪心经典问题)算法
每个顶点到另一个顶点代价相等——DFS/BFS/A*启发
BFS 最短路
#include <bits/stdc++.h>
using namespace std;
struct Node{
int row,col;
char c;
int Mto; // M点到该点的最短距离
int isVisited;
};
Node land[1002][1002];
Node *M;
Node *D;
char str[1002];
int N;
int newX[4] = {1, -1, 0, 0};
int newY[4] = {0, 0, 1, -1};
int BFS(){
queue<Node*> openlist;
openlist.push(M);
M->Mto = 0;
M->isVisited = 1;
while(openlist.size()!=0){
Node* curr = openlist.front();
openlist.pop();
if( curr==D ){
return curr->Mto;
}
int i, j;
// 以curr为中心 分别向上 右 下 左四个方向寻找
for(int k=0; k<4; k++){
i = curr->row + newX[k];
j = curr->col + newY[k];
if( i<0 || i>=N || j<0 || j>=N )
continue;
if( land[i][j].c=='#' || land[i][j].isVisited==1 )
continue;
Node *p = &land[i][j];
openlist.push(p);
p->Mto = curr->Mto + 1;
p->isVisited = 1;
}
}
return -1;
}
int main(){
while(scanf("%d", &N)!=EOF && N!=0){
// 初始化
for(int i=0; i<N; i++){
scanf("%s", str);
for(int j=0; j<N; j++){
land[i][j].c = str[j];
if( land[i][j].c == 'M' ){
M = &land[i][j];
}
else if( land[i][j].c == 'D'){
D = &land[i][j];
}
land[i][j].row = i;
land[i][j].col = j;
land[i][j].Mto = 0;
land[i][j].isVisited = 0;
}
}
int res = BFS();
if (res == -1)
printf("%d\n", res);
else
printf("%d\n", res);
}
return 0;
}
DFS最短路
#include<iostream>
using namespace std;
int n;
int next_x, next_y;
int minn;
string graph[1001];
int d[4][2] = { {-1,0},{0,1},{1,0},{0,-1} };
int g[1001][1001];
void dfs(int x, int y, int step, int end_x, int end_y) //step记录当前已经走过的步数
{
if (x == end_x && y == end_y)
{
if (step < minn)
minn = step;
return;
}
//找下一点坐标:水平垂直四种走法
for (int i = 0; i < 4; ++i)
{
next_x = x + d[i][0];
next_y = y + d[i][1];
//判断越界
if (next_x < 0 || next_y < 0 || next_x >= n || next_y >= n)
continue;
//判断是否可走
/*if (graph[next_x][next_y] == 'D')
break;*/
if (graph[next_x][next_y] == '@' && g[next_x][next_y] == 0)
{
g[next_x][next_y] = 1;
dfs(next_x, next_y, step + 1, end_x, end_y);
g[next_x][next_y] = 0; //回溯
}
}
return;
}
int main()
{
while (cin >> n && n != 0)
{
//step = 0;
minn = 9999;
int end_x, end_y;
int start_x, start_y;
int flag = 0;
//memset(g,0, 1001*1001*sizeof(int));
for(int i = 0; i < n; ++i)
for(int j = 0; j < n; ++j)
g[i][j] = 0;
for (int i = 0; i < n; ++i)
{
cin >> graph[i];
for (int j = 0; j < n; ++j)
{
//记录起点终点
if (graph[i][j] == 'M')
{
start_x = i;
start_y = j;
}
if (graph[i][j] == 'D')
{
end_x = i;
end_y = j;
}
}
}
graph[start_x][start_y] = '@';
graph[end_x][end_y] = '@';
g[start_x][start_y] = 1;
dfs(start_x, start_y, 0, end_x, end_y);
if (minn == 9999)
cout << -1 << endl;
else
cout << minn << endl;
}
return 0;
}
A*(OJ上一直是TLE的,但是样例都能过)
#include <bits/stdc++.h>
using namespace std;
struct Node{
int row,col;
char c;
int G; // 当前路径代价
int H; // 预估路径代价 曼哈顿距离
int F;
};
Node land[1001][1001];
char str[1001];
int N;
Node* M;
Node* D;
int len;
int isVisited=0;
bool A_star(){
vector<Node*> openlist; //可寻路的顶点
vector<Node*> closelist; //已被寻路的顶点
M->H = abs(M->row - D->row) + abs(M->col - D->col);
openlist.push_back(M);
M->G = 0;
M->F = M->H + M->G;
while( openlist.size()!=0 ){
Node* curr = NULL;
// 本质是BFS 但由于包含代价可以减少枚举次数
// 从可被寻路的顶点中选择F最小的
for(vector<Node*>::iterator p=openlist.begin(); p!=openlist.end(); p++){
if( curr==NULL ){
curr = *p;
}
else if((*p)->F < curr->F){
curr = *p;
}
}
// cout << "curr->row: " << curr->row << endl;
// cout << "curr->col: " << curr->col << endl;
if( curr==D ){
return true;
}
int i, j;
// 以curr为中心 分别向上 右 下 左四个方向寻找
if( (curr->row)-1 >= 0 ) { // 上可走
i = curr->row-1;
j = curr->col;
Node *p = &land[i][j];
if(land[i][j].c!='#'){
isVisited = 0;
for(vector<Node*>::iterator itr=closelist.begin(); itr!=closelist.end(); itr++){
if(*itr==p ){
isVisited = 1;
break;
}
}
if(isVisited==0 ) { //不在closelist的顶点
p->H = abs(p->row - D->row) + abs(p->col - p->col);
if( curr->G + 1 < p->G ){
p->G = curr->G + 1;
p->F = p->G + p->H;
if(find(openlist.begin(), openlist.end(), p)==openlist.end()){
openlist.push_back(p);
}
}
}
}
}
if( curr->col+1 < N ){ // 右可走
i = curr->row;
j = curr->col + 1;
Node *p = &land[i][j];
if(land[i][j].c!='#'){
isVisited = 0;
for(vector<Node*>::iterator itr=closelist.begin(); itr!=closelist.end(); itr++){
if(*itr==p ){
isVisited = 1;
break;
}
}
if(isVisited == 0){
p->H = abs(p->row - D->row) + abs(p->col - p->col);
if( curr->G + 1 < p->G ){
p->G = curr->G + 1;
p->F = p->G + p->H;
if(find(openlist.begin(), openlist.end(), p)==openlist.end()){
openlist.push_back(p);
}
}
}
}
}
if( curr->row+1<N ){ // 下可走
i = curr->row+1;
j = curr->col;
Node *p = &land[i][j];
if(land[i][j].c!='#'){
isVisited = 0;
for(vector<Node*>::iterator itr=closelist.begin(); itr!=closelist.end(); itr++){
if(*itr==p ){
isVisited = 1;
break;
}
}
if(isVisited==0 ){
p->H = abs(p->row - D->row) + abs(p->col - p->col);
if( curr->G + 1 < p->G ){
p->G = curr->G + 1;
p->F = p->G + p->H;
if(find(openlist.begin(), openlist.end(), p)==openlist.end()){
openlist.push_back(p);
}
}
}
}
}
if( curr->col-1>=0 ){ // 左可走
i = curr->row;
j = curr->col-1;
Node *p = &land[i][j];
if(land[i][j].c!='#'){
isVisited = 0;
for(vector<Node*>::iterator itr=closelist.begin(); itr!=closelist.end(); itr++){
if(*itr==p ){
isVisited = 1;
break;
}
}
if(isVisited == 0 ){
p->H = abs(p->row - D->row) + abs(p->col - p->col);
if( curr->G + 1 < p->G ){
p->G = curr->G + 1;
p->F = p->G + p->H;
if(find(openlist.begin(), openlist.end(), p)==openlist.end()){
openlist.push_back(p);
}
}
}
}
}
// 移除可访问列表中的curr
openlist.erase(find(openlist.begin(), openlist.end(), curr));
// 将curr加入已访问列表
closelist.push_back(curr);
}
return false;
}
int main(){
while(scanf("%d", &N)!=EOF && N!=0 ){
len = 0;
for(int i=0; i<N; i++){
scanf("%s", str);
for(int j=0; j<N; j++){
land[i][j].c = str[j];
if( land[i][j].c == 'M' ){
M = &land[i][j];
}
else if( land[i][j].c == 'D'){
D = &land[i][j];
}
land[i][j].row = i;
land[i][j].col = j;
land[i][j].G = 5000;
land[i][j].F = 5000;
}
}
// 初始化曼哈顿距离H 当前路径代价G和总代价F初始化为无穷大
// for(int i=0; i<N; i++){
// for(int j=0; j<N; j++){
// land[i][j].H = abs(i - D->row) + abs(j - D->col);
// land[i][j].G = numeric_limits<int>::max();
// land[i][j].F = numeric_limits<int>::max();
// }
// }
// A*查找
if(A_star()){
len = D->G;
printf("%d\n", len);
}
else{
len = -1;
printf("%d\n", len);
}
}
return 0;
}
2. BFS
拓展:8皇后问题
在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上。问有多少种摆法。
从第一行变化到第二行的状态,最少需要变换多少步,并打印每一步变换的过程。
输入样例 2
2 6 4 1 3 7 0 5 8
8 1 5 7 3 6 4 0 2
// 8-puzzle encoding
#include<cstdio>
#include<cstring>
//#include<set>
//using namespace std;
typedef int State[9]; // define the state type
const int MAXSTATE = 1000000;
State st[MAXSTATE], goal; // the array of all states st[MAXSTATE]
int dist[MAXSTATE]; // the distance array
int prevState[MAXSTATE]; // 对于prevstate[i]:存储第i个栈的前一个状态(存的是下标)
int path[MAXSTATE];
// cantor expansion 康托展开
int vis[362880], fact[9]; // 0~8 permutations -> 0~362879 (9!=362880)
void init_lookup_table() {
fact[0] = 1;
for(int i = 1; i < 9; i++) fact[i] = fact[i-1] * i;
}
int try_to_insert(int s) {
int code = 0; // map st[s] to code
for(int i = 0; i < 9; i++) {
int cnt = 0;
for(int j = i+1; j < 9; j++) if(st[s][j] < st[s][i]) cnt++;
code += fact[8-i] * cnt;
}
if(vis[code]) return 0;
return vis[code] = 1;
}
const int dx[] = {-1, 1, 0, 0}; // up down left right
const int dy[] = {0, 0, -1, 1};
int bfs() {
init_lookup_table();
int front = 1, rear = 2; // start from 1 (the index 0 is not used)
prevState[1] = 1;
while(front < rear) {
State& s = st[front]; // use &s to simplify the code
if(memcmp(goal, s, sizeof(s)) == 0) return front; // find the goal state, return; memcmp() is in cstring
int z;
for(z = 0; z < 9; z++) if(!s[z]) break; // find the empty position, that is, 0
int x = z/3, y = z%3; // row && column (0~2)
for(int d = 0; d < 4; d++) {
int newx = x + dx[d];
int newy = y + dy[d];
int newz = newx * 3 + newy; // the new 0 position
if(newx >= 0 && newx < 3 && newy >= 0 && newy < 3) { // if the move is possible
prevState[rear] = front;
State& t = st[rear];
memcpy(&t, &s, sizeof(s)); // expand a new state; memcpy s->t
t[newz] = s[z]; // move 0
t[z] = s[newz]; // move a number
dist[rear] = dist[front] + 1; // compute the new state's distance
if(try_to_insert(rear)) rear++; // the insert succeeds, rear++
}
}
front++; // after expansion, front++
}
return 0; // failed
}
int main() {
for(int i = 0; i < 9; i++){
scanf("%d", &st[1][i]);
}
for(int i = 0; i < 9; i++)
scanf("%d", &goal[i]);
int ans = bfs();
if(ans > 0) printf("%d\n", dist[ans]);
else printf("-1\n");
path[0] = ans;
int preIndex = prevState[ans];
for(int i=1; i<dist[ans]+1; i++){
path[i] = preIndex;
preIndex = prevState[preIndex];
}
for(int i=dist[ans]; i>=0; i--){
//printf("%d ", path[i]);
for(int k=0; k<8; k++){
printf("%d ", st[path[i]][k]);
}
printf("%d\n", st[path[i]][8]);
}
return 0;
}