原标题:Python系列—一笔画问题的算法研究

鸣谢

VSRC感谢业界小伙伴——奶权,投稿精品原创类文章。VSRC欢迎精品原创类文章投稿,优秀文章一旦采纳发布,将有好礼相送,我们已为您准备好了丰富的奖品!

(活动最终解释权归VSRC所有)

0x00 一笔画完游戏

昨天和朋友出去外面吃饭,吃完饭后朋友打开了一个小程序玩了起来。

游戏长这样

大概玩法是:从地图中猫的位置开始出发,并且经过所有的格子就算过关。游戏还算挺有意思的,经过我的不断努力终于过到了30来关的样子。

并且随着游戏关卡的增加。游戏难度也变得越来越大,过一关需要非常久的时间。

最近也正好在研究算法,就打算看能不能写个通用的算法来找出每个地图的解。

0x01 哥尼斯堡的"七桥问题"

这个游戏的玩法和哥尼斯堡的"七桥问题"有点类似。

哥尼斯堡的"七桥问题":

18世纪著名古典数学问题之一。在哥尼斯堡的一个公园里,有七座桥将普雷格尔河中两个岛及岛与河岸连接起来(如图)。问是否可能从这四块陆 地中任一块出发,恰好通过每座桥一次,再回到起点?

当时人们想到的证明方法是把七座桥的走法都列出来一个一个试验,用排列组合的知识很容易得到七座桥所有的走法大概有7!=5040种,如果真的逐一试验,会是个很大的工作量。

但数学家欧拉没有这样想,欧拉把两座岛和河两岸抽象成顶点,七座桥抽象成连接每个顶点的七条边,那么这个问题就能被抽象成下面的图:

假设每座桥都恰好走过一次,那么对于A、B、C、D四个顶点中的每一个顶点,需要从某条边进入,同时从另一条边离开,进入和离开顶点的次数是相同的,即每个顶点有多少条进入的边,就有多少条出去的边。也就是说:每个顶点相连的边是成对出现的,即每个顶点的相连边的数量必须是偶数。

很明显,上图中A、C、D三个顶点相连的边都有3条,B顶点的相连边为5条,都是奇数。因此这个图无法从一个顶点出发,且恰好走过每座桥一次。

由此次证明人们又得到了欧拉路关系,要使得一个图形可以一笔画完,必须满足如下两个条件:

图形必须是连通的不能有孤立的点。

图中拥有奇数连接边的点必须是0或2。

对于一个连通图,通常把从某结点出发一笔画成所经过的路线叫做欧拉路。

那么这个游戏是不是就是让我们找到一条欧拉路呢?

0x02 对游戏进行抽象

按照上面证明七桥问题的方法,我们可以将游戏的地图抽象成这样:

其中14号顶点为起点

顶点和边的关系在程序中可以刻画成一个二维列表

graph = [

[1, 6], #0

[0, 2], #1

[1, 7, 3], #2

...

[24, 19] #25

]

graph列表的第一层表示每一个顶点,第二层则是与当前顶点有边的顶点,抽象完这张游戏地图后可以很清楚知道,这游戏并不是让我们找到一条欧拉路。

因为找到一条欧拉路,需要的是经过每一座桥,且只经过一次,也就是说每个顶点可以被多次经过。

而这个游戏需要的是经过每一个顶点,并不要求走完每一座桥,且顶点只能被经过一次。

0x03 哈密顿通路

在研究了七桥问题发现并不能解决这类问题后,我开始向团队的表哥们请教,其中一个表哥告诉我此类问题叫做哈密顿图(这里感谢下团队的**@xq17**表哥)。

这里说的哈密顿图,实际上是哈密顿通路的一种特殊情况 ,这种特殊情况指的是:

由指定的起点出发,途中经过所有其他顶点且只经过一次,最后返回起点,称之为哈密顿回路,如果给定的图G具有哈密顿回路,则称图G为哈密顿图。

那么现在目标明确了,这个游戏的解法就是找到某个给定图的哈密顿通路。

但是!!!

但是来了!!!

哈密顿通路问题,在上世纪七十年代初,被证明是NP-hard问题

一个复杂问题如果能在多项式时间内解决,那么它便被称为P类问题

一个复杂问题如果不能确定在多项式时间内解决,那么它便被称为NP类问题

什么意思呢?就拿质因数分解来说吧

P类问题: 23x37=?

NP类问题: 已知 axb=740914799,且a和b都是质数,求a和b的值 让我们来看看这个问题有多复杂:

因为 axb=740914799,且a和b都是质数

设 P={x|2<=x<740914799/2,x是质数}

易得 (a,b)∈PxP 即P与它自身的笛卡尔积

我们用一种叫做筛法的算法来求一下P集合的元素个数

一共有19841519个质数,算了我大概14分钟。

PxP的元素个数一共有19841519^2个,要一个个验证是否等于740914799,无疑又是一项很大的工程,这就是典型的NP类问题。NP类问题虽然难,但是可以很快验证一个给定的答案,是否正确。

比如上面的题,我告诉你答案a=22229 b=33331,你很快就能验证答案是否正确了。

而NP-hard问题则是比NP问题更难的问题,例如:围棋。

也就是说并不能找到一个友好的算法,来解决哈密顿通路问题。

0x04 算法设计

虽然找到一个图的哈密顿通路是NP困难的,但是好在游戏中的顶点不算太多,还是可以使用暴力一点的方法实现的,例如:图的深度优先遍历法(dfs)即递归和回溯法思想。

算法流程:

1. 将当前顶点压入已访问栈和路径栈中

将与当前顶点相通的顶点列出来

随机选取一个相通的顶点 并判断此顶点是否在已访问栈中

A.在已访问栈中则取另一个相通的顶点

B.不在则将这个相通的顶点作为当前顶点

C.若所有相通的顶点都在已访问栈中, 则判断路径栈是否包含所有顶点

a. 路径栈中包含所有顶点,则路径栈为当前图的哈密顿通路

b. 不包含所有顶点则回到父顶点, 并从已访问栈和路径栈中删除

反复执行1~3

0x05 算法实现

上面说过图的顶点和边的关系可以用一个二维列表来描述

graph = [

[1, 6],#0

[0, 2],#1

[1, 7, 3],#2

...

[24, 19]#25

]

但是要手动输入这些顶点和边的关系还是太麻烦了。仔细想了下,如果每个顶点的上下左右有顶点,那么就一定与上下左右的顶点有边,那么这个二维列表就可以简化成:

graph = [

[1,1,1,1,1,1],

[1,0,1,1,0,1],

[1,1,1,1,1,1],

[1,0,1,1,0,1],

[1,1,1,1,1,1],

[0,0,0,0,0,0] #每个1代表一个顶点 1与上下左右的1都有边 与0则没有 长宽相等易于编写代码

]

```

还可以再简化成一维列表:

graph = [

'111111',

'101101',

'111111',

'101101',

'111111',

'000000'

]

简直机智如我啊!

于是我写了个函数对一维列表进行转换

defget_index(i, j, G):

num = 0

forainxrange(i):

num += G[a].count('0')

forb inxrange(j):

ifG[i][b] == '0':

num += 1

returni * len(G) + j - num

defget_graph(G):

G = [list(x) forx inG]

EG = [ ]

fori inxrange(len(G)):

forj inrange(len(G[ i ])):

ifG[ i ][ j ] == '0':

continue

side_list = []

ifj+1<= len(G[i]) - 1:

ifG[i][j+1] == '1':

index = get_index(i, j-1, G)

side_list.append(index)

ifj-1>= 0:

ifG[i][j-1] == '1':

index = get_index(i, j-1, G)

side_list.append(index)

ifi+1<= len(G) - 1:

ifG[i+1][j] == '1':

index = get_index(i+1, j, G)

side_list.append(index)

ifi-1>= 0:

ifG[i-1][j] =='1':

index = get_index(i-1, j, G)

side_list.append(index)

EG.append(side_list)

returnEG

而算法的实现用图的邻接矩阵则更为方便,因此我写了一个将上列二位列表转换成邻接矩阵形式的函数:

defget_matrix(graph):

result = [[0]*len(graph)for _inxrange(len(graph))]# 初始化

fori inxrange(len(graph)):

forj ingraph[i]:

result[i][j] = 1# 有边则为1

returnresult

主要的dfs算法如下:

# graph为图的邻接矩阵 used为已访问栈 path为路径栈 step为已经遍历的顶点的个数

defdfs(graph, path, used, step):

ifstep == len(graph): # 判断顶点是否被遍历完毕

printpath

return True

else:

fori inxrange(len(graph)):

if notused[i] andgraph[path[step-1]][i] ==1:

used[i] = True

path[step] = i

ifdfs(graph, path, used, step+1):

return True

else:

used[i] = False# 回溯 返回父节点

path[step] = -1

return False

defmain(graph, v):

used = [ ] # 已访问栈

path = [ ] # 路径栈

fori inxrange(len(graph)):

used.append(False) # 初始化 所有顶点均未被遍历

path.append(-1) # 初始化 未选中起点及到达任何顶点

used[v] = True# 表示从起点开始遍历

path[0] = v # 表示哈密顿通路的第一个顶点为起点

dfs(graph, path, used, 1)

完整代码

#! /usr/bin/env python

# -*- coding: utf-8 -*-

# Coding with love by Naiquan.

defdfs(graph, path, used, step):

ifstep == len(graph):

printpath

return True

else:

foriinxrange(len(graph)):

if notused[i] andgraph[path[step-1]][i] == 1:

used[i] =True

path[step] = i

ifdfs(graph, path, used, step+1):

return True

else:

used[i] = False

path[step] = -1

return False

defmain(graph, v):

used = [ ]

path = [ ]

fori inxrange(len(graph)):

used.append(False)

path.append(-1)

used[v] =True

path[0] = v

dfs(graph, path, used, 1)

defget_index(i, j, G):

num = 0

fora inxrange(i):

num += G[a].count('0')

forb inxrange(j):

ifG[i][b] =='0':

num += 1

returni * len(G) + j - num

defget_graph(G):

G = [list(x) forx inG]

EG = []

fori inxrange(len(G)):

forj inrange(len(G[i])):

ifG[i][j] == '0':

continue

side_list = []

ifj+1<= len(G[i]) - 1:

ifG[i][j+1] == '1':

index = get_index(i, j+1, G)

side_list.append(index)

ifj-1>= 0:

ifG[i][j-1] == '1':

index = get_index(i, j-1, G)

side_list.append(index)

ifi+1<= len(G) -1:

ifG[i+1][j] == '1':

index = get_index(i+1, j, G)

side_list.append(index)

ifi-1>=0:

ifG[i-1][j] == '1':

index = get_index(i-1, j, G)

side_list.append(index)

EG.append(side_list)

returnEG

defget_matrix(graph):

result = [[0]*len(graph) for_ inxrange(len(graph))]

fori inxrange(len(graph)):

forj ingraph[i]:

result[i][j] =1

returnresult

if__name__ == '__main__':

map_list = [

'111111',

'101101',

'111111',

'101101',

'111111',

'000000'

]

G = get_graph(map_list)

map_matrix = get_matrix(G)

# print map_matrix

SP = 14

main(map_matrix, SP)

0x06 结束

在实现了功能后,我拿着这个程序成功过到了差不多一百关,然后就玩腻了,哈哈哈哈哈哈哈哈哈

0x07 本文参考资料

1. 七桥问题_百度百科

【https://baike.baidu.com/item/七桥问题/2580504?fr=aladdin】

2. 哈密顿图_百度百科

【https://baike.baidu.com/item/哈密顿图/2587317?fr=aladdin】

3. 这个数学题我做可以,但世界毁灭了别怪我【https://www.bilibili.com/video/av19009866】

4. 基于回溯法寻找哈密顿回路

【http://www.cnblogs.com/cielosun/p/5654577.html】返回搜狐,查看更多

责任编辑:

python生成一笔画_Python系列—一笔画问题的算法研究相关推荐

  1. python生成规定随机数_python生成随机数的方法

    python生成随机数的方法 发布时间:2020-08-21 14:50:04 来源:亿速云 阅读:110 作者:小新 这篇文章主要介绍了python生成随机数的方法,具有一定借鉴价值,需要的朋友可以 ...

  2. python生成随机数方法_Python生成随机数的方法

    如果你对在Python生成随机数与random模块中最常用的几个函数的关系与不懂之处,下面的文章就是对Python生成随机数与random模块中最常用的几个函数的关系,希望你会有所收获,以下就是这篇文 ...

  3. python生成html报表_python生成HTMl报告(unittest)

    放在*\python3\script 或者放在当前项目文件夹下 亦或者在python35下的lib文件夹下 (注意最好放在根目录下) from HTMLTestRunner import HTMLTe ...

  4. python生成正态分布数据_python 生成正态分布数据,并绘图和解析

    1.生成正态分布数据并绘制概率分布图 import pandas as pd import numpy as np import matplotlib.pyplot as plt # 根据均值.标准差 ...

  5. python生成字符画_Python生成字符画 | 文艺数学君

    摘要这一篇文章介绍使用Python生成字符画的方式. 会给出简单的原理介绍和实现的方式. 最后会给出源代码的链接地址. 简介 这一篇介绍一下使用Python来进行字符画的生成. 这里会介绍一下生成的步 ...

  6. python生成一个圆_python生成圆形图片的方法

    本文实例为大家分享了python生成圆形图片的具体代码,供大家参考,具体内容如下 # -*- coding: utf-8 -*- """ __author__= 'Du' ...

  7. python生成随机数方法_Python随机数生成方法

    假设你对在Python生成随机数与random模块中最经常使用的几个函数的关系与不懂之处.以下的文章就是对Python生成随机数与random模块中最经常使用的几个函数的关系,希望你会有所收获,以下就 ...

  8. 学好python薪水有多少笔画_Python 获得汉字笔画

    通过unihan的文件来实现. 只要是unihan中有kTotalStrokes字段,获取起笔画数. Hash也是非常简单清楚的,但想到这些unicode其实会有一个分布规律,就记录了一下, 利用此性 ...

  9. python生成字符画_python生成动态字符画

    广告关闭 腾讯云11.11云上盛惠 ,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元! 但,可以将字符串的一部分复制到新创建的字符串,达到"看起来修改&quo ...

最新文章

  1. 转 Hystrix入门指南 Introduction
  2. 神策数据桑文锋:让销售回归科学
  3. js php调用webservice,js跨域调用WebService的使用方法
  4. Docker入门系列之三:如何将dockerfile制作好的镜像发布到Docker hub上
  5. Kubernetes 的原理
  6. C#开发笔记之01-为什么开源框架会大量的使用protected virtual?
  7. Awake OnEnable Start Update LateUpdate FixedUpdate
  8. 计算机小喇叭找不到,Win7电脑右下角的小喇叭不见了
  9. 如何从 GitHub 上下载指定项目的单个文件或文件夹
  10. 暂缓上市的云知声,技术究竟几何?
  11. python idle界面_3.7 IDLE 用户界面
  12. php高仿互站网源码,2020新版友价高仿互站网源码 虚拟交易商城整站源码
  13. 封装link或style中的css规则
  14. 网易考拉海购Dubbok框架优化详解(学习笔记)
  15. 你知道不注册国外邮箱也能往国外发邮件吗?
  16. 怎么修改云服务器,怎么修改云服务器的登录密码
  17. Android 11.0 锁屏页面时钟显示样式
  18. 用scratch编写游戏2048(印章法)
  19. 测试TensorFlow 是否安装成功
  20. 实时监测GPU的显存和显存清理小功能学习

热门文章

  1. 创建Android定时器的5种方法
  2. 青龙面板-服务器工具安装
  3. MySQL索引的理解学习,面试不问索引原理就是事务原理
  4. hadoop下载包目录结构
  5. MT6735 L版本开机待机后概率性唤醒不了
  6. 电脑账户头像怎么删掉_win10系统账户头像如何删除?windows10账户头像清除方法...
  7. Android APK 反编译的基本工具和步骤
  8. Linux查看实时网速
  9. Monkey脚本简介
  10. 什么是面向对象、面向过程与面向对象的区别