算法第十期——DFS(深度优先搜索)的剪枝优化

news2025/4/5 18:34:26

目录

DFS:剪枝

DFS:有哪些剪枝方法

DFS例题一:剪格子 

【思路】 

 DFS例题二:路径之谜

【样例分析】 

DFS例题三:四阶幻方

【思路】

【做法一】  

 【做法二】

DFS例题三:分考场

【样例分析】 

【思路】

DFS习题


DFS:剪枝

剪枝:把不会产生答案的或不必要的枝条“剪掉”。
剪枝的关键:剪什么枝、在哪里减。
剪枝是搜索常用的优化手段,常常能把指数级的复杂度,优化到近似多项式的复杂度。

DFS:有哪些剪枝方法

  • 可行性剪枝:对当前状态进行检查,如果当前条件不合法就不再继续,直接返回。
  • 搜索顺序剪枝搜索树有多个层次和分支,不同的搜索顺序会产生不同的搜索树形态。
  • 最优性剪枝在最优化问题的搜索过程中,如果当前花费的代价已超过前面搜索到的最优解,那么本次搜索已经没有继续进行下去的意义,停止对当前分支的搜索。
  • 排除等效冗余搜索的不同分支,最后的结果是一样的,那么只搜一个分支就够了。
  • 记忆化搜索:在递归的过程中,有许多分支被反复计算,会大大降低算法的执行效率。将已经计算出来的结果保存起来,以后需要用到的时候直接取出结果,避免重复运算,从而提高了算法的效率。

DFS例题一:剪格子 

剪格子lanqiao0J题号211

【题目描述】

如下图所示,3 x 3 的格子中填写了一些整数。

我们沿着图中的红色线剪开,得到两个部分,每个部分的数字和都是 60。

本题的要求就是请你编程判定:对给定的 m×n 的格子中的整数,是否可以分割为两个部分,使得这两个区域的数字和相等

如果存在多种解答,请输出包含左上角格子的那个区域包含的格子的最小数目

如果无法分割,则输出 0

【输入描述】

程序先读入两个整数 m,n 用空格分割 (m,n<10),表示表格的宽度和高度。

接下来是 n 行,每行 m 个正整数,用空格分开。每个整数不大于 10^4。

【输出描述】

在所有解中,包含左上角的分割区可能包含的最小的格子数目。

【输入输出样例】

输入

3 3
10 1 52
20 30 1
1 2 3

输出

3

【思路】 

 一道极典型的DFS题
做法:求所有格子的和sum,然后用DFS找一个联通区域,看这个区域的和是否为sum/2。
剪枝如果DFS到的部分区域的和已经超过sum/2,就不用继续DFS了。
这种格子DFS搜索题,是蓝桥杯的常见考题。

def dfs(x,y,c,s):                      # x,y是坐标点,c是格子数,s是求得的和
    global sum_num,ans
    if 2*s > sum_num: return           # 剪枝:当前s超过sum的一半就不需要继续
    if 2*s == sum_num:                 # 终止条件:当前s刚好是总面积的一半
        if ans>c and vis[0][0]== 1: ans = c # 格子数更小且访问过左上角,保留该格子数
        return
    vis[x][y] = 1                      # 保存现场:标记为访问过
    # 下面代码是对格子进行遍历
    dir = [(1,0),(-1,0),(0,-1),(0,1)]  # 四个方向
    for u, v in dir:                   # 在当前坐标往4个方向走
        tx,ty = x+u,y+v
   # 上面两行还可以这样写
   #for u in  range(4):
   #    tx,ty = x+dir[u][0],y+dir[u][1]
        if 0 <= tx <= n-1 and 0 <= ty <= m - 1: # 不能出界
            if vis[tx][ty] == 0:                # 没有访问过
                dfs(tx,ty, c+1, s+a[x][y])      # 访问下一个点
    vis[x][y] = 0                               # 恢复现场

m, n = map (int,input ().split())       # 格子的宽和高(n行m列)
a = [list (map(int,input ().split())) for _ in range(n)]    # 存格子:每次读一行
vis = [[0]*m for _ in range(n)]         # 记录访问情况
sum_num = 0
for i in a:
    sum_num += sum(i)                   # 对a求和      sum(i):第i行的和
ans = 1000000                           # ans是最小格子数,初始化为无穷大
dfs (0,0,0,0)                           # 搜索最少格子数
print(ans)

 DFS例题二:路径之谜

路径之谜2016年第七届决赛,lanqiao0J题号89

【题目描述】

小明冒充 XX 星球的骑士,进入了一个奇怪的城堡。城堡里边什么都没有,只有方形石头铺成的地面。假设城堡地面是 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

【样例分析】 

首先可以把靶子上四支箭的位置先确定下来(用绿色标记),然后再确定其他靶子所在的行和列的位置,发现第一行的两支箭已经确定了一支,而另一只箭只能出现在点4(用红色标记),若出现在点8或点12则没办法走到东南角。那么剩下的箭的位置也确定了(用红色标记)。

  • DFS:题目要求输出一条路径,用DFS很合适,DFS搜索过程中,自然生成一条路径。
  • 剪枝每走到一个格子,对应的靶子上箭多一支,靶子上的箭等于给定的数字后,就不用再DFS下去了。(或者做减法,靶子的数字减到0)
  • 记录路径的技巧:根据题目的要求,来跟踪DFS的过程,记录DFS走过的路径,是最方便的。DFS到某个格子时,把这个格子放到栈里,表示路径增加了这个格子。DFS回溯的时候,退出了这个格子,表示路径上不再包括这个格子,需要从栈中弹走这个格子。 
def dfs(x, y):
    if a[x]<0 or b[y]<0: return         # 剪枝:有靶子上的箭数<0
    if x==n-1 and y==n-1:               # 终止条件:到达终点
        ok=1
        for i in range(n) :             # 到达终点但箭数对不上,排除
            if a[i]!=0 or b[i]!=0: ok=0 ; return
        if ok==1 :                      # # 到达终点且箭数对上,把path的路径打印出来
            if a[i]!=0 or b[i]!=0: ok=0 ; return
            for i in range(len(path)) : print(path[i], end=' ')
    # 遍历每一点
    for d in [(1,0),(-1,0),(0,1),(0,-1)]:
        tx = x+d[0]; ty = y+d[1]
        if 0<= tx < n and 0<= ty < n and vis[tx][ty] == 0: # 没有越界且没有访问过
            # 下面三行是保护现场
            vis[tx][ty] = 1
            path. append(tx*n + ty)     # 进栈,记录路径。tx*n + ty:该点的编号
            a[tx] -= 1; b[ty] -= 1      # 经过这个点,这个点的行和列的箭数-1
            # 搜索下一个
            dfs (tx,ty)
            # 下面三行是恢复现场
            path. pop ()                # 出栈,DFS回溯
            a[tx] += 1 ; b[ty] += 1
            vis[tx][ty] = 0

n = int (input())
vis = [[0]*n for i in range(n)] # 记录访问情况
b = list (map(int,input ().split()))    # 北边靶子上箭数
a = list (map(int,input ().split()))    # 西边靶子上箭数
path = [0]             # 用栈记录路径,初始化一个0,因为从编号0(西北角)开始的
vis[0][0]=1            # 从左上角出发,标记为已访问
a[0] -= 1; b[0] -= 1   # 经过该点对应方位上的箭-1
dfs (0,0)              # 从坐标点(0,0)开始搜索

DFS例题三:四阶幻方

四阶幻方2015年第六届决赛,lanqiao0J题号689

【题目描述】

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

把 1 ~ 16 的数字填入 4×4 的方格中,使得行、列以及两个对角线的和都相,满足这样的特征时称为:四阶幻方。

四阶幻方可能有很多方案。如果固定左上角为 1,请计算一共有多少种方案。

比如:

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

以及:

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

就可以算为两种不同的方案。

【思路】

暴力解决:除了1,循环遍历数字2~16,有15!=1.3×10^12种排列,但时间太久,无法把所有排列都试一遍。
剪枝:每种排列,只要前面一些数字不适合,就不用再计算下去了。(类似于“暑假作业”的剪枝)

【做法一】  

正常剪枝:按顺序来剪枝,先搜索到第4个,剪枝第一行(浅蓝色),搜索到第8个和第12个,分别剪枝第二行和第三行(浅蓝色),搜索到第13个,剪枝第一列和一条对角线(红色)。搜索到第13和第14个,分别剪枝第二列和第三列(绿色)。搜索到16个时,再判断最后一行和最后一列和一条对角线(深蓝色),如果满足要求,ans+1。

def dfs(n):
    global ans
    if n>=4 and a[0]+a[1]+a[2]+a[3]!=34:            #各种剪枝情况
        return
    if n>=8 and a[4]+a[5]+a[6]+a[7]!=34:
        return
    if n>=12 and a[8]+a[9]+a[10]+a[11]!=34:
        return
    if n>=13 and (a[3]+a[6]+a[9]+a[12]!=34 or a[0]+a[4]+a[8]+a[12]!=34):
        return
    if n>=14 and a[1]+a[5]+a[9]+a[13]!=34:
        return
    if n>=15 and a[2]+a[6]+a[10]+a[14]!=34:
        return
    if n>=16 and (a[12]+a[13]+a[14]+a[15]!=34 or a[0]+a[5]+a[10]+a[15]!=34 or a[3] + a[7] + a[11] + a[15]!= 34):
        return
    if n==16:               #到达最后,且符合条件
        ans+=1
    for i in range(2,17):   #分别排列2-16这些数
        if vis[i]==0:
            a[n]=i          
            vis[i]=1
            dfs(n+1)
            vis[i]=0

ans=0
a=[0]*17
vis=[0]*17
a[0]=1              #固定a[0]是1
vis[1]=1
dfs(1)
print(ans)
#print(416)

运行时间:2min,有点久!

 【做法二】

尽快剪枝可以减少运行时间! 

0-15的编号不一定要按顺序来排,因为每个位置都需要遍历2~16这些数,每个位置其实是一样的,所以可以按照最快剪枝的方式来编号。 下面这个方式在n小于12的情况下剪枝4次,而按顺序来排的情况只剪枝2次。

  

比如说搜索到7这个位置,我就可以判断第一列了 。

如果按照原来0-15的顺序来编,要搜到m[13]才能判断第一列的情况 

def dfs(n):
    global cnt
    # 剪枝
    if n >= 4 and m[0] + m[1] + m[2] + m[3] != 34: return     # 第一行
    if n >= 7 and m[0] + m[4] + m[5] + m[6] != 34: return     # 第一列
    if n >= 10 and m[1] + m[7] + m[8] + m[9] != 34: return    # 第二列
    if n >= 11 and m[3] + m[6] + m[8] + m[10] != 34: return   # 对角线
    if n >= 12 and m[4] + m[7] + m[10] + m[11] != 34: return  # 第二行
    if n >= 14 and m[5] + m[8] + m[12] + m[13] != 34: return  # 第三行
    if n >= 15 and m[2] + m[10] + m[12] + m[14] != 34: return # 第三列
    if n == 16 and (m[6] + m[9] + m[14] + m[15] != 34         # 最后一行/最后一列/对角线
                    or m[3] + m[11] + m[13] + m[15] != 34
                    or m[0] + m[7] + m[12] + m[15] != 34) :return
    if n == 16: cnt += 1
    # 对每个位置用2~16去遍历
    for i in range(2,17):   #2~16的全排列
        if vis[i] == 0:
            m[n]=i
            vis[i] = 1  # 保护现场
            dfs(n + 1)
            vis[i] = 0  # 恢复现场

cnt = 0
m = [0]*17    # 一维数组表示幻方
m[0] = 1       # 1被固定
vis = [0]*17
vis[1] = 1
dfs(1)       # 从1开始搜索
print(cnt)

运行时间:2s 

DFS例题三:分考场

分考场2017年第八届决赛,lanqiaoo题号109

【题目描述】

n 个人参加某项特殊考试。

为了公平,要求任何两个认识的人不能分在同一个考场。

求是少需要分几个考场才能满足条件。

【输出描述】

输入格式:

第一行,一个整数 n (1≤n≤100),表示参加考试的人数

第二行,一个整数 m,表示接下来有 m 行数据

以下 m 行每行的格式为:两个整数 a,b,用空格分开 (1≤a,b≤n )表示第 a 个人与第 b 个人认识。

【输出描述】

输出一行一个整数,表示最少分几个考场。

【输入输出样例】

输入

5
8
1 2
1 3
1 4
2 3
2 4
2 5
3 4
4 5

输出

4

【样例分析】 

五个人 前三个关系1 2,1 3,1 4说明1不能和234同一个考场,至少两个考场;关系2 3和2 4说明2和3 4不能同一个考场,至少三个考场。关系3 4说明3和4不能一个考场,至少四个考场,剩下的关系2 5和4 5说明5不能和2 4同考场,可以和1 3同考场,所以需要四个考场。

【思路】

  • 从第1个考场开始,逐个加入考生。每新加进来一个人x,都与已经开设的考场里面的人进行对比,如果认识,就换个考场。直到找到一个考场,考场里面所有的人都不认识x,x就可以坐在这里。如果所有已经开设的考场都有熟人,就开一个新考场给x坐。
  • 这个模拟的结果是得到了一个可行的考场安排,但这个安排的考场数量不一定是最少的。
  • 题目求最少考场数量,需要把所有可能的考场安排都暴力地试一遍,找到最少的那个考场安排。

用DFS来暴力搜索:

  • 用DFS搜索所有可能的情况,得到最少考场。暴力搜索所有的考场安排,计算量很大
  • 剪枝:用剪枝来减少搜索。在搜索一种新的可能的考场安排时,如果需要的考场数量已经超过了原来某个可行的考场安排,就停止(最优性剪枝)
def dfs (x, room) :
    global num, p
    if room > num : return  # 剪枝:需要的考场数量已经错过了原来某个可行的考场安排,停止
    if x > n :      # 终止条件:人数超过了
        if room< num : num = room # 当前考场数小于最佳考场数,更新最佳考场数
        return
    # 1~room考场能坐
    for j in range(1, room+1):
        k = 0
        while p[j][k] and a[x][p[j][k]] == 0: # 若该考场有人且两人不认识
            k += 1          # 可以在该考场,座位号每次+1,直到找到没有人的座位。
        if p[j][k] == 0 :   # k座位没人坐
            p[j][k] = x     # 保护现场:第j考场的k座位,安排x坐
            dfs(x+1 , room) # 安排下一人
            p[j][k]= 0      # 恢复现场:回溯,放开这个位置
    # 1~room考场都不能坐
    p[room+1][0] = x    # x只能坐第room+1个考场的第一个位置
    dfs (x+1 , room+1)  # 继续安排第x+1人,试试第1~第room+1考场
    p[room+1][0] = 0    # 回溯

n = int (input ())
m = int (input())
num = 110
p = [[0 for i in range(n+1)]for j in range(n+1)]      # 二维数组存考场:第一维:考场号、第二维:座位号
a = [[0 for i in range(n+1)]for j in range(n+1)]        # 二维数组存关系
for i in range (m) :
    u, v = map(int, input ().split())
    a[u][v] = a[v][u] = 1                               # 将两人的关系存入a
dfs(1,0)        # 从第一个人开始搜索
print (num)

DFS习题

https://www.lanqiao.cn/problems/题号/learning/

大家只需要将下面题号补充在上面链接的题号就能找到这一题 

青蛙跳杯子102,发现环108,合根植物110,填字母游戏113,机器人塔118,四平方和122,取球博弈123,卡片换位125、生命之树131,穿越雷区141、长草149、小朋友崇拜圈182,剪格子211,版本分支223,迷宫与陷阱229,调手表230,分考场237,最长子序列244,九宫重排261,网络寻路263,危险系数264,约数倍数选卡片265,字母阵列621,魔方状态643,算式649,凑平方数653,方格填数664,完美正方形685,五星填数687,生成回文数691,走迷宫1216,N皇后问题1508,最少操作数1509。
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/161138.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

TPM零知识学习十一 —— tpm全安装流程复盘(下)

接前一篇文章《TPM零知识学习十 —— tpm全安装流程复盘&#xff08;中&#xff09;》&#xff0c;链接为&#xff1a; TPM零知识学习十 —— tpm全安装流程复盘&#xff08;中&#xff09;_蓝天居士的博客-CSDN博客 五、TPM模拟器做成服务 本步骤前导步骤参见 《TPM零知识学…

Docker——网络配置

目录 本次目标&#xff1a; 一、Docker网络配置 1.bridge模式(默认模式) 2.host模式 二、bridge模式 三、host模式 网络模式与数据卷容器挂载的操作 四、如何创建自定义网络 本次目标&#xff1a; Docker网络配置Docker部署SpringCloud项目 一、Docker网络配置 Dock…

蓝牙耳机啥牌子音质好?听音乐最好的蓝牙耳机分享

现如今&#xff0c;蓝牙耳机越来越成为外出携带设备使用频率最高的数码产品之一。随着技术的进步&#xff0c;蓝牙耳机在音质上的表现也受到了很多人的关注。在这&#xff0c;我来给大家介绍四款音质很不错的蓝牙耳机&#xff0c;一起来看看吧。 一、南卡小音舱蓝牙耳机 售价…

SpringBoot+Vue项目医护人员排班系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7/8.0 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 浏…

Unity3d VFX 采用skinned mesh sampling(蒙皮采样)发射粒子效果时fbx模型是否优化(Optimized)带来的问题记录

前言&问题 如题的问题困扰了很久&#xff0c;最近终于在机缘巧合下解决了&#xff0c;这里特此记录分享一下&#xff0c;可能会对此有问题的朋友有点引导。 之前在弄一个通过摄像头/或视频识别人物的肢体动作并同步的功能&#xff1a; 详细功能可以去B站查看一下。https…

python学习 --- 列表基础

目录 一、列表的创建 1、使用中括号 2、使用中括号和内置函数list() 二、列表的特点 三、获取列表中元素 1、获取列表中指定元素的索引-index() 2、获取列表中单个元素 3、获取列表中多个元素-切片 四、列表元素的查询和遍历 1、判断指定元素在列表中是否存在 2、遍历…

为什么使用 golang http包 会把 linux 句柄打满?

最近工作的时候一个接入服务需要测性能测试&#xff0c;万万没想到测出了一个把 linux 句柄打满的问题 具体是什么问题呢&#xff0c;我们一起来看看 正常操作 项目中&#xff0c;有一些 http 请求是这样写的&#xff1a; 请求 https 的地址&#xff0c;为了绕过 tls &…

【论文精读】基于流序列的基于残差图卷积网络的匿名网络流量识别

Flow Sequence-Based Anonymity Network Traffic Identification with Residual Graph Convolutional Networks 基于流序列的基于残差图卷积网络的匿名网络流量识别 摘要 从网络流量中识别匿名服务是网络管理和安全的关键任务。 目前&#xff0c;一些基于深度学习的工作已经…

JavaScript 事件案例

文章目录JavaScript 事件案例统计输入字符数量验证输入内容搜索框复选框的全选和反选下拉列表自定义鼠标右键菜单ctrlenter发送消息规定范围内拖拽元素两元素碰撞切换图片内容滚动条鼠标滚动缩放图片JavaScript 事件案例 统计输入字符数量 <!DOCTYPE html> <html>…

一文看懂C/C++编译过程以及g++编译选项

前言在linux系统下&#xff0c;输入man g&#xff0c;即可以看到gcc官方文档对gcc编译选项的详细说明&#xff0c;本文也主要是在官方文档基础上&#xff0c;对gcc/g编译过程和一些编译选项进行了总结和说明&#xff0c;希望对学习这块内容的人有所帮助。1、编译的四个阶段一般…

Cannot read properties of null (reading ‘pickAlgorithm‘)

2568 verbose node v16.15.0 2569 verbose npm v8.5.5 2570 error Cannot read properties of null (reading ‘pickAlgorithm’) 2571 verbose exit 1 2572 timing npm Completed in 20918ms 2573 verbose unfinished npm timer reify 1673607123032 2574 verbose unfinished …

openSUSE-Leap-15.4系统分析和微调指南-略读笔记

openSUSE-Leap-15.4系统分析和微调指南——略读笔记 openSUSE Leap 15.4 System Analysis and Tuning Guide SUSE 产品文档-中文(简体)-下载中心 https://documentation.suse.com/zh-cn/sled/15-SP4/ 单击English选择简体中文——单击PDF是可以下载的&#xff0c;例如System A…

学习记录:featurecounts

Input one or more files of aligned reads (short or long reads) in either SAM or BAM formata list of genomic features in either Gene Transfer Format (GTF) or General Feature Format (GFF) or Simplified Annotation Format (SAF)比对后产生的bam或者sam文件&#…

一次简单的本机调试webshell的经历

环境安装 安装php和nginx&#xff0c;不再赘述 apt-get update apt-get install nginx这里我的版本是php7.4 # php -v PHP 7.4.33 (cli) (built: Jan 6 2023 16:10:36) ( NTS ) Copyright (c) The PHP Group Zend Engine v3.4.0, Copyright (c) Zend Technologies with Zend …

Optional最佳实践(对象操作利器)

前篇文章已经总结了集合的操作是如何在Java8优化的&#xff1a;函数编程和Stream_txxs的博客-CSDN博客&#xff0c;这篇文章总结一下对于对象如何利用Java8进行操作&#xff0c;这样对于大部分代码都可以用Java8的语法进行操作了。 一、 Optional 是什么 Optional 的作者 Bri…

收购淘米后,MMV加速走向迪士尼式IP开发之旅

一家纯正的元宇宙控股公司与经典IP公司碰撞&#xff0c;能擦出怎样的火花&#xff1f;这个问题或将在不远的将来得出答案。 1月12日晚间&#xff0c;刚在一周前以SPAC方式登陆纳斯达克的MMV&#xff08;MultiMetaVerse&#xff0c;即“元宇宙控股”&#xff09;&#xff0c;宣…

C语言进阶——动态内存管理

目录 一. 为什么存在动态内存分配 二. 动态内存函数 1.malloc 2.free 3.calloc 4.realloc 三. 常见的动态内存错误 一. 为什么存在动态内存分配 在此之前&#xff0c;我们为数组开配空间都是这样的 int nums[10]{0}; 但这样会有很大的局限性 1. 空间开辟大…

十三、Gtk4-TfeTextView函数

TfeTextView相关函数在这一章节介绍 1 tfetextview.h 头文件tfetextview.h提供了: TfeTextView的类型&#xff0c;是TFE_TYPE_TEXT_VIEW。G_DECLARE_FINAL_TYPE的扩展包含了一些有用的宏。定义了open-response信号的常量。tfetextview.c的公共函数被声明。 因此&#xff0c…

「布道人生」第一期:阿里云DevOps资深专家章屹

本期嘉宾——章 屹 阿里云 DevOps 资深专家。2012 年加入阿里巴巴&#xff0c;十年如一日专注在 DevOps 领域的理论、咨询、解决方案和产品技术工作上。作为 CIO 学院和阿里云大学的讲师&#xff0c;为众多大型企业管理高层讲授 DevOps 课程&#xff0c;探讨通过 DevOps 提升企…

drawCell | 不会画细胞结构图就用这个R包吧~ Super Nice!~

1写在前面 我们在paper中经常需要画到细胞结构图&#xff0c;新手ppt一点一点画&#xff0c;高手可能会用AI手搓&#xff0c;土豪直接使用BioRender。&#x1f912; 今天给大家大家分享一个代码画细胞结构图的R包&#xff0c;如果你觉得自己不会写代码&#xff0c;不想看了&…