DFS排列组合与连通性

目录

一、前言

二、DFS与排列组合

1、DFS:自写排列算法1

(1)基础模板

(2)基于(1)输出前n个数任意m个都全排列

2、DFS:自写排列算法2(这个写法更常见)

(1)从小到大打印 n 个数的全排列

(2)从小到大打印n个数中任意m个数的排列

3、DFS:自写组合算法

4、例题——迷宫(2017年省赛,lanqiaoOJ题号641)

5、例题——寒假作业(2016年省赛,lanqiaoOJ题号1388)

(1)简单做法 (超时)

(2)自写全排列

三、DFS连通性判断

1、例题——全球变暖(2018年省赛,lanqiaoOJ题号178)


一、前言

该篇博文主要讲利用Python实现排列组合与连通性判断。

二、DFS与排列组合

求排列的系统函数,例如 Python 的 permutations()。

在某些场景下,permutations() 不能用,需要手写代码实现排列。本讲给出例题。

手写排列组合代码、暴力法、二进制法。

1、DFS:自写排列算法1

(1)基础模板

让第一个数不同,得到 n 个数列

把第 1 个和后面每个数交换。

1 2 3 4 5 ...... n

2 1 3 4 5 ...... n

......

n 2 3 4 5 ...... 1

这 n 个数列,只要第一个数不同,不管后面 n-1 个数怎么排,这 n 个数列都不同。

在上面的每个数列中,去掉第一个数,对后面的 n-1 个数进行类似的排列。例如从第 2 行的 { 2 1 3 4 5 ..... n } 进入第二层。先去掉首位 2,然后对 n-1 个做排列

1 3 4 5 ...... n

3 1 4 5 ...... n

......

n 3 4 5 ...... 1

这 n-1 个数列,只要第一个数不同,不管后面 n-2 个数怎么排,这n-1个数列都不同。这是递归的第二层。(自顶向下:这就是 DFS 的思想)

def dfs(s,t):
    if s==t:        #递归结束,产生一个全排列
        print(a[0:n])
    else:
        for i in range(s,t+1):
            a[s],a[i]=a[i],a[s]   #交换
            dfs(s+1,t)            #缩小范围
            a[i],a[s]=a[s],a[i]   #恢复

a=[1,2,3,4,5,6,7,8,9]
n=3
dfs(0,n-1)      #求前n个数的全排列

上面的代码为什么这样写,自己画棵树出来看看就明白其中的逻辑了。很多时候递归代码为什么没看懂也写不出来,就是因为你不会画树也不理解树。

但上面的代码有一个致命的缺点:不能按从小到大的顺序打印排列。

(2)基于(1)输出前n个数任意m个都全排列

如前4个数任选3个。

def dfs(s,t):
    #global cnt
    if s==3:        #递归结束,产生一个全排列
        print(a[0:3])
        #cnt+=1
    else:
        for i in range(s,t+1):
            a[s],a[i]=a[i],a[s]   #交换
            dfs(s+1,t)            #缩小范围
            a[i],a[s]=a[s],a[i]   #恢复

#cnt=0
a=[1,2,3,4,5,6,7,8,9]
n=4
dfs(0,n-1)      #求前n个数的全排列,C43A33
#print(cnt)

2、DFS:自写排列算法2(这个写法更常见)

(1)从小到大打印 n 个数的全排列

例:前 3 个数的全排列

def dfs(s,t):
    if s==t:
        print(b[0:n])   #递归结束,输出一个全排列
    else:
        for i in range(t):
            if vis[i]==False:
                vis[i]=True
                b[s]=a[i]   #存排列
                dfs(s+1,t)
                vis[i]=False

a=[1,2,3,4,5,6,7,8,9]
b=[0]*10            #记录生成的一个全排列
vis=[False]*10      #记录第i个数是否用过
n=3
dfs(0,n)

(2)从小到大打印n个数中任意m个数的排列

def dfs(s,t):
    if s==3:
        print(b[0:3])   #递归结束,输出一个排列
    else:
        for i in range(t):
            if vis[i]==False:
                vis[i]=True
                b[s]=a[i]   #存排列
                dfs(s+1,t)
                vis[i]=False

a=[1,2,3,4,5,6,7,8,9]
b=[0]*10            #记录生成的一个全排列
vis=[False]*10      #记录第i个数是否用过
n=4
dfs(0,n)    #前n个数的排列

3、DFS:自写组合算法

dfs 时,选或不选第 k 个数,就实现了各种组合

例(1) :打印二进制数。以打印 000~111 为例

(如果要反过来打印,只需要交换 8、10 行)

vis=[0]*10
def dfs(k):     #深搜到第k个
    if k==3:
        for i in range(3):
            print(vis[i],end='')
            print()
    else:
        vis[k]=0    #不选第k个
        dfs(k+1)    #继续搜下一个
        vis[k]=1    #选第k个
        dfs(k+1)    #继续搜下一个
dfs(0)

例(2):打印组合。

以 3 个数  {1, 2, 3} 为例,把上面的代码与需要打印的数列结合

def dfs(k):     #深搜到第k个
    if k==3:
        for i in range(3):
            if vis[i]==1:
                print(a[i],end='')
        print()
    else:
        vis[k]=0    #不选第k个
        dfs(k+1)    #继续搜下一个
        vis[k]=1    #选第k个
        dfs(k+1)    #继续搜下一个

vis=[0]*10
a=[1,2,3,4,5,6,7,8,9,10]
dfs(0)

4、例题——迷宫(2017年省赛,lanqiaoOJ题号641)

【问题描述】

给出一个迷宫,问迷宫内的人有多少能走出来。迷宫如下:其中 L 表示向左走,R 表示向右走,U 表示向上走,D 表示向下走。

函数 dfs(i, j):

判断从坐标点 (i,j) 出发,是否能走出去。

能走出去:返回1

否则:返回0

dfs():递归,在每个点,它根据指示牌向上、下、左、右四个方向走。

dfs() 结束的条件:

1)走出了迷宫,返回1。

2)走不出迷宫,返回0。

什么情况下走不出迷宫?

兜圈子,回到了曾经走过的点。

用 vis[i][j] 记录点 (i, j) 是否曾经走过,如果走过,就是兜圈子。

def dfs(x,y):
    if x<0 or y<0 or x>=10 or y>=10:
        return 1
    if vis[x][y]==1:
        return 0
    vis[x][y]=1
    if mp[x][y]=="L":
        return dfs(x,y-1)
    if mp[x][y]=="R":
        return dfs(x,y+1)
    if mp[x][y]=="U":
        return dfs(x-1,y)
    if mp[x][y]=="D":
        return dfs(x+1,y)

mp=[[''*10] for i in range(10)]     #二维矩阵存迷宫
for i in range(10):
    mp[i]=list(input())         #读迷宫
ans=0
for i in range(10):
    for j in range(10):
        vis=[[0]*10 for _ in range(10)]   #初始化vis[][]
        if dfs(i,j)==1:
            ans+=1
print(ans)

迷宫有 n 行 n 列,做一次 dfs(),最多需要走遍所有的点,即 O(n^2) 次;

每个点都做一次 dfs(),总复杂度 O(n^4)。

能优化吗?

  • 用不着对每个点都做一次 dfs()。
  • 从一个点出发,走过一条路径,最后走出了迷宫,那么以这条路径上所有的点为起点,都能走出迷宫;
  • 若这条路径兜圈子了,那么这条路径上所有的点都不能走出迷宫。如果对路径进行记录,就能大大减少计算量。

5、例题——寒假作业(2016年省赛,lanqiaoOJ题号1388)

(1)简单做法 (超时)

用 permutations() 函数,生成所有的排列,检查是否合法

运行时间极长!

13个数的排列:13!= 6,227,020,800

可能需要数小时

(2)自写全排列

不需要生成一个完整排列。例如一个排列的前 3 个数,如果不满足 “□+□=□”,那么后面的 9 个数不管怎么排列都不对。这种提前终止搜索的技术叫 “剪枝”。

def dfs(num):
    global ans
    if num==13:
        if b[10]==b[11]*b[12]:
            ans+=1
        return
    if num==4 and b[1]+b[2]!=b[3]:
        return #剪枝
    if num==7 and b[4]-b[5]!=b[6]:
        return
    if num==10 and b[7]*b[8]!=b[9]:
        return
    for i in range(1,14):
        if not vis[i]:
            b[num]=i
            vis[i]=1
            dfs(num+1)
            vis[i]=0
ans=0
b=[0]*15
vis=[0]*15
dfs(1)
print(ans)

三、DFS连通性判断

连通性判断:图论的一个简单问题,给定一张图,图由点和连接点的边组成,要求找到图中互相连通的部分。

1、例题——全球变暖(2018年省赛,lanqiaoOJ题号178)

【题目描述】

你有一张某海域 NxN 像素的照片,"." 表示海洋、"#" 表示陆地,如下所示:

其中 "上下左右 " 四个方向上连在一起的一片陆地组成一座岛屿。例如上图就有 2 座岛屿。由于全球变暖导致了海面上升,科学家预测未来几十年,岛屿边缘一个像素的范围会被海水淹没。具体来说如果一块陆地像素与海洋相邻 (上下左右四个相邻像素中有海洋),它就会被淹没。例如上图中的海域未来会变成如下样子:

请你计算:照片中有多少岛屿会被完全淹没。照片保证第 1 行、第 1 列、第 N 行、第 N 列的像素都是海洋。

【输入描述】

第一行包含一个整数 N (1<N<1000)。以下 N 行 N 列代表一张海域照片。

【输出描述】

输出一个整数表示答案。

连通性问题,计算步骤:

  • 遍历一个连通块 (找到这个连通块中所有的'#',标记已经搜过,不用再搜);
  • 再遍历下一个连通块……;
  • 遍历完所有连通块,统计有多少个连通块。

  • 什么岛屿不会被完全淹没?若岛中有个陆地 (称为高地),它周围都是陆地,那么这个岛不会被完全淹没。
  • 用 DFS 搜出有多少个岛(连通块),检查这个岛有没有高地,统计那些没有高地的岛 (连通块) 的数量,就是答案。
  • 计算复杂度:每个像素点只用搜一次且必须至少搜一次,共 N^2 个点,DFS 的复杂度是 O(N^2),不可能更好了。

  • 从图上任意一个点 u 开始遍历,标记 u 已经搜过。
  • 递归 u 的所有符合连通条件的邻居点。
  • 递归结束,找到了与 u 连通的所有点,这是一个连通块。
  • 不与 u 连通的、其他没有访问到的点,继续用上述步骤处理,找到所有的连通块。

import sys
sys.setrecursionlimit(60000)    #设置递归深度,否则不能通过100%的测试
def dfs(x,y):
    d=[(0,1),(0,-1),(1,0),(-1,0)]
    global flag
    global vis
    global mp
    vis[x][y]=-1
    if mp[x][y+1]=='#' and mp[x][y-1]=='#' and mp[x+1][y]=='#' and mp[x-1][y]=='#':
        flag=1
    for i in range(4):
        nx=x+d[i][0]
        ny=y+d[i][1]
        if vis[nx][ny]==0 and mp[nx][ny]=='#':
            dfs(nx,ny)

n=int(input())
mp=[]
for i in range(n):
    mp.append(list(input()))
vis=[]
for i in range(n):
    vis.append([0]*n)
ans=0
for i in range(n):
    for j in range(n):
        if vis[i][j]==0 and mp[i][j]=='#':
            flag=0
            dfs(i,j)
            if flag==0:
                ans+=1
print(ans)

以上,DFS排列组合与连通性

祝好

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吕飞雨的头发不能秃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值