我对搜索算法的一点点理解

大家在学习算法参加信息学竞赛的过程中会遇到一种很暴力的“搜索·”算法,说他很暴力是因为他真的很暴力(哈哈,开个玩笑),搜索在最坏情况下的时间复杂度相当于暴力。与此同时,搜索有时能够解决所有问题的一种方法,因为不管是动态规划,字符串,数学,。。。,我们用很暴力的搜索都能够得到想要的答案,当然是在不考虑Time Limit Exceed的前提下。

现在我们举几个例子来加深我们对“搜索”的理解。

我小时候很喜欢玩玩具,有一次我的一个变形金刚不知道放哪里去了(虽然他很久,是我哥哥小时候玩的),所以我必须在家里寻找这一个不知道放在哪里的玩具,好吧,我必须按顺序找我的变形金刚,我按这样的顺序来找:杂物间的抽屉一,杂物间的抽屉二,杂物间的抽屉三,杂物间的床上,爷爷奶奶房间的抽屉一,爷爷奶奶房间的抽屉二,爸爸妈妈房间的抽屉一,爸爸妈妈房间的抽屉二。假设这就是我的搜索范围。

那么我开始找了。我现在杂物间的抽屉一找,没有找到,然后在杂物间的抽屉二中找,也没有找到,。。。,好了现在我打开了爸爸妈妈房间的抽屉一,找了一下,发现了我的变形金刚,“哇塞,终于找到了!!!”我很开心。因为我经历了搜索的过程后找到了我搜索的目标。这是一件多么令人愉快的过程啊:)

当然我那个时候还小,我甚至可能在发现我找不到我心爱的玩具的时候变得急躁起来,什么,你小时候一直很淡定,找不到东西从来不会着急,好吧,你牛逼(哈哈,开个玩笑)!我可能从杂物间的抽屉一开始找,然后找杂物间的抽屉二,然后我可能会忘了找杂物间的抽屉三而直接去爷爷奶奶的房间找,当在爷爷奶奶的房间找不到玩具后我可能又跑到杂物间在杂物间的抽屉一里面找了。

这其中有两种情况是有问题的。我把他们称为:

1、遗漏:我在找完杂物间的抽屉二后没有接着去找杂物间的抽屉三,而是去了爷爷奶奶的房间;

2、重复:我重复找了两次杂物间的抽屉一。(天啊,我真是一个大沙茶啊!!!)

当然非人为的“遗漏”在搜索中是不会有的,而“重复”则可能经常出现,“重复”将导致我寻找效率的降低(本来找8个抽屉就能找到了,现在要找大于8个抽屉),有的时候“重复”还可能导致我陷入死循环(比如说我找完杂物间的抽屉一去找杂物间的抽屉二,找完杂物间的抽屉二去找爷爷奶奶房间的抽屉一,找完爷爷奶奶房间的抽屉一又返回来找杂物间的抽屉一;真的,现实中我没有沙茶的这种程度)。当然对于“重复”我们有一种御用的解决方案--“记忆化搜索”。我可以在找玩具之前先烈一个表,上面是所有带访问的抽屉的名称,然后没胆我搜索完一个抽屉,我就在我列的表格中对应的抽屉名字后面打钩,那么下次我就不会傻到再去我找过的抽屉中找我的玩具,这就是记忆化搜索。记忆化搜索也叫备忘录式动态规划,我们可以在《算法导论》中的动态规划中找到这方面的内容,由此观之,搜索和动态规划还是有着一些关系的,但是因为这里我们讲的是搜索的内容,所以关于搜索和动态规划的关系这里就不展开了,因为这里我想让你们了解的一点就是:搜索算法的时间复杂度一般来说都是O(2^n)的,而采用了记忆化搜索的方法后时间复杂度就等同于动态规划的时间复杂度O(n^2)或O(n^3),这是很能减少时间的方法,是搜索的一个很大优化。

上面说过非人为的遗漏是不可能发生的,但是对于有些情况我是可以人为的遗漏的。比如说,我知道杂物间的第三个抽屉里面放的是漫画书,我从来没有在放漫画书的柜子里放过玩具,这种情况下我就可以不用打开杂物间的抽屉三了,因为里面肯定没有我要找的玩具:)我们称人为的遗漏为“剪枝”,他减掉我们不需要的访问环节,对于提高搜索的速度起了很大的作用。

现在我们总结一下上述两种对搜索的优化方法:

1、记忆化搜索:对已经找过的节点(这里节点相当于抽屉)做一个标记和记录,下次还要访问这个节点时我可以直接返回这个节点之前所记录的值,而不是重复的再搜索一遍。

2、剪枝:对于有些肯定不符合要求的节点,不必搜索,而是直接否决他!

搜索算法主要分为两类:宽度优先搜索(BFS)和深度优先搜索(DFS),其他的搜索方法基本上都是在这两种方法的基础上添加了一些属性而得到的。

关于BFSDFS的主要内容,大家可以在《算法竞赛入门经典》或者《图论算法、理论实现及应用》中学习,我在这里简要介绍一下。

DFS:相信大家小时候都玩过走迷宫的游戏(什么,你没有玩过,好吧,到百度上找一张迷宫来),我不知道你们当时是怎么走迷宫的,反正我是这么走的:我直接从起点开始连线,沿着前方有路的路走,最好情况下就是我一笔就画到了终点。但是更多情况下是我画到了一个死胡同,这个是我我会沿我之前的路线返回,直到我找到一个交叉点其中有一条路我没有走过,然后我会从这个交叉点开始沿我没有走过的路划线。这就是搜索算法在走迷宫游戏中的体现,你看咱们多牛逼,在没上小学的时候都已经会深度优先搜索了:)然后我总结一下我走迷宫的过程:其中很多时间我是不断向前划线的,又有很多时候我是沿着走错的线返回有没走过的路的交叉点的,这种返回寻找交叉点的过程称为回溯。DFS的过程其实就是不断向前搜索和回溯的过程。

BFS:我也可以采用BFS的方法进行走迷宫。BFS方法需要开一个队列,一开始我把起点放进队列,然后每次从队首取出一个节点,把与这个节点直接相连的并且满足没有进过队列并且没有在队列中的交叉点推进队列,直到出现第一个节点满足该节点是与终点直接相连的节点。这种方法还可以用于测量两点之间的最近距离。

现在我提供两个搜索进阶的文章,这两篇文章都可以在百度上搜到:

文章1aiiyuu的《状态空间搜索》

获得途径:上百度搜“可可的算法街区”,点开第一个链接对应的人人主页就能看到,偏重理论。感觉其中的内容总结自刘汝佳的《算法艺术与信息学竞赛》(黑书)。

文章2ambition的《搜索进阶》

获得途径:上百度搜“搜索进阶 amb”,点开第一个博客园的链接就能看到,偏重代码实现,代码传上来的时候排版有些问题。

下面我想介绍一下dfsbfs的结构:

DFS的结构:

bool ok = false;
void dfs(int position_now) {
    if(ok) return;
    if(position_now满足某些特定条件) {
        ok = true;
        return;
    }
    for(position_now所引出的所有position_then) {
        dfs(position_then);
    }
    return;
}

最后检查ok

bool dfs(position_now) {
    if(position_now满足某些特定条件) return true;
    else {
        for(position_now所引出的所有position_then) {
            if(dfs(position_then)) return true;
        }
    }
    return false;
}

BFS的结构:

queue <Type> que;
void bfs() {
    q.push(start_position);
    while(!q.empty()) {
        position_now = q.front(); q.pop();
        for(position_now所引出的所有position_then) {
if(position_then满足终点的条件) return;
            if(position_then满足进队列的条件) {
                q,push(position_then);
            }
        }
    }
}

附:

简单DFS举例:hdu1313 Red and Black

参考代码:

附:
简单DFS举例:hdu1313 Red and Black
参考代码:
const int N = 22;
char G[N][N];
int n , m;
bool vis[N][N];
int dir[4][2] = {1,0,0,1,-1,0,0,-1};
bool inmap(int x,int y) {
    return x >= 0 && y >= 0 && x < n && y < m;
}
int ans;
void dfs(int x,int y) {
    vis[x][y] = 1;
    ans ++;
    for(int i=0;i<4;i++) {
        int xx = x + dir[i][0] , yy = y + dir[i][1];
        if(!inmap(xx,yy) || vis[xx][yy] || G[xx][yy] == '#') continue;
        dfs(xx , yy);
    }
}
int main() {
    while(~scanf("%d%d",&m,&n) && m + n) {
        for(int i=0;i<n;i++) scanf("%s",G[i]);
        memset(vis,0,sizeof(vis));
        for(int i=0;i<(n);i++) for(int j=0;j<(m);j++) {
            if(G[i][j] == '@') {
                ans = 0;
                dfs(i,j);
            }
        }
        printf("%d\n" , ans);
    }
    return 0;
}

简单BFS举例:hdu1253 胜利大逃亡

参考代码:

const int N = 55;
int dir[6][3] = {1,0,0,-1,0,0,0,1,0,0,-1,0,0,0,1,0,0,-1};
int a , b , c , t;
bool inmap(int x,int y,int z) {
    return x>=0&&x<a&&y>=0&&y<b&&z>=0&&z<c;
}
struct node {
    int x , y , z;
};
int ma[N][N][N] , maze[N][N][N];
queue <node> q;
void bfs() {
    node u , v;
    while(!q.empty()) q.pop();
    u.x = u.y = u.z = 0;
    memset(ma,-1,sizeof(ma));
    ma[0][0][0] = 0;
    q.push(u);
    while(!q.empty()) {
        u = q.front(); q.pop();
        for(int i=0;i<6;i++) {
            int x = u.x + dir[i][0];
            int y = u.y + dir[i][1];
            int z = u.z + dir[i][2];
            if(!inmap(x,y,z) || maze[x][y][z] == 1) continue;
            if(ma[x][y][z] == -1 || ma[u.x][u.y][u.z]+1<ma[x][y][z]) {
                ma[x][y][z] = ma[u.x][u.y][u.z] + 1;
                v.x = x , v.y = y , v.z = z;
                q.push(v);
            }
        }
    }
}
int main() {
    int T;
    scanf("%d",&T);
    while(T--) {
        scanf("%d%d%d%d",&a,&b,&c,&t);
        for(int i=0;i<a;i++)
        for(int j=0;j<b;j++)
        for(int k=0;k<c;k++)
            scanf("%d",&maze[i][j][k]);
        bfs();
        if(ma[a-1][b-1][c-1] == -1 || ma[a-1][b-1][c-1] > t) puts("-1");
        else printf("%d\n" , ma[a-1][b-1][c-1]);
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值