为对一组数进行 全排列 深度优先搜索
#include<stdio.h> int a[10], book[10],n; void dfs(int step) { int i; if (step == n + 1) //如果已经选择完毕 { //则打印,所选数据 for (i = 1; i <= n; i++) printf("%d ", a[i]); printf("\n"); return; //一定要立即return,不然就会无限制地运行下去 } for (i = 1; i <= n; i++) //选择数 { if (book[i] == 0) //如果这个数是可选的 { a[step] = i; //第step个位置,选择的数i book[i] = 1; //因为上面a[]已选,则把这个i标记为不可选 dfs(step + 1); //选择下一个数 book[i] = 0; //将上面选择的数标记为可选,为下一次选数做准备 } } return; } int main() { scanf("%d", &n); dfs(1); return 0; }深度优先搜索的关键在于解决 “当下该如何做” 。 至于下一步怎么做是和 “当下怎么做” 是一样的
深度优先搜索的基本模型:
void dfs(int step) { 判断边界,并进行一些操作 尝试每一种可能 for (i = 1; i <= n; i++) { 继续下一步 dfs(step + 1); } 返回 }每一种尝试就是一种扩展
[ ][ ][ ]+[ ][ ][ ]=[ ][ ][ ] 找九个互不相同的数,使上式成立
#include<stdio.h> int a[10], book[10], total = 0; void dfs(int step) { int i; if (step == 10) //如果选择完毕 { if (a[1] * 100 + a[2] * 10 + a[3] + a[4] * 100 + a[5] * 10 + a[6] == a[7] * 100 + a[8] * 10 + a[9]) { total++; printf("%d%d%d+%d%d%d+%d%d%d\n", a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9]); } return; //返回之前的一步,最近调用的地方 } //此刻进行第step的选择 for (i = 1; i <= 9; i++) //按照1/2/3···牌的顺序选择 { if (book[i] == 0) //判断i这个数是否还可以被选 { a[step] = i; //选择i book[i] = 1; //表示i已经被选了,不能再选了 dfs(step + 1); //进行下一选择 book[i] = 0; //把刚刚选的标记删除,进行下一次尝试 } } return; } int main() { dfs(1); //对第一个数进行选择 printf("total=%d", total / 2); //因为会有相同的,比如A+B=C和B+A=C相同 return 0; }这个代码中最为关键的还是dfs那一部分。 dfs的思想方法 简直牛逼的不行
主要思想 深度优先,先走一边,然后一直尝试,直到走不通了,就层层回溯,回到最初的位置,然后换方向走
就是先判断这个点是不是想要走到的点 如果不是,则进行走到下一个点 并递归调用dfs,以对下一个点进行分析,操作
//当前点的x,y坐标,以及已经走过的步数step void dfs(int x, int y, int step) { //判断是否找到了 if (x == p && y == q) //如果找到了 { if (step < min) //看是不是比最小的还要小 min = step; return; //这里的返回很重要 } //如果没有找到,则找出下一步可以走的地方 int next[4][2] = { {0,1}, //向右走 {1,0}, //向下走 {0,-1}, //向左走 {-1,0} }; //向上走 for (k = 0; i < 4; k++) { tx = x + next[k][0]; //下一步的坐标 ty = y + next[k][1]; if (tx<1 || tx>n || ty<1 || ty>m) //如果超界直接返回 continue; //判断是否为障碍物,或者已经走过了 if (a[tx][ty] == 0 && book[tx][ty] == 0) { book[tx][ty] = 1; //标记这里已经走过了 dfs(tx, ty, step + 1); //进行下一步的走 book[tx][ty] = 0; //尝试结束,也就是退到了这个点的上一个位置 } } return; }完整代码:
#include<stdio.h> int n, m, p, q, min = 999999999; int a[51][51], book[51][51]; //当前点的x,y坐标,以及已经走过的步数step void dfs(int x, int y, int step) { //判断是否找到了 int tx, ty, k; if (x == p && y == q) //如果找到了 { if (step < min) //看是不是比最小的还要小 min = step; return; //这里的返回很重要 } //如果没有找到,则找出下一步可以走的地方 int next[4][2] = { {0,1}, //向右走 {1,0}, //向下走 {0,-1}, //向左走 {-1,0} }; //向上走 for (k = 0; k < 4; k++) { tx = x + next[k][0]; //下一步的坐标 ty = y + next[k][1]; if (tx<1 || tx>n || ty<1 || ty>m) //如果超界直接返回 continue; //判断是否为障碍物,或者已经走过了 if (a[tx][ty] == 0 && book[tx][ty] == 0) { book[tx][ty] = 1; //标记这里已经走过了 dfs(tx, ty, step + 1); //进行下一步的走 book[tx][ty] = 0; //尝试结束,也就是退到了这个点的上一个位置 } } return; } int main() { int i, j, startx, starty; scanf("%d %d", &n, &m); for (i = 1; i <= n; i++) { for (j = 1; j <= m; j++) { scanf("%d", &a[i][j]); } } scanf("%d%d%d%d", &startx, &starty, &p, &q); book[startx][starty] = 1; dfs(startx, starty, 0); printf("%d", min); return 0; }广度优先搜索 宽度优先搜索
通过一层一层的扩展,来找到想要找到的出路 扩展时每发现一个点就把这个点加入到队列中,直到走到出路为止
核心思想: 利用队列,实现层层扩展
核心代码:
while (head < tail) //当队列不为空的时候循环 { //枚举四个方向 for (k = 0; k < 4; k++) { tx = que[head].x + next[k][0]; //下一个点是从head扩展出来的 ty = que[head].y + next[k][1]; //判断是否越界 if (tx<1 || tx>n || ty<1 || ty>m) continue; //判断是否是障碍物或者已经走过了 if (a[tx][ty] == 0 && book[tx][ty] == 0) { //把这个点标记为已走 //BFS中每个点只入队一次,因此,book一旦标记,就不再还原 book[tx][ty] = 1; //将走到的点插入到队列中 que[tail].x = tx; que[tail].y = ty; que[tail].f = head; //因为这个点是从head扩展出来的,因此父亲就是head que[tail].s = que[head].s + 1; tail++; } if (tx == p && ty == q)//如果到达终点了,直接跳出 { flag = 1; break; } } if (flag == 1) break; head++; //当一个点的扩展结束了,head++才能对后面的点再扩展 } #include<stdio.h> struct node { int x; //横坐标 int y; //纵坐标 int f; //父亲在队列中的编号,这个是为输出路径而准备的 int s; //步数 }; int main() { int i, j, k, n, m; node que[2501]; //因为地图大小不超过50*50,因此队列扩展不会超过2500 int head, tail; //队首,队尾 int a[51][51] = { 0 }; //地图 int book[51][51] = { 0 }; //book的作用是记录哪些点已经在队列中,防止一个点被重复扩展,并全部初始化为0 int next[4][2] = { {0,1}, //向右走 {1,0}, //向下走 {0,-1}, //向左走 {-1,0} }; //向上走 int startx, starty; //开始的位置的横纵坐标 int p, q; int tx, ty; //下一个点的横纵坐标 int flag; scanf("%d%d", &n, &m); //输入地图 for (i = 1; i <= n; i++) { for (j = 1; j <= m; j++) { scanf("%d", &a[i][j]); } } //输入开始的横纵坐标 scanf("%d%d%d%d", &startx, &starty, &p, &q); //最开始的时候需要进行队列初始化,即将队列设置为空 head = 1; tail = 1; //在队列中设置开始时位置坐标以及路径长度 que[tail].x = startx; que[tail].y = starty; que[tail].s = 0; que[tail].f = 0; tail++; book[startx][starty] = 1; flag = 0; //用来记录是否到达目标点,0表示未到,1表示到达 while (head < tail) //当队列不为空的时候循环 { //枚举四个方向 for (k = 0; k < 4; k++) { tx = que[head].x + next[k][0]; //下一个点是从head扩展出来的 ty = que[head].y + next[k][1]; //判断是否越界 if (tx<1 || tx>n || ty<1 || ty>m) continue; //判断是否是障碍物或者已经走过了 if (a[tx][ty] == 0 && book[tx][ty] == 0) { //把这个点标记为已走 //BFS中每个点只入队一次,因此,book一旦标记,就不再还原 book[tx][ty] = 1; //将走到的点插入到队列中 que[tail].x = tx; que[tail].y = ty; que[tail].f = head; //因为这个点是从head扩展出来的,因此父亲就是head que[tail].s = que[head].s + 1; tail++; } if (tx == p && ty == q)//如果到达终点了,直接跳出 { flag = 1; break; } } if (flag == 1) break; head++; //当一个点的扩展结束了,head++才能对后面的点再扩展 } //打印队列中末尾最后一个点的步数(这时候就是到达终点的点) printf("%d", que[tail - 1].s); //tail指向的队列中最后一个元素的下一个位置。因此需要减1 return 0; }BFS大致的模板
while (head < tail) { for (k = 0; k < 4; k++) { tx = que[head].x + next[k][0]; ty = que[head].y + next[k][1]; if (tx<1 || tx>n || ty<1 || ty>m) continue; if (a[tx][ty] == '.' && book[tx][ty] == 0) { book[tx][ty] = 1; que[tail].x = tx; que[tail].y = ty; //在这里进行你先要的操作 tail++; } } head++; }DFS 这个与为了找到终点是不一样的 这个是为遍历图中的每一个点 因此,需要的将所有的点都做一个标记 而且不能清除这个标记,不然没有办法知道,是否遍历完所有的点
void dfs(int x, int y) { //操作 for (k = 0; k < 4; k++) { tx = x + next[k][0]; ty = y + next[k][1]; if (tx<0 || tx>n - 1 || ty<0 || ty>m - 1) continue; if (a[tx][ty] == '.' && book[tx][ty] == 0) { book[tx][ty] = 1; dfs(tx, ty); //book[tx][ty] = 0; 因为是遍历图中的每一个点,与找到终点不一样,所以这个步骤删除 } } return; }上述代码就是着色法的实现: 着色法:以某个点为源点对其临近的点进行着色
求一个图中有多少个独立子图 对图中每一个点进行一次DFS,并对其进行不同的着色
Floodfill漫水填充法 Floodfill算法在计算机图形学中有着非常广泛的应用。比如图像切割,物体识别 例如:Windows下画图软件的油漆桶就是基于这个算法
输出通路,需要一个栈 每次进行尝试的时候入栈 如果尝试不成功,则将这个不成功的出栈
最后栈中保留的,就是正确的路线 当成功的时候,就输出栈中路线就可以了
这个我觉得收获最大是 形成路线 至于水管的选择还是dfs 不过,在选择水管的时候,可以选择进行多个dfs,给我带来了启发
#include<stdio.h> int a[51][51]; int book[51][51]; int n, m, flag = 0; struct note { int x; int y; }s[100]; //形成一个栈 int top = 0; void dfs(int x, int y, int front)//x,y代表位置,front代表进水口方向 { int i; //判断是否到达终点 //判断终点一定要在判断越界前面 if (x == n && y == m + 1) { flag = 1; for (i = 1; i <= top; i++) printf("(%d,%d)", s[i].x, s[i].y); return; } if (x < 1 || x>n || y<1 || y>m) { return; } if (book[x][y] == 1) //判断这个管道是不是已经使用过 return; book[x][y] = 1;//如果没有使用过,标记的当前管道 //将当前尝试入栈 top++; s[top].x = x; s[top].y = y; //枚举当前管道的每一种摆放方式。根据进水口的方向来决定管道方向 //当前管道是直道 if (a[x][y] >= 5 && a[x][y] <= 6) { //front代表进水口方向 if (front == 1) { dfs(x, y + 1, 1); } if (front == 2) { dfs(x + 1, y, 2); } if (front == 3) { dfs(x, y - 1, 3); } if (front == 4) { dfs(x - 1, y, 4); } } //当前是弯道的时候 //每个弯道有两种状态,因此对应于需要两个dfs if (a[x][y] >= 1 && a[x][y] <= 4) { if (front == 1) //如果进水口是左边 { dfs(x + 1, y, 2); //那么我下一个位置进水口只有上面和下面 dfs(x - 1, y, 4); } if (front == 2) { dfs(x, y + 1, 1); dfs(x, y - 1, 3); } if (front == 3) { dfs(x - 1, y, 4); dfs(x + 1, y, 2); } if (front == 4) { dfs(x, y + 1, 1); dfs(x, y - 1, 3); } } book[x][y] = 0; //取消标记 这是因为,这个尝试失败了,要换一种了 top--; //将当前尝试出栈 return; } int main() { int i, j, num = 0; scanf("%d%d", &n, &m); for (i = 1; i <= n; i++) { for (j = 1; j <= m; j++) { scanf("%d", &a[i][j]); } } dfs(1, 1, 1); if (flag == 0) { printf("impossible\n"); } else printf("找到铺设方案\n"); }