程序员的算法趣题Q38: 填充白色

目录

1. 问题描述

2. 解题分析

2.1 Naive approach

2.2 最大路径问题--广度优先搜索

2.3 节点状态表示及翻转操作

3. 代码及测试

4. 后记


1. 问题描述

        题设中所说的“。。。要按照能以最多次数把所有方格。。。”的提法疑似笔误,应该为最少次数。这个与后面问题中的“最多”并不矛盾。先求每种状态下到达全白状态所需要的最少翻转次数,然后再比较所有各状态(到达全白状态)所需最少翻转次数的最大值,相当于一个Maxmin问题(更常见的形式是Minmax问题) 

2. 解题分析

        把每种盘面状态看作是一个节点(共有2^16=65536中状态/节点,本系列中通常把节点和状态交换使用),把各状态到达全白状态所需要最少翻转次数视为该节点到达全白节点的距离,本题可以看作是在由这65536个节点构成的图中距离全白节点最远的点。当前这里隐含了一个前提,就是说这个图是全连接图,或者说任意一个状态出发经过有限次翻转操作后都能够到达全白状态。

2.1 Naive approach

       作为Naïve approach,遍历从每个状态出发,然后搜索它们到全白状态的距离(即所需翻转次数),然后再进行比较。这样做当然也可以,但是将会导致巨大的重复和冗余搜索。作为一种改进方案,可以采用“递归+memoization”的策略,将已经搜索得到的结果记忆下来,较长距离的节点的搜索可以利用较短距离的节点的搜索结果。这样相比上述Naïve approach虽然可以得到巨大的性能提升,但是仍然不够好。

2.2 最大路径问题--广度优先搜索

       由于翻转操作是可逆的,上述的图是一个无向图,A和B两个节点的距离从A向B搜索与从B向A搜索会得到相同的结果。采用逆向思考(本系列中已经出现了多道基于逆向思考策略的题目了,等做完了本系列考虑做一次全面的各种算法策略的总结),从全白状态反向搜索到达各节点的距离,寻找其中最大值,这个问题就转化为从固定起点开始的图搜索中的最长路径搜索问题了,这类问题的经典策略是广度优先搜索。

        注意,不仅仅最短路径搜索可以用广度优先搜索,最长路径搜索也可以用广度优先搜索。

        在最短路径搜索中,是一旦找到目标点就停止搜索。与之不同的是,在最长路径搜索中,要遍历所有的点,最后到达的那个节点就是距离最大的节点。

2.3 节点状态表示及翻转操作

        4*4的棋盘共有16个方格,其黑白状态可以用16比特的二进制数来表示。

        而翻转操作则可以以bit-wise异或运算来实现。对于所选中的每一格方格,其对应的翻转操作可以表示为一个16比特的二进制数,称之为掩码。让掩码与表示当前状态的16比特二进制数进行bit-wise异或运算即等价实现了题设所要求的翻转处理。16个方格对应16种掩码可以提前计算好备用。

        如下图所示为一个运算示意图,包括棋盘状态、操作掩码的二进制表示及运算结果(’0b’开头表示这是一个二进制表示):

3. 代码及测试

# -*- coding: utf-8 -*-
"""
Created on Fri Sep 24 08:23:21 2021

@author: chenxy
"""

import sys
import time
import datetime
import random
from   typing import List
from   collections import deque
import itertools as it
import numpy as np

# all_white = int('0000000000000000',2)
mask_lut = dict()
mask_lut[ 0] = 0b1111_1000_1000_1000
mask_lut[ 1] = 0b1111_0100_0100_0100
mask_lut[ 2] = 0b1111_0010_0010_0010
mask_lut[ 3] = 0b1111_0001_0001_0001
mask_lut[ 4] = 0b1000_1111_1000_1000
mask_lut[ 5] = 0b0100_1111_0100_0100
mask_lut[ 6] = 0b0010_1111_0010_0010
mask_lut[ 7] = 0b0001_1111_0001_0001
mask_lut[ 8] = 0b1000_1000_1111_1000
mask_lut[ 9] = 0b0100_0100_1111_0100
mask_lut[10] = 0b0010_0010_1111_0010
mask_lut[11] = 0b0001_0001_1111_0001
mask_lut[12] = 0b1000_1000_1000_1111
mask_lut[13] = 0b0100_0100_0100_1111
mask_lut[14] = 0b0010_0010_0010_1111
mask_lut[15] = 0b0001_0001_0001_1111

all_white = 0b0000_0000_0000_0000
visited   = set()
q         = deque()
q.append((all_white,0))
visited.add(all_white)

tStart  = time.perf_counter()
while len(q) > 0:
    board, layer = q.popleft()
    for k in range(16):
        mask = mask_lut[k]
        nxt_board = mask ^ board
        if nxt_board not in visited:
            visited.add(nxt_board)
            q.append((nxt_board,layer+1))
            
tCost  = time.perf_counter() - tStart

print('final_board = {0}, layer = {1}, tCost = {2:6.3f}(sec)'.format(board,layer,tCost))  
        

        运行结果: final_board = 65535, layer = 16, tCost =  0.275(sec)

        65536表示为二进制为0b1111_1111_ 1111_1111,也就是表示全黑的状态。所以距离全白状态最远的是全黑状态,倒是一点都不意外。

4. 后记

        本题是到目前为止第一道coding完未经调试直接运行正确的题目,有点小小的得意^-^.

        算法编程解题确实也是一个“无他、唯手熟尔”的事情。前面有些题目做的非常费劲,是因为不管是针对问题本质的洞见、算法策略、Python编程等都很不熟悉(就是对各种套路和飞刀不熟悉),但是随着对这些技能的逐渐熟练的掌握,现在的解题就一点一点地变得轻松起来了。

       

        上一篇:Q37: 翻转7段码

        下一篇:Q39: 反复排序

        本系列总目录参见:程序员的算法趣题:详细分析和Python全解

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
假设变刚度阻尼双足机器人动力学方程可以表示为: M(q)q'' + C(q, q')q' + K(q)q = f 其中,q是机器人的广义坐标向量,M(q)是质量矩阵,C(q, q')是科里奥利力矩阵,K(q)是刚度矩阵,f是外部力矩向量。 假设我们要求解某个参数p对所有变量的偏导数,那么我们需要先通过ode45求解机器人的运动学方程,得到q和q'随时间的变化情况。然后,我们可以利用MATLAB的符号计算工具箱,对动力学方程进行符号化处理,并求出所有变量的偏导数。具体步骤如下: 1. 定义符号变量 syms q1 q2 q3 q4 q5 q6 q7 q8 q9 q10 q11 q12 q13 q14 q15 q16 q17 q18 q19 q20 q21 q22 q23 q24 q25 q26 q27 q28 q29 q30 q31 q32 q33 q34 q35 q36 q37 q38 q39 q40 q41 q42 q43 q44 q45 q46 q47 q48 q49 q50 p 其中,q1~q50表示机器人的广义坐标,p是我们要求导的参数。 2. 符号化动力学方程 假设我们已经定义好机器人的质量矩阵M,科里奥利力矩阵C,刚度矩阵K和外部力矩向量f,那么我们可以通过如下代码符号化动力学方程: q = [q1; q2; q3; q4; q5; q6; q7; q8; q9; q10; q11; q12; q13; q14; q15; q16; q17; q18; q19; q20; q21; q22; q23; q24; q25; q26; q27; q28; q29; q30; q31; q32; q33; q34; q35; q36; q37; q38; q39; q40; q41; q42; q43; q44; q45; q46; q47; q48; q49; q50]; q_dot = diff(q); q_ddot = diff(q_dot); M = % 定义质量矩阵 C = % 定义科里奥利力矩阵 K = % 定义刚度矩阵 f = % 定义外部力矩向量 D = M*q_ddot + C*q_dot + K*q - f; 3. 求导 接下来,我们可以利用MATLAB的符号计算工具箱对动力学方程进行求导: dD_dp = diff(D, p); 4. 数值化 最后,我们可以将变量q和q'的数值代入到偏导数表达式中,得到p对所有变量的偏导数值: q_val = % 机器人广义坐标向量随时间的变化 q_dot_val = % 机器人广义速度向量随时间的变化 dD_dp_val = double(subs(dD_dp, [q; q_dot], [q_val; q_dot_val])); 其中,subs函数可以将符号变量中的所有数值替换为实际的数值,double函数可以将符号变量转换为双精度数值。最终,dD_dp_val将是一个与q和q'相同维度的向量或矩阵,表示p对所有变量的偏导数值。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

笨牛慢耕

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

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

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

打赏作者

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

抵扣说明:

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

余额充值