概述
一、深度优先搜索(DFS)
1、概念
DFS(Depth First Search)是从初始结点开始扩展,扩展顺序总是先扩展最新产生的结点。这就使得搜索沿着状态空间某条单一的路径进行下去,直到最后的结点不能产生新结点或者找到目标结点为止。当搜索到不能产生新的结点的时候,就沿着结点产生顺序的反方向寻找可以产生新结点的结点,并扩展它,形成另一条搜索路径。
2、剪枝策略
剪枝就是通过一些判断,剪掉搜索树上不必要的子树。在采用DFS算法搜索时,有时候我们会发现某个结点对应的子树的状态都不是我们要的结果,这时候我们没必要对这个分支进行搜索,砍掉这个子树,就是剪枝。
在DFS搜索算法中,剪枝策略就是寻找过滤条件,提前减少不必要的搜索路径。应用剪枝策略的核心问题是设计剪枝判断方法,即确定哪些枝条应当舍弃,哪些枝条应当保留的方法。
有时没有剪枝或剪枝不全:答案错误,压爆栈(特别是模拟类、地图搜索问题)等
剪枝策略按照其判断思路可大致分成两类:可行性剪枝及最优性剪枝。
(1)可行性剪枝
可行性剪枝就是把能够想到的不可能出现的情况给它剪掉 。该方法判断继续搜索能否得出答案,如果不能直接回溯。
(2)最优性剪枝
最优性剪枝,又称为上下界剪枝,是一种重要的剪枝策略。它记录当前得到的最优值,如果当前结点已经无法产生比当前最优解更优的解时,可以提前回溯。
对于求最优解**(最小值)**的一类问题,通常可以用最优性剪枝。比如在求迷宫最短路的时候,如果发现当前的步数已经超过了当前最优解,那从当前状态开始的搜索都是多余的,因为这样搜索下去永远都搜不到更优的解。通过这样的剪枝,可以省去大量冗余的计算。
(一)非回溯
1、概念
DFS中一般用于操作个数不确定且终止值不连续的情况。
2、解题思路
剪枝:剪去已访问及不符合题意的点,直接return
(题意给的特殊值情况)
终止条件,否则拓展
public static void dfs(int x,int y,int mou,int times) {
//1、剪枝:剪去已访问及不符合题意的点,直接return
if(mou==0 || times>m*n || times>minans || x<=0
|| x>n || y<=0 || y>m || a[x][y]==0) return;
//2、处理,v数组或根据题意给的特殊值情况
if(a[x][y]==4) mou=6;
//v[] = 1;
//3、终止条件
if(a[x][y]==3) {
minans=Math.min(times, minans);
}//否则拓展
else {
dfs(x + 1,y,mou - 1,times + 1);
dfs(x - 1,y,mou - 1,times + 1);
dfs(x,y + 1,mou - 1,times + 1);
dfs(x,y - 1,mou - 1,times + 1);
}
//4、回溯
//v[] = 0;
}
3、地图搜索问题??
解题思路
a.剪枝:剪去已访问及不符合题意的点,直接return(先做不符合的希望后续统一更改)
b.接下来符合条件,每个点可沿题目给定的几个方向拓展
技巧
a.适用v数组记录已访问的点,防止重复计算要统计的点及爆栈。一半要但非必需,如(1)(2)用
(3)(4)不用:(3)使用了数组值进行区分,(4)有条件剪枝
b.
3.1 简单遍历
a.黑色方块问题
https://www.cnblogs.com/cs-whut/p/11147213.html
void dfs(int x, int y)
{
if(x >=0 && x<h && y>=0 && y<w && map[x][y]!='#' && visit[x][y]==0)
{
visit[x][y] = 1;
sum++;
dfs(x+1,y); // 递归访问四个方向的砖块
dfs(x-1,y);
dfs(x,y+1);
dfs(x,y-1);
}
}
3.2 连通块
a.求细胞数量
为什么两次判断?主函数:起点不能重且符合条件,dfs:拓展点不能重且符合条件
import java.util.*;
public class Main
{
static int m;
static int n;
static int[][] q;
static int[][] v;
public static void main(String args[]) {
Scanner in = new Scanner(System.in);
n = in.nextInt();
m = in.nextInt();
int sum = 0;
q = new int[n][m];
v = new int[n][m];
in.nextLine();
for(int i = 0;i < n;i++) {
String s = in.nextLine();
char[] c = s.toCharArray();
for(int j = 0;j < m;j++) {
q[i][j] = c[j] - '0';
v[i][j] = 0;
}
}
for(int i = 0;i < n;i++) {
for(int j = 0;j < m;j++) {
if(q[i][j] != 0 && v[i][j] == 0) {
sum++;
dfs(i,j);
}
}
}
System.out.println(sum);
}
public static void dfs(int x,int y) {
if(x >= 0 && x <= n - 1 && y >= 0 && y <= m - 1 && v[x][y] == 0 && q[x][y] != 0) {
v[x][y] = 1;
dfs(x + 1,y);
dfs(x - 1,y);
dfs(x,y + 1);
dfs(x,y - 1);
}
}
}
b.Lake Counting S
//八个方向
public static void dfs(int x,int y) {
if(x >= 0 && x <= m - 1 && y >= 0 && y <= n - 1 && q[x][y] == 'W' && v[x][y] == 0) {
v[x][y] = 1;
dfs(x + 1,y);
dfs(x - 1,y);
dfs(x,y + 1);
dfs(x,y - 1);
dfs(x - 1,y - 1);
dfs(x - 1,y + 1);
dfs(x + 1,y - 1);
dfs(x + 1,y + 1);
}
}
3.3 染色问题
a.拯救oibh总部
为什么要外搜一圈呢??
import java.util.*;
public class Main
{
static int n;
static int m;
static int[][] q = new int[500][500];
public static void main(String args[]) {
Scanner in = new Scanner(System.in);
n = in.nextInt();
m = in.nextInt();
int sum = 0;
for(int y = 1;y <= n;y++) {
String s = in.next();
char[] c = s.toCharArray();
for(int x = 1;x <= m;x++) {
if(c[x - 1] == '0') {
q[x][y] = 0;
}else {
q[x][y] = 1;
}
}
}
//染色,从外圈开始?
dfs(0,0);
//统计染色后仍为0的数量,即保护到的数量
for(int x = 1;x <= m;x++) {
for(int y = 1;y <= n;y++) {
if(q[x][y] == 0) {
sum++;
}
}
}
System.out.println(sum);
}
public static void dfs(int x,int y) {
//外搜一圈??
if(x >= 0 && x <= m+1 && y >= 0 && y <= n+1 && q[x][y] == 0) {
//将水淹没的地方标为2
q[x][y] = 2;
dfs(x + 1,y);
dfs(x - 1,y);
dfs(x,y + 1);
dfs(x,y - 1);
}
}
}
b.填涂颜色
import java.util.*;
public class Main
{
static int n;
static int[][] q = new int[901][901];
public static void main(String args[]) {
Scanner in = new Scanner(System.in);
n = in.nextInt();
for(int x = 1;x <= n;x++) {
for(int y = 1;y <= n;y++) {
q[x][y] = in.nextInt();
}
}
dfs(0,0);
for(int x = 1;x <= n;x++) {
for(int y = 1;y <= n;y++) {
if(q[x][y] == 0) {
q[x][y] = 2;
}else if(q[x][y] == 3) {
q[x][y] = 0;
}
System.out.print(q[x][y]);
//注意空格
if(y < n) {
System.out.print(" ");
}
}
System.out.println("");
}
}
public static void dfs(int x,int y) {
if(x >= 0 && x <= n+1 && y >= 0 && y <= n+1 && q[x][y] == 0) {
q[x][y] = 3;
dfs(x + 1,y);
dfs(x - 1,y);
dfs(x,y + 1);
dfs(x,y - 1);
}
}
}
3.4 模拟问题
回家
缺少剪枝:会爆栈!
可行性剪枝:times>m*n ,不可能大于mn之积
最优性剪枝:times>minans,要求的是最小
import java.util.Scanner;
public class Main {
static int n=0,m=0,tx=0,ty=0,lx=0,ly=0,minans=1<<30; //minans 1乘以2的30次方
static int[][] a=new int[11][11];
public static void main(String[] args) {
Scanner in=new Scanner(System.in);
n=in.nextInt();
m=in.nextInt();
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
a[i][j]=in.nextInt();
if(a[i][j]==2) { //记录开始点
tx=i;
ty=j;
}
}
}
dfs(tx,ty,6,0);
//如果此时 minans 与初值相等,则没有找到可行路径
if(minans!=1<<30) {
System.out.println(minans);
}else {
System.out.println("-1");
}
}
public static void dfs(int x,int y,int mou,int times) {
//如果血量为0或者超出步数上界或者此时的步数已经超过了答案
if(mou==0 || times>m*n || times>minans || x<=0 || x>n || y<=0 || y>m || a[x][y]==0) return;
if(a[x][y]==4) mou=6;
if(a[x][y]==3) {
//如果到达终点
minans=Math.min(times, minans);
}else {
dfs(x + 1,y,mou - 1,times + 1);
dfs(x - 1,y,mou - 1,times + 1);
dfs(x,y + 1,mou - 1,times + 1);
dfs(x,y - 1,mou - 1,times + 1);
}
}
}
4、其他模拟问题
对于此类问题,理论上来说可以用回溯做,但若条件比较复杂,则最好还是用非回溯
一般还会搭配其他思想,如(2)中还使用了贪心思想,故以后遇到再做练习
(1)奇怪的电梯
https://www.luogu.com.cn/problem/P1135
import java.util.Scanner;
public class Main {
static int n;
static int a,b;
static int[] rule = new int[300];
static int[] v = new int[300];
static int min = Integer.MAX_VALUE;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
n = in.nextInt();
a = in.nextInt();
b = in.nextInt();
for(int i = 1;i <= n;i++) {
rule[i] = in.nextInt();
v[i] = 0;
}
dfs(a,0);
if(min == Integer.MAX_VALUE) {
System.out.println(-1);
}else {
System.out.println(min);
}
}
public static void dfs(int t,int num) {
if(t < 1 || t > n || num > min || v[t] == 1) return ;
v[t] = 1;
if(t == b) {
min = num;
}else {
dfs(t + rule[t],num + 1);
dfs(t - rule[t],num + 1);
}
v[t] = 0;
}
}
(2)Sum It Up?
复杂在于:要去重
https://www.itdaan.com/blog/2018/09/12/ee3c020f4a35870f29c39cba21319555.html
思路:
(1)可行性剪枝: 因为题目中所有待选数都是正数,因此一旦发现当前的和值sum都已经大于t了,那么之后不管怎么选,和值都不可能回到t,所有当sum > t时,可以直接剪掉。
(2)从大到小:因给的数从大到小,故只需从前往后遍历即可
(3)注意防止结果式子重复?
i==k||a[i]!=a[i-1]
#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;
int n,t,a[13],f=1,b[13];
void dfs(int len,int k,int sum)
{
if(sum==0){ //遇到满足题意的,输出。
f=0;
printf("%d",b[0]);
for(int i=1;i<len;i++)
printf("+%d",b[i]);
printf("n");
}else{
for(int i=k;i<n;i++){
if((i==k||a[i]!=a[i-1])&&sum-a[i]>=0){ //防止重复
b[len]=a[i];
dfs(len+1,i+1,sum-a[i]);
}
}
}
}
int main()
{
while(1){
cin>>t>>n;
f=1;
if(t==0&&n==0) break;
for(int i=0;i<n;i++) cin>>a[i];
printf("Sums of %d:n",t);
dfs(0,0,t);
if(f) printf("NONEn");
}
return 0;
}
(3)The Robbery (POJ 3900)???
复杂在于:物品种类太多,使用贪心思想
思路:
a.dfs
可行性剪枝(剪枝三:题目要求一种钻石要拿则必须全拿)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lu1v5wI0-1595651391751)(C:Usersddjj6AppDataRoamingTyporatypora-user-images1583335893893.png)]
b.贪心:选择性价比最高的
https://blog.csdn.net/V5ZSQ/article/details/48894677
(二)回溯
适用于操作个数确定且连续的情况,具体见之前笔记。
二、广度优先搜索(BFS)
1、概念
广度优先搜索BFS(Breadth First Search)也称为宽度优先搜索,它是一种先生成的结点先扩展的策略。
为了便于进行搜索,要设置一个表存储所有的结点。由于在广度优先搜索算法中,要满足先生成的结点先扩展的原则,所以存储结点的表一般采用队列这种数据结构。
如果目标结点存在于解答树的有限层上,广度优先搜索算法一定能保证找到一条通向它的最佳路径,因此广度优先搜索算法特别适用于只需求出最优解的问题。当问题需要给出解的路径,则要保存每个结点的来源,也就是它是从哪一个节点扩展来的。
2、解题思路
//1、起始入队
visit = 1;
//2、循环
while(队列不为空){
//3、出队
//(4、处理出队元素,判断是否结束)
//5、出队拓展{
//6、符合条件,入队
visit = 1;
}
}
3、技巧
(1)用有visit数组标识结点是否已访问;当求步数时,用visit数组标注访问到某点所需步数,为-1表示未访问
如果没有v数组去重:时间、空间爆表,所以bfs一定要有v数组!!
(2)队列操作
//C
int front = 0,rear = 0; // front为队头指针,rear为队尾指针
q[rear++]=cur; // 初始结点入队
cur=q[front++]; // 队头元素出队
//Java
Queue<T> q = new LinkedList<T>();
q.offer(in); //入队
q.poll(); //出队并删除
while(!q.isEmpty)
(3)队列所放元素如何定义
(4)拓展方式:见题意;判断条件:访问与否、题目条件
(5)创建两个节点对象:放置赋值重叠!
in out
4、优化:双向广度搜索
所谓双向广度搜索指的是搜索沿两个方向同时进行:
(1)正向搜索:从初始结点向目标结点方向搜索;
(2)逆向搜索:从目标结点向初始结点方向搜索;当两个方向的搜索生成同一子结点时终止此搜索过程。
广度双向搜索通常有两种方法:
(1)两个方向交替扩展;
(2)选择结点个数较少的那个方向先扩展。
方法(2)克服了两方向结点的生成速度不平衡的状态,可明显提高效率。
https://www.cnblogs.com/cs-whut/p/11157732.html
4.1 双向
(1)定义一个队列
用同一个队列来保存正向和逆向扩展的结点。**开始时,将起点坐标和终点坐标同时入队列。**这样,第1个出队的坐标是起点,正向搜索扩展队列;第2个出队的坐标是终点,逆向搜索扩展队列。…,两个方向的扩展依次交替进行。
(2)使用vis数组
初始时,vis数组的全部元素值为0,由正向扩展来的结点的vis对应元素值置为1,由逆向扩展来的结点的vis对应元素值置为2。
(3)数组判断
若vis[next.x][next.y]==0,则next结点未访问过,将next结点入队并进行相应设置;
若vis[next.x][next.y]!=0,则next结点已访问过。由于vis[cur.x][cur.y]记录的是当前扩展方向(1代表正向,2代表逆向);
若vis[next.x][next.y] != vis[cur.x][cur.y],则表示next结点以前按相反的方向访问过,正向和反向遇到了同一个结点,搜索成功,返回step[cur.x][cur.y]+step[next.x][next.y]+1;
4.2 结点个数少的方向先扩展
(1) 定义两个队列
两个队列q1[]和q2[]分别用于两个方向的扩展,两个队列的队头指针和队尾指针分别为front1、front2和rear1、rear2。将起始节点放入队列q1,将目的节点放入队列q2;
(2)队列q1里的未处理节点比q2中的少(即rear1-front1 < rear2-front2),则扩展队列q1;否则扩展队列q2
int BFS(int start_x,int start_y,int end_x,int end_y,int n)
// 在n*n的棋盘中搜索从起点(start_x,strat_y)到终点(end_x,end_y)所需的最少步数
{
int front1,rear1,front2,rear2,i,flag;
point cur,next,q1[45001],q2[45001];
front1=rear1=0;
front2=rear2=0;
cur.x = start_x;
cur.y = start_y;
vis[start_x][start_y] = 1; // 设置正向探索标记为1
q1[rear1++] = cur; // 起始坐标入正向队列
next.x = end_x;
next.y = end_y;
vis[end_x][end_y] = 2; // 设置逆向探索标记为2
q2[rear2++] = next; // 终点坐标入逆向队列
while (front1 < rear1 && front2<rear2)
{
if (rear1-front1 < rear2-front2)
{
cur = q1[front1++]; flag=1; // 扩展正向队列
}
else
{
cur = q2[front2++]; flag=2; // 扩展逆向队列
}
for (i=0; i<8; ++i)
{
next.x = cur.x + dx[i];
next.y = cur.y + dy[i];
if (next.x<0 || next.x>=n || next.y<0 || next.y>=n)
continue;
//可拓展
if (!vis[next.x][next.y])
{
vis[next.x][next.y] = flag;
step[next.x][next.y] = step[cur.x][cur.y] + 1;
// 扩展正向队列
if (flag==1)
q1[rear1++] = next;
// 扩展逆向队列
else
q2[rear2++] = next;
}
else if (vis[cur.x][cur.y] != vis[next.x][next.y])
{
return step[cur.x][cur.y]+step[next.x][next.y]+1;
}
}
}
return -1; // 若搜索不成功,表示不可达
}
5、遍历全部
处理出队元素这里没有结束判断
(1)黑色方块问题
https://www.cnblogs.com/cs-whut/p/11147348.html
#include <iostream>
using namespace std;
#define N 21
struct Node
{
int x;
int y;
};
int dx[4]={-1,1,0,0};
int dy[4]={0,0,-1,1};
char map[N][N];
int visit[N][N];
int bfs(int startx, int starty,int w,int h)
{
Node q[N*N],cur,next; // q为队列
int front,rear; // front为队头指针,rear为队尾指针
int i,x,y,sum;
front=rear=0; // 队列q初始化
sum=0;
cur.x=startx; cur.y=starty;
//1、起始入队
visit[startx][starty]=1;
q[rear++]=cur; // 初始结点入队
//2、循环
while(rear!=front) // 队列不为空
{
//3、出队
cur=q[front++]; // 队头元素出队
//4、处理出队元素
sum++; // 方砖计数
//5、拓展
for (i=0;i<4;i++)
{
//6、符合条件入队
x=cur.x+dx[i]; y=cur.y+dy[i];
if(x >=0 && x<h && y>=0 && y<w && map[x][y]!='#' && visit[x][y]==0)
{
visit[x][y] = 1;
next.x=x; next.y=y; // 由cur扩展出新结点next
q[rear++]=next; // next结点入队
}
}
}
return sum;
}
int main()
{
int i,j,pos_x,pos_y,w,h,sum;
while(1)
{
cin>>w>>h;
if (w==0 && h==0) break;
for(i=0;i<h;i++)
{
for(j=0;j<w;j++)
{
cin>>map[i][j];
if (map[i][j]=='@')
{
pos_x = i;
pos_y = j;
}
visit[i][j] = 0;
}
}
sum=bfs(pos_x, pos_y,w,h);
cout<<sum<<endl;
}
return 0;
}
6、求最优、最短解
处理出队元素这里有结束判断
(1)Knight Moves (POJ 2243)
求最优、最短解
(2)整数变换
用有visit数组标识结点是否已访问;当求步数时,用visit数组标注访问到某点所需步数,为-1表示未访问
https://www.cnblogs.com/cs-whut/p/11150285.html
(3)质数变换?
**拓展:**得到只有一位不同的数
**打表:**质数,10的倍数
https://www.cnblogs.com/cs-whut/p/11150285.html
#include <iostream>
using namespace std;
char prime[10000];
void GetPrime() // 用筛法构建质数判定表
{
int i,j;
for (i=2;i<10000;i++)
prime[i]='1';
for(i=2;i<10000;i++)
{
if (prime[i]=='1')
for (j=2*i;j<10000;j+=i)
prime[j]='0';
}
prime[1]='0';
}
void BFS(int k,int m)
{
int visit[10000],que[10000],a[4]={1,10,100,1000}; //打表!!
int front,rear,i,j,s,x,y,num;
for (i=1000;i<10000;i++) //一个个初始化为-1!!!
visit[i]=-1;
front=rear=0;
que[rear++]=k; // k入队
visit[k]=0;
while(front!=rear)
{
s=que[front++]; // 队头元素出队
if (s==m)
{
cout<<visit[s]<<endl;
return;
}
for (i=0;i<4;i++)
{
//j是变换的位
for (j=0;j<10;j++)
{
//求一个不同的数???
//取前
x=s/(a[i]*10);
//取后
y=s%a[i];
num=x*a[i]*10+j*a[i]+y;
if (num>1000 && visit[num]==-1 && prime[num]=='1')
{
que[rear++]=num; // 变换后的质数入队
visit[num]=visit[s]+1;
}
}
}
}
cout<<"Impossible"<<endl;
}
int main()
{
int n,k,m;
GetPrime();
cin>>n;
while (n--)
{
cin>>k>>m;
BFS(k,m);
}
return 0;
}
(4)医院设置?
由于二叉树可往上进行,故使用临界数组存储节点可达关系
import java.util.*;
public class Main {
public static class Node{
int index,step;
public Node(int index,int step) {
this.index = index;
this.step = step;
}
}
static int[][] g = new int[101][101];
static int[] num = new int[101];
static int n;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
n = in.nextInt();
for(int i = 1;i <= n;i++) {
num[i] = in.nextInt();
int l = in.nextInt();
int r = in.nextInt();
//不要写错!!!
if(l != 0) {
g[i][l] = g[l][i] = 1;
}
if(r != 0) {
g[i][r] = g[r][i] = 1;
}
}
int min = Integer.MAX_VALUE;
for(int i = 1;i <= n;i++) {
int sum = bfs(i);
if(sum < min) {
min = sum;
}
}
System.out.println(min);
}
public static int bfs(int start) {
int sum = 0;
Node out,in;
int[] v = new int[101];
for(int i = 1;i <= 100;i++) {
v[i] = 0;
}
Queue<Node> q = new LinkedList<Node>();
in = new Node(start,0);
q.offer(in);
v[start] = 1;
while(!q.isEmpty()) {
out = q.poll();
sum += num[out.index] * out.step;
for(int i = 1;i <= n;i++) {
if(g[out.index][i] == 1 && v[i] == 0) {
in = new Node(i,out.step + 1);
q.offer(in);
v[i] = 1;
}
}
}
return sum;
}
}
(5)马的遍历
n m 不要写错了
import java.util.*;
public class Main {
public static class Node{
int i,j,step;
public Node(int i,int j,int step) {
this.i = i;
this.j = j;
this.step = step;
}
}
static int[][] map;
static int[][] v;
static int[] x = new int[] {1,1,-1,-1,2,2,-2,-2};
static int[] y = new int[] {2,-2,2,-2,1,-1,1,-1};
static int n;
static int m;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
n = in.nextInt();
m = in.nextInt();
map = new int[n + 1][m + 1];
v = new int[n + 1][m + 1];
for(int i = 1;i <= n;i++) {
for(int j = 1;j <= m;j++) {
map[i][j] = -1;
}
}
int si = in.nextInt();
int sj = in.nextInt();
bfs(si,sj);
for(int i = 1;i <= n;i++) {
for(int j = 1;j <= m;j++) {
//左对齐,宽5格
System.out.printf("%-5d",map[i][j]);
}
System.out.println();
}
}
public static void bfs(int si,int sj) {
Node in,out;
Queue<Node> q = new LinkedList<Node>();
in = new Node(si,sj,0);
q.offer(in);
while(!q.isEmpty()) {
out = q.poll();
map[out.i][out.j] = out.step;
v[out.i][out.j] = 1;
for(int i = 0;i < 8;i++) {
int ini = out.i + x[i];
int inj = out.j + y[i];
if(ini >= 1 && ini <= n && inj >= 1 && inj <= m && v[ini][inj] == 0) {
in = new Node(ini,inj,out.step + 1);
q.offer(in);
v[ini][inj] = 1;
}
}
}
}
}
(6)棋盘????
https://www.luogu.com.cn/problem/P3956
7、搜索状态判重(bfs+hash)
在状态判重方法中,hash法是一种常用的方法。常用于对判断数组的压缩以节约空间。
hash法重点在于hash函数的构造。
a.排序的压缩
对n个数字的排列,可构造vis[n!]的数组存储遍历情况
n! 与n个数字组成的全排列如何映射呢?我们以3个数字1、2、3组成的全排列来说明问题。
设排列中所有数字满足从小到大排列,则称为正序。不是正序的排列中一定存在某个数字k后面有若干个数字比k小,比k小的数字个数n称为k的逆序个数。
例如,123的各位逆序个数序列为:0,0,0。映射整数为:0=02!+01!+0*0!=0
132的各位逆序个数序列为:0,1,0。映射整数为:1=02!+11!+0*0!=1
213的各位逆序个数序列为:1,0,0。映射整数为:2=12!+01!+0*0!=2
231的各位逆序个数序列为:1,1,0。映射整数为:3=12!+11!+0*0!=3
312的各位逆序个数序列为:2,0,0。映射整数为:4=22!+01!+0*0!=4
321的各位逆序个数序列为:2,1,0。映射整数为:5=22!+11!+0*0!=5
对6位数 654312而言,各位逆序个数序列为:5,4,3,2,0,0,应映射为:55!+44!+33!+22!+01!+00!=600+96+18+4=718。
实现方式
int fact[]={1,1,2,6,24,120}; // 对应0!,1!,2!,3!,4!,5!
int hash(char *s) // 把1..6的排列映射为数字 0..(6!-1)
{
int i, j, temp, num;
num = 0;
for (i = 0; i <6-1; i++)
{
temp = 0;
for (j = i + 1; j < 6; j++)
{
if (s[j] < s[i])
temp++;
}
num += fact[6-i-1] * temp;
}
return num;
}
(1)最少交换
思路:
(1)用字符数组读取易于操作
(2)使用hash压缩
https://www.cnblogs.com/cs-whut/p/11162392.html
#include <stdio.h>
#include <string.h>
int fact[]={1,1,2,6,24,120}; // 对应0!,1!,2!,3!,4!,5!
int hash(char *s) // 把1..6的排列*s 映射为数字 0..(6!-1)
{
int i, j, temp, num;
num = 0;
for (i = 0; i <6-1; i++)
{
temp = 0;
for (j = i + 1; j < 6; j++)
{
if (s[j] < s[i])
temp++;
}
num += fact[6-i-1] * temp;
}
return num;
}
int BFS(char src[],char dest[])
{
int vis[720],front,rear,s0,s1,ts,i;
for(int i = 0;i < 720;i++){
vis[i] = -1;
}
char q[720][7],cur[7],next[7],tmp;
front=rear=0;
s1=hash(dest);
strcpy(q[rear++],src);
s0=hash(src);
vis[hash(src)]=0;
while (front<rear)
{
strcpy(cur,q[front++]); // 出队列
s0=hash(cur);
if (s0==s1) // 达到目标状态
return vis[s0];
for (i=0;i<6-1;i++)
{
strcpy(next,cur);
tmp=next[i]; next[i]=next[i+1];next[i+1]=tmp; // 交换位置i和i+1中的数字
ts=hash(next);
if (vis[ts]==-1) // 状态未出现过
{
vis[ts]=vis[s0]+1; // 记录步数
strcpy(q[rear++],next);
}
}
}
}
int main()
{
char src[7],dest[7];
while(scanf("%s%s",src,dest)!=EOF)
{
printf("%dn",BFS(src,dest));
}
return 0;
}
(2)八数码难题???
为保存状态空间,定义三个全局数据:
char visited[MAXN]; // visited[i]=1表示状态i被访问过;为0,表示未被访问
int parent[MAXN]; // parent[i]=k 表示状态结点i是由结点k扩展来的
char move[MAXN]; // move[i]=d 表示状态结点i是由父节点按照方式d扩展来的
https://www.cnblogs.com/cs-whut/p/11162663.html
bfs核心操作
while(!que.empty())
{
cur = que.front();
que.pop();
for(i=0; i<4; ++i)
{
if(!(i==0 && cur.space<3 || i==1 && cur.space%3==0 || i==2 && cur.space%3==2 ||i==3 && cur.space>5))
{
nst = cur;
nst.space = cur.space-5+2*(i+1);
nst.board[cur.space]=nst.board[nst.space];
nst.board[nst.space]=9;
k = hash(nst.board);
if(visited[k] != 1)
{
move[k] = i;
visited[k] = 1;
parent[k] = hash(cur.board);
if(k == 0) //目标结点hash值为0
return;
que.push(nst);
}
}
}
}
输出路径
void print_path()
{
int n, u;
char path[1000];
n = 1;
path[0] = move[0];
u = parent[0];
while(parent[u] != -1)
{
path[n] = move[u];
++n;
u = parent[u];
}
for(int i=n-1; i>=0; --i)
{
cout<<md[path[i]];
}
cout<<endl;
}
三、DFS和BFS的比较
在采用BFS搜索时,BFS是按一层一层来访问的,层层搜索每一层就代表了一步。BFS优先访问的是兄弟节点,只有这一层全部访问完才能访问下一层,也就是说BFS第几层就代表从起点最少需要多少歩走到当前层的结点;而DFS是按递归来实现的,它优先搜索深度,到一条路径走不下去了再回溯,优先访问的是没有访问过的子结点。
由此可体会:BFS用来搜索最短径路的解是比较合适的,比如求最少步数的解,最少交换次数的解。因为BFS搜索过程中遇到的解一定是离根最近的,所以遇到一个解,一定就是最优解,此时搜索算法可以终止。这个时候一般不适宜使用DFS,因为DFS搜索到的解不一定是离根最近的,只有全局搜索完毕,才能从所有解中找出离根的最近的解。
DFS适合搜索全部的解,因为要搜索全部的解,那么BFS搜索过程中,遇到离根最近的解,并没有什么用,也必须遍历完整棵搜索树,DFS搜索也会搜索全部,但是相比DFS不用记录过多信息,所以搜素全部解的问题,DFS显然更加合适。
DFS:尽量使用,全部遍历及部分最小解、最大解
BFS:大多数求最小解时使用
https://www.cnblogs.com/cs-whut/p/11149521.html
最后
以上就是殷勤大米为你收集整理的算法(四)搜索的全部内容,希望文章能够帮你解决算法(四)搜索所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复