猎人抓兔子 - (广度优先算法)

题目描述:

假设有一只兔子,有4个排成一排的洞,编号为1和4。兔子每天晚上跳到相邻的一个洞里住,2号洞可以跳1号和3号洞,4号洞只能跳3号洞。而猎人每天白天检查其中的一个洞。猎人告诉你每天检查的洞的编号,分析一下是否一定能抓到兔子。如果能抓到,找出最优路径。

这道题思路:想办法把兔子逼到最后一个洞或者第一个洞,这样它下一步只只有一个洞可跳自然就找到了,列举探索的思路也很重要,要分别列出当前可能藏的洞和当前没找到下一次可能藏的洞,找准这两个变量很关键,最终是要把兔子逼到第一个洞或者最后一个洞,所以下一次可能藏的洞会越来越少,所以猎人选择洞的时候也要考虑这一点(猎人选择所要找的洞,下一次可能藏的洞相比找之前可能藏的洞应该会少,直到最终剩下一个洞)。

例如:先选择1,兔子不在1,那么当前可能在的洞2,3,4,下一次可能藏的洞为1,2,3,4。这样跟下一次比较洞没有减少。所以想着第一步不应该选1。按照每找一次下一次可能藏的洞越来越少思路。实际我们代码中找的时候肯定是按顺序找,列出所有可能找的过程,所以我们代码就要决定哪一步是不需要的,找准条件筛选。

BFS(广度优先算法)

此题的思想和BFS(广度优先算法)类似,简单说一下。BFS(广度优先算法)是一个针对图和树的遍历算法。简单的说,BFS是从根节点开始,沿着树的宽度遍历树的节点,如果所有节点均被访问,则算法中止;最初用于解决迷宫最短路径和网络路由等问题。

BFS操作步骤如下: 
1、把起始点放入queue; 
2、重复下述2步骤,直到queue为空为止: 
1) 从queue中取出队列头的点;并删除队列首节点。
2) 找出与此点邻接的且尚未遍历的点,进行标记,然后全部放入queue中。 

使用该算法关键的数据结构为队列。新加入的节点一般要是未处理过的,所以某些情况下最初要对所有节点进行标记。 这里写图片描述(1)将起始节点1放入队列中,标记为已遍历: 
(2)从queue中取出队列头的节点1,找出与节点1邻接的节点2,3,标记为已遍历,然后放入queue中。 
(3)从queue中取出队列头的节点2,找出与节点2邻接的节点1,4,5,由于节点1已遍历,排除;标记4,5为已遍历,然后放入queue中。以此类推.........

要标志节点是否访问过,用数组是一种很快速的方法,但有时数据量太大,很难用一个大数组来记录时,采用hash,set是最好的做法。实际上visit数组在这里也是充当hash,set的作用。python中暂时采用集合set。还可以利用字典dict标记(充当hash)。

猎人抓兔子代码如下:

import queue


def get_new_set_hole(search_hole, cur_hole):
    """
    :param search_hole: 要寻找的洞
    :param cur_hole: 当前兔子可能在的洞
    :return:
    """
    # 利用集合代表当查看search_hole后,没有抓到兔子,兔子可能藏身的洞
    set_hole = set()
    # 在当前可能存在的洞找相邻的洞即为下一次可能藏身的洞
    for hole in cur_hole:
        # 右邻的洞,没有抓到兔子,所以!=search_hole
        if hole+1 != search_hole and hole+1 <= 4:
            set_hole.add(hole+1)
        # 左邻的洞
        if hole-1 != search_hole and hole-1 >= 1:
            set_hole.add(hole-1)

    return set_hole


def main():
    """
    1.首先将初始情况加入队列,当队列不为空时:
    2.如果已经得到了结果,那就结束,跳转到(7.)
    3.取出队列的第一个值
    4.分别检查4个洞
    5.如果检查之后兔子的藏身洞变少,那就将这个洞加入到队列中
    6.如果检查后兔子没有藏身之处,则已经找到结果,
      将结果赋值给path同时设置已找到结果标志,并跳出循环
    7.输出最后记录的结果
    8.根据得到的结果,找出每一步后兔子藏身之处
    """
    queue_hole = queue.Queue()
    # 记录最终的找寻过程
    path = []
    # 找到就退出循环标志
    flag = False
    # 三个参数分别表猎人要找的洞,当前可能藏身的洞,空列表记录猎人找洞的记录
    # 初始状态还没开始找,所以暂时赋下面的值,第一个参数一般是1-4循环尝试,
    # 这里初始赋值随便赋一个不影响的值
    queue_hole.put((0, {1, 2, 3, 4}, []))
    while not queue_hole.empty():
        if flag:
            break
        cur, cur_hole_set, record = queue_hole.get()
        for hole in range(1, 5):
            copy_record = record[:]
            next_hole_set = get_new_set_hole(hole, cur_hole_set)
            if len(next_hole_set) <= len(cur_hole_set):
                copy_record.append(hole)
                queue_hole.put((hole, next_hole_set, copy_record))
            if not next_hole_set:
                record.append(hole)
                path = copy_record
                flag = True
                break
    print(path)

例子:LeetCode 127 单词接龙

题目描述:

字典wordList中从单词beginWord和endWord的转换序列是一个按下述规格形成的序列beginWord -> s1 -> s2 -> ... -> sk:
每一对相邻的单词只差一个字母。
对于 1 <= i <= k 时,每个 si 都在 wordList 中。注意,beginWord 不需要在 wordList 中。sk == endWord
给你两个单词beginWord和endWord和一个字典wordList,返回从beginWord 到endWord的最短转换序列 中的 单词数目。如果不存在这样的转换序列,返回 0 。

要求:①每次只能变换一个字母。②每个字符串长度相同
③每次转换的字符串必须在wordList中。④如果没有这种转换,返回0。⑤没有重复的

示例:

输入:beginWord = "hit", endWord = "cog", wordList = ["hot", "dot", "dog", "lot", "log", "cog"]
输出:5
解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。

输入:beginWord = "hit", endWord = "cog", wordList = ["hot", "dot", "dog", "lot", "log"]
输出:0
解释:endWord "cog" 不在字典中,所以无法进行转换。 

解题思路

1、本题要求的是最短转换序列的长度,看到最短首先想到的就是广度优先搜索。想到广度优先搜索自然而然的就能想到图,但是本题并没有直截了当的给出图的模型,因此我们需要把它抽象成图的模型。
2、我们可以把每个单词都抽象为一个点,如果两个单词可以只改变一个字母进行转换,那么说明他们之间有一条双向边。因此我们只需要把满足转换条件的点相连,就形成了一张图。
3、基于该图,我们以 beginWord 为图的起点,以 endWord 为终点进行广度优先搜索,寻找 beginWord 到 endWord 的最短路径。

由图可得显然有两条路径满足最短路径。

本题跟二叉树的层次遍历类似,想像有一颗二叉树,广度优先搜索就是把二叉树拆成一层一层的,广度优先。(深度优先搜索就是直接一路找到最下面,如果没找到然后再返回来,深度优先)。

因为此题只要求最短路径长度,所以只要遍历到该层时及时记录该层的层数(深度deep)即可。所以差不多就是个找寻满足条件的单词的问题,找到单词之后及时入队列和出队列。

按照分层思路来:那么每层满足条件的单词和层数如下:同一个单词出现在两层因为不止一条路径

hit    1    i变为o 到2
hot   2    h变为d和l到 3
dot   3    d变为l,但lot在第三层已经访问过了,所以不到4,t变为g, 到4
lot    3    t变为g 到4 ,由于l可以变为d,但是dot访问过了。所以4没有dot
dog  4    ..............
log   4
cog  5

import queue


def word_chain(begin_word, end_word, word_list):
    """
    :param begin_word: 开始单词
    :param end_word: 结束单词
    :param word_list: 单词表
    :return:
    """
    if end_word not in word_list:
        return 0
    # 构造一个字典,key为'*ot','h*t','ho*'这样的形式(key为word_list中单词变化而来)
    # 然后把满足只变化一个字母的单词归并到同一个key的value中。
    # 例如:每个单词都有len(word)中形式,hot: *ot, h*t, ho*,分别当做key,然后然后选取满足条件的value
    # {'*ot': ['hot', 'dot', 'lot'], 'h*t': ['hot'], 'ho*': ['hot']}
    word_dict = dict()
    for word in word_list:
        for i in range(len(word)):
            key = "{0}*{1}".format(word[:i], word[i+1:])
            if key in word_dict:
                word_dict[key].append(word)
            else:
                word_dict[key] = [word]

    queue_data = queue.Queue()
    queue_data.put((begin_word, 1))
    # 集合标记单词是否访问过
    visited = set()
    while not queue_data.empty():
        word, steps = queue_data.get()
        print(word, steps)
        if word not in visited:
            visited.add(word)
            if word == end_word:
                return steps
            for i in range(len(word)):
                s = "{0}*{1}".format(word[:i], word[i+1:])
                for j in word_dict.get(s, []):
                    # 如果j在visited中,说明j已经被访问过了,不需要再次进行访问。
                    if j not in visited:
                        queue_data.put((j, steps + 1))
    return 0

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值