搜索(BFS和DFS)

文章讨论了在路径问题中,如N皇后问题、路径之谜、迷宫问题等,如何利用深度优先搜索(DFS)和广度优先搜索(BFS)算法寻找解决方案。DFS通常用于寻找一条路径,而BFS则用于寻找最短路径。文章通过多个实例详细解释了这两种搜索策略的实现和边界判断,并提供了相应的Python代码示例。
摘要由CSDN通过智能技术生成

先用几道题巩固上节所学

题目描述

我们要求找出具有下列性质数的个数(包含输入的自然数 n):

先输入一个自然数 n (n≤1000),然后对此自然数按照如下方法进行处理:

  1. 不作任何处理;

  2. 在它的左边加上一个自然数,但该自然数不能超过原数的一半;

  3. 加上数后,继续按此规则进行处理,直到不能再加自然数为止。

输入描述

输入一个正整数 n。

输出描述

输出一个整数,表示具有该性质数的个数。。

输入输出样例

示例 1

输入

6

输出

6

 题解:

import os
import sys

n = int(input())
res = 1
def f(n):
  global res
  if n == 1:
    return
  for i in range(1,n//2+1):
    res = res + 1
    f(i)
f(n)
print(res)

2. 题目描述

X 星球的居民脾气不太好,但好在他们生气的时候唯一的异常举动是:摔手机。

各大厂商也就纷纷推出各种耐摔型手机。X 星球的质监局规定了手机必须经过耐摔测试,并且评定出一个耐摔指数来,之后才允许上市流通。

X 星球有很多高耸入云的高塔,刚好可以用来做耐摔测试。塔的每一层高度都是一样的,与地球上稍有不同的是,他们的第一层不是地面,而是相当于我们的 2 楼。

如果手机从第 7 层扔下去没摔坏,但第 8 层摔坏了,则手机耐摔指数 = 7。

特别地,如果手机从第 1 层扔下去就坏了,则耐摔指数 = 0。

如果到了塔的最高层第 n 层扔没摔坏,则耐摔指数 = n。

为了减少测试次数,从每个厂家抽样 3 部手机参加测试。

如果已知了测试塔的高度,并且采用最佳策略,在最坏的运气下最多需要测试多少次才能确定手机的耐摔指数呢?

输入描述

一个整数 n(3<n<10000),表示测试塔的高度。

输出描述

输出一个整数,表示最多测试多少次。

输入输出样例

示例

输入

3

输出

2

样例解释

手机 a 从 2 楼扔下去,坏了,就把 b 手机从 1 楼扔;否则 a 手机继续 3 层扔下。

思路:

先进行审题

 题解

b = [0] * 105
c = [0] * 105
n = int(input())
i = 0
while c[i] < n:
   i += 1
   b[i] = i + b[i - 1]
   c[i] = c[i - 1] + b[i - 1] + 1
print(i)

DFS: 深度优先搜索

问题1:N皇后问题

题目描述

在 N×N 的方格棋盘放置了 N 个皇后,使得它们不相互攻击(即任意 2 个皇后不允许处在同一排,同一列,也不允许处在与棋盘边框成 45° 角的斜线上。你的任务是,对于给定的 N,求出有多少种合法的放置方法。

输入描述

输入中有一个正整数 N≤10,表示棋盘和皇后的数量

输出描述

为一个正整数,表示对应输入行的皇后的不同放置数量。

输入输出样例

示例 1

输入

5

输出

10

读题:

N皇后问题其实是一个摆棋子的问题,以4*4的棋盘为例,假设皇后放置在不同行。

把皇后放在第一行第一格,然后找第二行,只能放在三和四。

放在三时:

我们发现整个棋盘已经无法再放多一个皇后了,所以位置3是不行的,我们进行回溯,回溯到第二步,重新在第二行摆放棋子,将棋子摆到第四格的时候:

 我们发现,第三行的第二格还能再放一个棋子,但是第四行就没办法放棋子了。我们再进行回溯,回溯到第一步,重新调整第一行棋子的位置。

以此类推,直到找到一种可能,此时count+=1

根据模板的思路:

这里用一个N*N的矩阵来表示棋盘

1.算法开始:

当前行设为第一行,当前列设为第一列,从第一行第一列开始搜索,即只能让皇后从第一行放到第n行。这样放,皇后一点不在同一行。

我们只用判断之前的1到a-1个皇后的位置和当前第a个皇后的位置是否属于同一列或者斜线,判断是否同一列。

2.判断边界:

在当前行,当前列的位置上判断是否满足条件(即保证经过这一点的行,列与斜线上都没有两个皇后),若不满足,跳到第5步,即不符合边界条件。

首先说一下,什么叫不符合边界条件,不只是跳出了搜索范围,剪枝也可以从这里开始,比如这里不满足条件,向下继续搜索也不会再有结果。

这可以理解为超出边界的剪枝,我们的边界只得可能存在解的范围,这里已经超出有解的范围,必然要被踢出。

3.判断条件:

我们用数组x(al=i来表示第a个皇后的位置在第a行第i列,我们不用考虑是否在同一行的问题,我们只用判断之前的1到a-1个皇后的位置和当前第a个皇后的位置是否属于同一列或者斜线。

判断是否属于同一列:就判断x【a】是否等于xli;判断是否属于同一斜线:等同于判断行之差是否等于列之差也,即abs(x【k】-x【i】)|x【k】==x【i】。

4.搜索过程:调用Check 函数。

如果边界条件,就继续调用放下一个皇后的位置check(参数):

如果当搜索到第 N+1行的时候,即代表前N 行已经搜索完了,所以这个时候正好求出了一个解,记录加一。

5.在当前位置上不满足条件的情形,进行回溯。(返回上一层)

x = [0] * 15
n = 0
sum = 0
def pd(k):
    for i in range(1, k):
        if abs(k - i) == abs(x[k] - x[i]):   #可以自己动手画坐标图看看,会发现一个点(x, y)与其两条斜线上的相邻点的x和y的差的绝对值相同,下面有配图
            return 0
        elif x[k] == x[i]:
            return 0
# 即判断是否符合条件来放,i表示皇后所在的行数,x[i]表示所在的列数,
# 所以前面那个条件用来判断两个皇后是否在对角线上,后面用来判断是否在同一列上。
# 行数不需要判断,因为他们本身的i就代表的是行数
    return 1
def check(a):
    if a > n:
        global sum
        sum += 1
    else:
        return False
    return True
def DFS(a):
    if check(a):
        return
    else:
        for i in range(1, n + 1):
            x[a] = i
            # 第a个皇后放的列数
            if pd(a):
            # 判断是否能放这步
                DFS(a + 1)
                # 能的话进行下一个皇后的放置
            else:
                continue
                # 不能就下一列
if __name__ == '__main__':
    n = int(input())
    # 不能就下一列
    DFS(1)
    # 每次都从第一个皇后开始
    print(sum)

配图:

 

问题2:路径之迷

题目描述

小明冒充 X 星球的骑士,进入了一个奇怪的城堡。

城堡里边什么都没有,只有方形石头铺成的地面。

假设城堡地面是 n×n 个方格。如下图所示。

按习俗,骑士要从西北角走到东南角。可以横向或纵向移动,但不能斜着走,也不能跳跃。每走到一个新方格,就要向正北方和正西方各射一箭。(城堡的西墙和北墙内各有 �n 个靶子)同一个方格只允许经过一次。但不必走完所有的方格。如果只给出靶子上箭的数目,你能推断出骑士的行走路线吗?有时是可以的,比如上图中的例子。

本题的要求就是已知箭靶数字,求骑士的行走路径(测试数据保证路径唯一)

输入描述

第一行一个整数 N(0≤N≤20),表示地面有 N×N 个方格。

第二行 N 个整数,空格分开,表示北边的箭靶上的数字(自西向东)

第三行 N 个整数,空格分开,表示西边的箭靶上的数字(自北向南)

输出描述

输出一行若干个整数,表示骑士路径。

为了方便表示,我们约定每个小格子用一个数字代表,从西北角开始编号: 0,1,2,3 ⋯⋯

比如,上图中的方块编号为:

0 1 2 3

4 5 6 7

8 9 10 11

12 13 14 15

输入输出样例

示例

输入

4
2 4 3 4
4 3 3 3

输出

0 4 5 1 2 3 7 11 10 9 13 14 15

读题:

 

这里用一个N*N的矩阵来表示城堡的位置,横向、纵向标号1-N。

我们采用逆推法,既然原题目是走到哪里射一支箭,那我们就走到那里之后拔一支箭,如果最后得到所有的靶子上都没有箭了,由于题目的路径唯一,那就证明我们找到了题目所要求的路径。

1.算法开始:

当前行设为第一行,当前列设为第一列,从第一行第一列开始搜索。

然后从左上角初始位置,按照题目意思进行寻路。

2.判断边界:

在当前行,当前列的位置上判断是否满足条件,若不满足,跳到第5步,即不符合边界条件。判断条件如下:

i.flag[x][y]==1标记数组已经被标记已被走过,不能再走,超出边界

ii.x<1从左侧走出方格

iii.x>n从右侧走出方格

iv.y<1从上侧走出方格

v.y>n从下侧走出方格

vi.col【x】<=0没走到右下角,箭用完了

vii.rol【y2】<=0没走到右下角,箭用完了

3.搜索过程:

调用Check函数。

如果边界条件满足,就继续调用搜索,找到下一步的位置

4.check(参数):

如果当搜索到x=n,y=n时,且靶子上的箭都没了,按就找到了答案。按照题目输出即可。

5.在当前位置上不满足条件的情形,进行回溯,并还原现场

题解:

n = 0

flag = [[0 for i in range(26)] for i in range(27)]

resX = [0 for i in range(1000)]

resY = [0 for i in range(1000)]

resCount = 0

# ---------图的路径搜索常用方向移动表示-------

dx = [0, 1, -1, 0]

dy = [1, 0, 0, -1]


# 两两组合形成上下左右四个方向
#      1------------------> x
#      |
#      |
#      |
#      |
#      |
#      |
#      |
#      ↓
#      y

# dx[0]=0 dy[0]=1 那么代表向下的方向

# dx[1]=1 dy[1]=0 那么代表向右的方向

# dx[2]=-1 dy[0]=0 那么代表向左的方向

# dx[3]=0 dy[1]=-1 那么代表向上的方向

# --------------------------------------------


def check(x, y):
    global n
    if x == n & y == n:

        # print("check point1")

        for i in range(1, n + 1):

            if (col[i] != 0):
                return False

                # 如果箭靶上的数目不为0,根据逆推,我们通过当前路径得不到箭靶上的结果

        for i in range(1, n + 1):

            if (rol[i] != 0):
                return False

        for i in range(0, resCount):
            x2 = resX[i]
            # x 轴坐标

            y2 = resY[i]

            # y 轴坐标

            sum = n * (x2 - 1) + y2 - 1

            # 通过计算的到为题目要求的坐标系

            print(sum, end=" ")


        return False
        # 成功终止
    else:
        return True  # 继续搜索

        # 关于终止还是继续我们交给判定即可


def pd(x2, y2):
    global n
    # print("x2 :", x2,"y2 :", y2, " n ", n)
    if flag[x2][y2] == 1:
        # print("checkPoint3")
        return False
        # 已被走过,不能再走,超出边界
    elif x2 < 1:
        # print("checkPoint5")
        return False
    # 从左侧走出方格

    elif x2 > n:
        # print("checkPoint6")
        return False
    # 从右侧走出方格

    elif col[x2] <= 0:
        # print("checkPoint8")
        return False
    # 没走到右下角,箭用完了

    elif y2 < 1:
        # print("checkPoint7")
        return False
    # 从上侧走出方格

    elif y2 > n:
        # print("y2 :",y2," n ",n)

        return False
    # 从下侧走出方格

    elif rol[y2] <= 0:
        # print("checkPoint9")
        return False
    # 没走到右下角,箭用完了







    else:
        return True


# 符合边界条件,可以继续执行搜索
def dfs(x, y):
    if not check(x, y):
        return
    # 包含不符合规则的地方,回溯,用于剪枝
    else:

        for i in range(0, 4):

            xt = dx[i] + x

            yt = dy[i] + y

            # print(xt, yt)
            if not pd(xt, yt):
                # print("CheckPoint", xt, yt)
                continue
                # 不符合要求继续换方向搜索


            else:

                # 因为要进行位置转移,我们给它起个名字,叫作案现场

                # 比如向下移动

                col[xt] -= 1
                rol[yt] -= 1
                flag[xt][yt] = 1

                global resCount

                resX[resCount] = xt

                resY[resCount] = yt

                resCount += 1
                # print("---------123-------")
                # print(flag)
                # print("----------------")
                dfs(xt, yt)

                # 搜索回溯后,因为没有找到正确答案,所以要回复作案现场,返回到搜索之前

                resCount -= 1

                flag[xt][yt] = 0
                # print("--------321--------")
                # print(flag)
                # print("----------------")
                col[xt] += 1
                rol[yt] += 1


if __name__ == '__main__':
    n = int(input())

    # print("----------------")
    # print(flag)
    # print("----------------")

    rol = input().split()

    rol = list(map(int, rol))

    rol = [0] + rol

    col = input().split()

    col = list(map(int, col))
    col = [0] + col

    flag[1][1] = 1
    # print("----------------")
    # print(flag)
    # print("----------------")
    col[1] -= 1

    rol[1] -= 1

    resX[resCount] = 1

    resY[resCount] = 1

    resCount += 1

    dfs(1, 1)

BFS搜索的原理

“逐层扩散”。从起点出发,按层次从近到远,逐层先后搜索。

编码:用队列实现。

应用:BFS一般用于求最短路径问题,BFS的特点是逐层搜索,先搜到的层离起点更近。

 BFS的特点:逐层扩散。

往BFS的队列中加入邻居结点时,按距离起点远近的顺序加入:先加入距离起点为1的邻居结点,加完之后,再加入距离为2的邻居结点,等等 搜完一层,才会继续搜下一层。

最短路径:从起点开始,沿着每一层逐步往外走,每多一层,路径长度就增加1。 所有长度相同的最短路径都是从相同的层次扩散出去的。 搜到第一个到达终点的路径,就是最短路径

广度优先搜索算法的设计步骤

按照定义设计:

1.确定该题目的状态(包括边界)

2.找到状态转移方式

3.找到问题的出口,计数或者某个状态

4.设计搜索

会发现我们前期要找到的参数基本一致,所以在一般情况下BFS和DFS可以相互转换.

根据设计步骤的伪代码:

 

1. 长草

题目描述

小明有一块空地,他将这块空地划分为 n 行 m 列的小块,每行和每列的长度都为 1。

小明选了其中的一些小块空地,种上了草,其他小块仍然保持是空地。

这些草长得很快,每个月,草都会向外长出一些,如果一个小块种了草,则它将向自己的上、下、左、右四小块空地扩展,

这四小块空地都将变为有草的小块。请告诉小明,k 个月后空地上哪些地方有草。

输入描述

输入的第一行包含两个整数n,m。

接下来 n 行,每行包含 m 个字母,表示初始的空地状态,字母之间没有空格。如果为小数点,表示为空地,如果字母为 g,表示种了草。

接下来包含一个整数 k。 其中,2≤n,m≤1000,1≤k≤1000。

输出描述

输出 n 行,每行包含 m 个字母,表示 k 个月后空地的状态。如果为小数点,表示为空地,如果字母为 g,表示长了草。

输入输出样例

示例

输入

4 5
.g...
.....
..g..
.....
2

输出

gggg.
gggg.
ggggg
.ggg.

题目解析:

这个题目是广度优先搜索的模板题。由于这个题目时输出广度优先搜索K次扩展后的终态,那我们就不用设置Check 函数。

这里用一个N*M的矩阵来表示草地。

1.算法开始:将字母为g的草地的位置加入队列,然后向下执行

2.判断边界:判断是否已经长了草,判断是否超出边界范围

3.搜索过程:不断从队列取出一个节点,进行上下左右的扩展,执行2判断边界,符合就放入队列,不符合就跳过。执行K次扩展,输出草地状态。

题解:

# 请在此输入您的代码
from queue import Queue

dx = [0, 1, -1, 0]

dy = [1, 0, 0, -1]

# 两两组合形成上下左右四个方向
#      1------------------> x
#      |
#      |
#      |
#      |
#      |
#      |
#      |
#      ↓
#      y

# dx[0]=0 dy[0]=1 那么代表向下的方向

# dx[1]=1 dy[1]=0 那么代表向右的方向

# dx[2]=-1 dy[0]=0 那么代表向左的方向

# dx[3]=0 dy[1]=-1 那么代表向上的方向

Map = []

q = []
qfront = 0
qend = 0

n = 0
m = 0
k = 0

length = 0

#pd函数判断是否越界
def pd(x, y):
    if x < 0:
        return False

    # x 轴坐标左侧越界

    elif x >= n:
        return False
    # x 轴坐标右侧越界

    elif y < 0:
        return False
    # y轴坐标上侧越界

    elif y >= m:
        return False
    # y 轴坐标下侧越界

    elif Map[x][y] == 'g':
        return False
    # 已经长草了

    else:
        return True

    # 在范围内,且没长草


def BFS():
    global k, q, n, m, Map, length, qend, qfront
    # print("K Length", k, length)
    while k > 0 and length > 0:

        tempPair = q[qfront]


        qfront += 1
        x = tempPair[0]  # 横坐标

        y = tempPair[1]  # 纵坐标

        for i in range(4):

            nowx = x + dx[i]  # 扩展后的横坐标

            nowy = y + dy[i]  # 扩展后的纵坐标

            if (pd(nowx, nowy)):
                q.append((nowx,nowy))
                qend += 1
                Map[nowx][nowy] = 'g'
        length -= 1

        if length == 0:
            k -= 1
            length = qend - qfront


if __name__ == '__main__':

    n, m = map(int, input().split())

    Map = [[0 for _ in range(m)] for _ in range(n)]  # Python 动态开数组会减少运行时间

    for i in range(n):
        input_ = input()
        for j in range(m):
            Map[i][j] = input_[j]
            if Map[i][j] == 'g':
                q.append((i,j))
                qend += 1

    k = int(input())

    length = qend - qfront

    BFS()
    for i in range(n):
        str_temp = ''
        for j in range(m):
            str_temp += Map[i][j]
        print(str_temp)

PS:__name__属性是Python的一个内置属性,记录了一个字符串。

  • 若是在当前文件,name 是__main__。
  • 在hello文件中打印本文件的__name__属性值,显示的是__main__
  • 若是导入的文件,__name__是模块名。
  • test文件导入hello模块,在test文件中打印出hello模块的__name__属性值,显示的是hello模块的模块名
  • 因此__name__ == '__main__'就表示在当前文件中,可以在if __name__ == '__main__':条件下写入测试代码,如此可以避免测试代码在模块被导入后执行。

2.迷宫1

题目描述

给定一个 N×M 的网格迷宫 G。G 的每个格子要么是道路,要么是障碍物(道路用 1 表示,障碍物用 0表示)。

已知迷宫的入口位置为 (x1​,y1​),出口位置为 (x2​,y2​)。问从入口走到出口,最少要走多少个格子。

输入描述

输入第 1行包含两个正整数 N,M,分别表示迷宫的大小。

接下来输入一个 N×M 的矩阵。若 Gi,j​=1 表示其为道路,否则表示其为障碍物。

最后一行输入四个整数 x1​,y1​,x2​,y2​,表示入口的位置和出口的位置。

1≤N,M≤10^2,0≤Gi,j​≤1,1≤x1​,x2​≤N,1≤y1​,y2​≤M。

输出描述

输出仅一行,包含一个整数表示答案。

若无法从入口到出口,则输出 −1−1。

输入输出样例

示例 1

输入

5 5 
1 0 1 1 0
1 1 0 1 1 
0 1 0 1 1
1 1 1 1 1
1 0 0 0 1
1 1 5 5 

输出

8

思路:

1.算法开始:

我们以起点开始做BFS,将入口压入栈开始执行BFS算法

2.判断边界:

在当前行,当前列的位置上判断是否满足条件,若不满足,跳到第5步,即不符合边界条件。判断条件如下:

i.vis[x][y]>=1标记数组已经被标记已被走过,不能再走,超出边界

ii.x<1从左侧走出方格

iii.x>n从右侧走出方格

iv.y<1从上侧走出方格

v.y>n从下侧走出方格

vi.map[x][y]!=1没有路不能走

3.搜索过程:

调用Check函数。

如果边界条件满足,就继续调用搜索,找到下一步的位置每次找到下一个位置的时候,令其Vis【nectc】【necty】=当前Vis+1

这样既能用vis数组标记又能使用vis数组存步数,从1开始,即开始节点是1,所以最后要减去1。4.check(参数):

如果当搜索到x=终点x,y=终点y时,就找到了重点,此时他的Vis数组就存储了他的步数,但是是从1开始的。

from queue import Queue

dx = [0, 1, -1, 0]

dy = [1, 0, 0, -1]

# 两两组合形成上下左右四个方向
#      1------------------> x
#      |
#      |
#      |
#      |
#      |
#      |
#      |
#      ↓
#      y

# dx[0]=0 dy[0]=1 那么代表向下的方向

# dx[1]=1 dy[1]=0 那么代表向右的方向

# dx[2]=-1 dy[0]=0 那么代表向左的方向

# dx[3]=0 dy[1]=-1 那么代表向上的方向

Map = []

q = []
qfront = 0
qend = 0

n = 0
m = 0
k = 0

length = 0


def pd(x, y):
    if x < 0:
        return False

    # x 轴坐标左侧越界

    elif x >= n:
        return False
    # x 轴坐标右侧越界

    elif y < 0:
        return False
    # y轴坐标上侧越界

    elif y >= m:
        return False
    # y 轴坐标下侧越界

    elif Map[x][y] == 'g':
        return False
    # 已经长草了

    else:
        return True

    # 在范围内,且没长草


def BFS():
    global k, q, n, m, Map, length, qend, qfront
    # print("K Length", k, length)
    while k > 0 and length > 0:

        tempPair = q[qfront]


        qfront += 1
        x = tempPair[0]  # 横坐标

        y = tempPair[1]  # 纵坐标

        for i in range(4):

            nowx = x + dx[i]  # 扩展后的横坐标

            nowy = y + dy[i]  # 扩展后的纵坐标

            if (pd(nowx, nowy)):
                q.append((nowx,nowy))
                qend += 1
                Map[nowx][nowy] = 'g'
        length -= 1

        if length == 0:
            k -= 1
            length = qend - qfront



if __name__ == '__main__':

    n, m = map(int, input().split())

    Map = [[0 for _ in range(m)] for _ in range(n)]  # Python 动态开数组会减少运行时间

    for i in range(n):
        input_ = input()
        for j in range(m):
            Map[i][j] = input_[j]
            if Map[i][j] == 'g':
                q.append((i,j))
                qend += 1

    k = int(input())

    length = qend - qfront

    BFS()
    for i in range(n):
        str_temp = ''
        for j in range(m):
            str_temp += Map[i][j]
        print(str_temp)

3.迷宫2

题目描述

本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。

下图给出了一个迷宫的平面图,其中标记为 1 的为障碍,标记为 0 的为可以通行的地方。

010000
000100
001001
110000

迷宫的入口为左上角,出口为右下角,在迷宫中,只能从一个位置走到这 个它的上、下、左、右四个方向之一。

对于上面的迷宫,从入口开始,可以按 DRRURRDDDR 的顺序通过迷宫, 一共 10 步。其中 D、U、L、R 分别表示向下、向上、向左、向右走。 对于下面这个更复杂的迷宫(30 行 50 列),请找出一种通过迷宫的方式,其使用的步数最少,在步数最少的前提下,请找出字典序最小的一个作为答案。

请注意在字典序中 D<L<R<U。

01010101001011001001010110010110100100001000101010
00001000100000101010010000100000001001100110100101
01111011010010001000001101001011100011000000010000
01000000001010100011010000101000001010101011001011
00011111000000101000010010100010100000101100000000
11001000110101000010101100011010011010101011110111
00011011010101001001001010000001000101001110000000
10100000101000100110101010111110011000010000111010
00111000001010100001100010000001000101001100001001
11000110100001110010001001010101010101010001101000
00010000100100000101001010101110100010101010000101
11100100101001001000010000010101010100100100010100
00000010000000101011001111010001100000101010100011
10101010011100001000011000010110011110110100001000
10101010100001101010100101000010100000111011101001
10000000101100010000101100101101001011100000000100
10101001000000010100100001000100000100011110101001
00101001010101101001010100011010101101110000110101
11001010000100001100000010100101000001000111000010
00001000110000110101101000000100101001001000011101
10100101000101000000001110110010110101101010100001
00101000010000110101010000100010001001000100010101
10100001000110010001000010101001010101011111010010
00000100101000000110010100101001000001000000000010
11010000001001110111001001000011101001011011101000
00000110100010001000100000001000011101000000110011
10101000101000100010001111100010101001010000001000
10000010100101001010110000000100101010001011101000
00111100001000010000000110111000000001000000001011
10000001100111010111010001000110111010101101111000

题解:

from collections import *

mp = []
for i in range(30):
    mp.append(list(map(int, input())))  #输入迷宫
k = ('D', 'L', 'R', 'U')
dir = [(1, 0), (0, -1), (0, 1), (-1, 0)]
vis = [[0] * 50 for i in range(30)]
father = [[''] * 50 for i in range(30)]


def dfs(x, y):   #x,y分别表示行和列
    if x == 0 and y == 0:
        return
    if father[x][y] == 'D':  dfs(x - 1, y)  
    if father[x][y] == 'L':  dfs(x, y + 1)  
    if father[x][y] == 'R':  dfs(x, y - 1)  
    if father[x][y] == 'U':  dfs(x + 1, y)  
    print(father[x][y], end="")


def bfs(x, y):
    global father
    global vis
    q = deque()
    q.append((x, y))
    vis[x][y] = 1
    while q:
        now = q.popleft()
        if now[0] == 29 and now[1] == 49:
            return
        for i in range(4):
            next = [0, 0]
            next[0] = now[0] + dir[i][0]
            next[1] = now[1] + dir[i][1]
            if next[0] < 0 or next[0] >= 30 or next[1] < 0 or next[1] >= 50:
                continue
            if vis[next[0]][next[1]] == 1 or mp[next[0]][next[1]] == 1:
                continue
            vis[next[0]][next[1]] = 1
            q.append((next[0], next[1]))
            father[next[0]][next[1]] = k[i]


bfs(0, 0)
dfs(29, 49)

4.跳蚱蜢

题目描述

本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。

如下图所示: 有 9 只盘子,排成 1 个圆圈。 其中 8 只盘子内装着 8 只蚱蜢,有一个是空盘。 我们把这些蚱蜢顺时针编号为 1 ~ 8。

每只蚱蜢都可以跳到相邻的空盘中, 也可以再用点力,越过一个相邻的蚱蜢跳到空盘中。

请你计算一下,如果要使得蚱蜢们的队形改为按照逆时针排列, 并且保持空盘的位置不变(也就是 1−8 换位,2−7换位,...),至少要经过多少次跳跃?

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 128M

思路:

直接让蚱蜢跳到空盘有点麻烦,因为有很多蚱蜢在跳。

反过来看,让空盘跳,跳到蚱蜢的位置,简单多了,只有一个空盘在跳。

题目是一个圆圈,不好处理,用一个建模技巧“化圆为线”,把圆形转换为线形。

把空盘看成0,有9个数字{0,1,2,3,4,5,6,7,8},一个圆圈上的9个数字,拉直成了一条线上的9个数字,这条线的首尾两个数字处理成相连的。

这样就变成八数码问题:有9个数字{0, 1, 2, 3, 4, 5, 6, 7, 8},共有9!= 362880种排列,不算多

本题的初始状态是“012345678”,目标状态是“087654321”。从初始状态“012345678”跳一次,有 4 种情况:“102345678”、 “210345678”、 “812345670”、 “712345608”。然后从这 4 种状态继续跳到下一种状态,一直跳到目标状态为止。

 

用 BFS 扩展每一层。每一层就是蚱蜢跳了一次,扩展到某一层时发现了终点“087654321”,这一层的深度就是蚱蜢跳跃的最少次数。

万一蚱蜢重复跳了某一格呢?

这时候需要我们进行判重。

判重:判断有没有重复跳,如果跳到一个曾经出现过的情况,就不用往下跳了。一共只有9!= 362880种情况。

在python中,我们用字典判重

  1. 字典的创建。
    massege={'小李':'123124543643','xiaohua':'17855666','LiMing':'1249699859456'}
    
    #或者创建空的字典
    empty_dict = {}
    #或者使用元组作为key
    group_dict = {(60, 99):'good', 100:'nice'}
  2. 字典的添加。
    # 如果字典内不含有相应的Key值,则会执行添加操作
    dict[key]=value
  3. 字典的修改。
    # 如果字典内含有相应的Key值,则会执行更新操作
    dict[key]=new_value
    
    # 使用update()修改
    # update() 方法可使用一个字典所包含的 key-value 对来更新己有的字典。如果有就修改,没有就添加。
    dict.update({'key':123,'key2':234})
  4. 字典的删除。
    del dict['key']  # 删除键是'key'的条目
    dict.clear()      # 清空字典所有条目
    del dict          # 删除字典
  5. 字典的访问。
    dict = {'Name': 'Zara', 'Age': '7'} 
    print (dict['Name'])
    
    #当然如果key值不存在,将会抛出异常
    #也可以是用get()方法,不存在会返回None,但不会抛出异常
    print(dict.get('Name')) 

题解:

##例题 跳蚱蜢
from queue import *
cnt = 0
meet = False
def extend(q,m1,m2):
    global cnt
    global meet
    s = q. get ()
    for i in range (len(s) ) :
        if s[i]=='0': break
    for j in range(4):
        cnt += 1            #统计计算次数
        news = list(s)      #用list比较方便
        if j==0: news[(i-2+9) %9] , news[i]= news[i], news[(i-2+9)%9]
        if j==1: news[(i-1+9) %9], news[i] = news[i], news[(i-1+9)%9]
        if j==2: news[(i+1+9)%9], news[i] = news[i], news[(i+1+9)%9]
        if j==3: news[(i+2+9)%9], news[i] = news[i], news[(i+2+9)%9]
        a = "".join(news)#重新转成字符串
        if a in m2:
            print(m1[s] + 1 + m2[a])
            # print(cnt)#打印计算次数
            meet = True
            return
        if a not in m1:
            q. put(a)
            m1[a] = m1[s]+1#向字典中添加
    meet = False
q1 = Queue()# 正向搜索
q2 = Queue()# 逆向搜索
q1.put("012345678")
q2.put("087654321")
mp1={'012345678':0}
mp2={'087654321':0}       #定义字典,用于判重
while not q1.empty() and not q2.empty() :
    if q1.qsize() <= q2.qsize() : extend(q1, mp1, mp2)
    else:                        extend(q2, mp2, mp1)
    if meet==True: break

5.七段码

题目描述

本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。

小蓝要用七段码数码管来表示一种特殊的文字。

上图给出了七段码数码管的一个图示,数码管中一共有 77 段可以发光的二 极管,分别标记为 a,b,c,d,e,f,g。

小蓝要选择一部分二极管(至少要有一个)发光来表达字符。在设计字符 的表达时,要求所有发光的二极管是连成一片的。

例如:b 发光,其他二极管不发光可以用来表达一种字符。

例如 c 发光,其他二极管不发光可以用来表达一种字符。这种方案与上 一行的方案可以用来表示不同的字符,尽管看上去比较相似。

例如:a,b,c,d,e 发光,f,g 不发光可以用来表达一种字符。

例如:b,f 发光,其他二极管不发光则不能用来表达一种字符,因为发光 的二极管没有连成一片。

请问,小蓝可以用七段码数码管表达多少种不同的字符?

 思路:DFS+并查集

关于并查集个人感觉讲的比较好的是这篇博客:(114条消息) 数据结构-第六期——并查集(Python)_并查集python_让机器理解语言か的博客-CSDN博客icon-default.png?t=N2N8https://blog.csdn.net/m0_69478345/article/details/128620453?ops_request_misc=&request_id=&biz_id=102&utm_term=%E5%B9%B6%E6%9F%A5%E9%9B%86python&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-3-128620453.142^v76^control_1,201^v4^add_ask,239^v2^insert_chatgpt&spm=1018.2226.3001.4187

并查集其实属于拉帮结派的过程,当一个人A创立了一个帮派1,他就需要拉拢更多人加入他的帮派,他拉拢了很多人B,C,D等等,B,C,D发现他们都被A拉过,B还拉拢了E,于是他们便进行合并成一个大帮派。

如果帮派1被帮派2收购了,那么帮派1的所有成员都被合并到帮派2中,而帮派1成为了帮派2的子分支

 

 实现并查集

第一步:存储

第二步:查询

 

 第三步:路径压缩

 

第四步:合并

 

 

题目:合根植物

题目描述

w 星球的一个种植园,被分成m×n 个小格子(东西方向 m 行,南北方向 n 列)。每个格子里种了一株合根植物。

这种植物有个特点,它的根可能会沿着南北或东西方向伸展,从而与另一个格子的植物合成为一体。

如果我们告诉你哪些小格子间出现了连根现象,你能说出这个园中一共有多少株合根植物吗?

输入描述

第一行,两个整数m,n,用空格分开,表示格子的行数、列数(1≤m,n≤1000)。

接下来一行,一个整数 k (0≤k≤105 ),表示下面还有 k 行数据。

接下来 k 行,每行两个整数 a,b,表示编号为 a 的小格子和编号为 b 的小格子合根了。

格子的编号一行一行,从上到下,从左到右编号。

比如:5×4 的小格子,编号:

1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
17 18 19 20

输出描述

输出植物数量。

输入输出样例

示例

输入

5 4
16
2 3
1 5
5 9
4 8
7 8
9 10
10 11
11 12
10 14
12 16
14 18
17 18
15 19
19 20
9 13
13 17

输出

5

样例说明

其合根情况参考下图:

题解:

Maxn = 2000000
# 假设所需的数量为200
fa = []
def init():
    for i in range(Maxn + 1):
        fa.append(i)
def find(x):
    if fa[x] == x:
        return x
    else:
        fa[x] = find(fa[x])
        #父节点设为根节点
        return  fa[x]
        # 返回父节点
def merge(x,y):
    fa[find(x)] = find(y)
if __name__ == '__main__':
    n,m=input().split()
    k=input()
    n=int(n)
    m=int(m)
    k=int(k)
    Maxn=m*n+100
    init()
    for _ in range(k):
        a,b= input().split()
        a=int(a)
        b=int(b)
        merge(a,b)
    ans=0
    for i in range(m*n): #从0开始
        index=i+1 #调整一下,应该是从1开始
        if(fa[index]==index):
            ans+=1

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值