轻松驾驭程序设计方法(分治法)-让程序设计成为手下败将

欢迎来到本博客
本次博客内容教大家如何设计程序,通过有趣的例子让哦我们一起开开心心的学习!!!
作者简介⭐️⭐️⭐️目前计算机研究生在读。主要研究方向是人工智能和群智能算法方向。目前熟悉python网页爬虫、机器学习、计算机视觉(OpenCV)、群智能算法。然后正在学习深度学习的相关内容。以后可能会涉及到网络安全相关领域,毕竟这是每一个学习计算机的梦想嘛!
博主优势博客内容尽量做到每一步都可以进行实操,做到极度细致,不仅仅可以满足自己复习,也方便大家进行学习!亲民!!!
目前更新目前已经更新了关于网络爬虫的相关知识、机器学习的相关知识、目前正在更新计算机视觉-OpenCV,本次博客主要教大家如何设计一个程序,后续将继续更新其他知识。
个人主页感兴趣的可以来这里转转呀!吃猫的鱼python个人主页
支持如果觉得博主的文章还不错或者您用得到的话,可以免费的关注一下博主,如果三连收藏支持就更好啦!这就是给予我最大的支持!
本文摘要

本文我们将通过几个例子来教大家如何用分治法设计一个程序。
⭐️1.自顶向下(分治法)的程序设计简介
⭐️2.分治法应用-五猴分桃
⭐️ 3.分治法应用-算命
⭐️ 4.分治法应用-智慧囚犯
⭐️ 5.分治法应用-扑克牌魔术
轻松驾驭程序设计方法(分治法)-让程序设计成为手下败将_第1张图片
在这里插入图片描述

⭐️1.自顶向下(分治法)的程序设计简介
一个程序员最容易犯的错误就是当遇到一个问题的时候,先编写子程序,然后编写子程序的父程序,最后编写主程序。当子程序都调试成功了然后编写父程序,会给人一种踏实感,其实这是不对的,我们应该从宏观的角度来看问题。这样做的缺点就是我们过多的关注局部而忽略了整体和全局,就有可能忽略了主程序的需求和整体的需求。
这是一种削足适履的做法,我们应该去找合适的鞋子,而不是看到了一个鞋子去让脚适应鞋子。所以综上我们得出的设计程序的方法就是:

先把问题分解成若干个小问题,然后再从小问题中继续分解成若干个更小的问题,以此类推,直到每一个小问题都可以得到相应的解决。

这就是著名的分治法,称为自顶向下的程序设计方法,又称之为先整体后局部的程序设计方法。
⭐️2.分治法应用-五猴分桃
问题描述
有五只猴子上山去摘桃子,一直摘到了天黑。他们把所有的桃子放在一起,准备第二天一起来分。
第二天,来了一只猴子,它想直接把桃子分了,于是分为了5等份,分完后还剩下了一个,他想:我这么辛苦,那么我就把它吃了吧!于是他把桃子吃掉了。并且带上了一份桃子走了。
一会,第二个猴子来了,他也把桃子分为了五份,也发现多了一份,同样他也吃掉了桃子,并且带走了一份。后来,三四五个猴子都按照这样做了。
问:最初一共有多少个桃子?
问题分析
我们首先把问题进行分解,最开始假设桃子是1,那么如果1个桃子能被5只猴子这样分掉,那么结果就是1。如果不能,那么桃子数量就加一,直到桃子数满足这样被5个猴子成功分掉。至于具体怎么分,我先不关心。于是对于这一部分:

def monkeys_peaches(monkeys):
    peaches=1
    while not distuibutable(peaches,monkeys):
        peaches+=5
    return peaches
if __name__=="__main__":
    print(monkeys_peaches(5))

这里我们用了distuibutable()函数来判断猴子是否成功的分完了桃子,继而我们继续想:要判断桃子能够被5个猴子分掉,那么我们只需要把问题分解为桃子能否被1只猴子分掉,如果能则继续下一次判断,如果5次成功分掉,那么返回True。否则,只要有一次没有分掉,那么就返回False。

def distuibutable(peaches,monkeys):
    for _ in range(monkeys):
        peaches-=1#因为被吃掉了一个,剩下的可以被5整除就ok
        if not peaches%monkeys==0:
             return False
         peaches=peaches//monkeys*(monkeys-1)#这里是因为先整除5,然后猴子带走了一份,还剩下四份,所以剩下的桃子就是再✖4
     return True
def monkeys_peaches(monkeys):
    peaches=1
    while not distuibutable(peaches,monkeys):
        peaches+=5
    return peaches
if __name__=="__main__":
    print(monkeys_peaches(5))

这样设计程序不仅仅符合问题分解的正确思路,而且还可以帮助我们很容易的发现错误或者优化程序。例如我们我们如果想直到6个猴子,那么就把最后的5改为6就OK。
我们还可以使用逆向思维,也就是从第五个猴子开始考虑,他把桃子分为了5等份,拿走了一份剩下4份,假设每份桃子数量为n,那么它开始分桃子前的桃子总数就是5n+1。那么对于第四个猴子来说这个5n加一一定可以被4整除。如果不能整除说明这个n就是有问题的,如果可以整除,那么把这个数除以4然后再✖5加1,这样就又得到了第三个猴子分完的结果了。以此类推,最后我们可以直到桃子的总数。

def get_peaches(unit_peaches,monkeys):
    peaches=(monkeys-1)*unit_peaches#这里表示第五只猴子分完桃子之后剩下四份桃子的数量。
    for _ in range(monkeys):
        if not peaches%(monkeys-1)==0:
            return None
        peaches=peaches//(monkeys-1)*monkeys+1
    return peaches
if __name__=='__main__':
    for i in range(500):
        if get_peaches(i,5) !=None:
            print(get_peaches(i,5))
            break
    print(monkey_peaches(5))

这里我们使用逆向思维的代码大大减少了最底层循环的次数,为594次,正向思维内层循环被调用了1405次。

⭐️ 3.分治法应用-算命
问题描述
小红有一天走在大街上,碰到了一个算命的,为了验证算的准不准,算命人拿出来七张纸,每张纸上密密麻麻写满了各种姓氏,小红需要告诉算命人他的姓氏在哪张纸上出现过,算命人就可以立马说出来她姓什么。那么问题就是:算命人如何做到的?
问题分析
其实秘密就再这写满姓氏的七张纸上,首先我们计算这七张纸的排列组合可以表示多少个姓氏?就是2的7次方-1,127个姓氏。所以我们把百家姓中的每个姓用七位的二进制进行编码,然后根据0或1决定相应的姓氏是否要写在对应的纸上。比如说她告诉你他的姓氏只出现再了第一张和第三张的纸上,那么就意味着她姓氏的编码是0000101,也就是对应的第五个姓名。所以我们编写的主程序应该是:

for page in pages:
    print(page)
    answer=input('你的姓名在这一行吗?(y,n,yes,no)')
    if answer is None and len(answer)==0:
        break
    if answer.lower() in ('y','yes'):#lower()将字符串中的所有大写字母转换为小写字母。
        answers.append(True)
    else:
        answers.append(False)
print('你的姓名是:',get_name(answers))

主程序主题是一个循环,用来显示每一页的姓氏。姓氏页列表需要我们调用程序获得,同样get_name这个函数怎么获得的最后姓名我们现在也不关心。
接下来我们开始写生成姓氏页列表的子程序:

import math 
NAMES='赵钱孙李周吴郑王冯陈楚卫蒋沈韩'
def get_name_page():
    pages=int(math.log2(len(NAMES))+1)#计算总页数
    result=[''for _ in range(pages)]#设置每一页都为空
    id=1
    for name in NAMES:
        insert_name(id,name,result)
        id+=1
    return result
def insert_name(id,name,result):
    for page_id in range(len(result)):
        if id % 2!=0:
            result[page_id]+=name+''
        id//2

通过对姓氏的个数取对数的方法获得总页数。例如100个姓氏就需要7张纸,15个姓氏就需要4张纸。接着,程序用空字符‘’初始化了所有的姓氏页,然后将姓氏进行插入,我们依旧不关注怎么样插入。
然后就是insert_name()函数,这就是要给将十进制转为二进制的一个过程,取2的模,然后整除2,不断重复。如果id的二进制某一位是1那么就表示把该姓名插入到相应页中。
然后我们再将对应的二进制转化为十进制,方法就是采用连乘法。1011对应的十进制就是((1*2+0)*2+1)*2+1

def get_name(answers):
    result=0
    for answer in reversed(answers):
        result*=2
        if answer:
            result+=1
        return NAMES[result-1]

总程序:

import math 
NAMES='赵钱孙李周吴郑王冯陈楚卫蒋沈韩'
def get_name_page():
    pages=int(math.log2(len(NAMES))+1)#计算总页数
    result=[''for _ in range(pages)]#设置每一页都为空
    id=1
    for name in NAMES:
        insert_name(id,name,result)
        id+=1
    return result
def insert_name(id,name,result):
    for page_id in range(len(result)):
        if id % 2!=0:
            result[page_id]+=name+''
        id//2
def get_name(answers):
    result=0
    for answer in reversed(answers):
        result*=2
        if answer:
            result+=1
        return NAMES[result-1]
if __name__=="__main__":
    for page in pages:
    print(page)
    answer=input('你的姓名在这一行吗?(y,n,yes,no)')
    if answer is None and len(answer)==0:
        break
    if answer.lower() in ('y','yes'):#lower()将字符串中的所有大写字母转换为小写字母。
        answers.append(True)
    else:
        answers.append(False)
print('你的姓名是:',get_name(answers))

⭐️4.分治法应用-智慧囚犯
问题描述
一群囚犯将被带到牢房里坐牢,看守把他们集中在一起,宣布:
1.每个囚犯被单独关在一个牢房。
2.每天随机抽一个囚犯放风。
3.放风的地方有一盏电灯,囚犯可以随意开关电灯。电灯永久有电。
4.囚犯放风回来其他囚犯不会发现。
5.牢房之间相互隔绝,囚犯之间不能传递消息。
6.囚犯除了开关电灯不会留下任何信息。
7.看守不会传递信息。如果有人确定所有人都放风过了,那么报告看守,如果是真的,那么那么所有囚犯就会被释放,如果是假的那么全部永久坐牢。
问:囚犯们商议出一个什么样的方法避免永久坐牢?

问题分析
这里我们想到的就是使用一进制解决该问题,那么什么是一进制呢?比如古代的结绳记事就是一进制,还有就是问你有几只羊,假如你有50只,那么你用石头刻下50个道道就表示你有50只羊。这就是一进制求解问题。
那么题中我们首先要定义一个囚犯是计数员,如果轮流他放风的时候台灯是亮的,那么就把计数加一。如果轮流其他囚犯放风的时候,看到台灯是亮的或者自己已经点亮过台灯就什么都不做,回牢房!如果看到台灯是灭的且自己之前从来没有点亮过台灯,那么点亮台灯!

import numpy as np

def prison(prisoners):
    counter=prisoners-1#定义计数员
    switch=[False]*counter#定义一个数组,初始值都为False判断囚犯是否开过灯
    lamp=False#初始灯的状态
    num=0
    while True:
        luck=np.random.randint(0,prisoners)
        print(luck)
        if luck==counter:
            print('----------------')
            if lamp:
                lamp=False
                num+=1
                if num==counter:
                    break
        else:
            if not lamp and not switch[luck]:
                lamp=True
                switch[luck]=True
    print('所有囚犯都放过风了,看守!!!')
if __name__=='__main__':
    prison(7)

⭐️ 5.分治法应用-扑克牌魔术
问题描述
有一副只有点数没有花色的扑克牌,点数分别是1,2,3…20,一共20张。我们把所有牌堆成一叠放在手上,然后我们翻开第一个,发现是1,然后我们把1放在一边,然后摸下一张牌。这是我们不看他的点数,把其放在牌的末尾,然后我们翻开下一张发现是2,然后按顺序摸两张牌放在牌底,然后翻开翻开下一张发现是3,这样一直重复直到从一一直翻到了20,那么请问:最开始的牌序是什么呢?
问题分析
我们解决这个问题依旧是自顶向下、先整体在局部。采用逆向思维,我们准备一叠牌,把20点分别插入到这叠牌中,至于怎么插进去我目前不关心。

def get_pokers(num):
    result=[]
    for k in range(num,0,-1):
        _insert(k,result)
    return result
if __name__=="__main__":
    get_pokers(20)

按照正向思维我们翻开拿到一边后我们又翻开了k张牌,并放到了这叠牌的底下。现在我们使用逆向思维,把这个牌插入到原始列表中,那么需要我们从牌底下一张一张抽出来k张牌,并按照顺序放在顶端。

def _insert(k,pokers):
    if len(pokers)>0:
        for _ in range(k):
            _move_last_to_top(pokers)
    pokers.insert(0,k)
    return pokers

最后我们来实现_move_last_to_top函数,我们只需要把列表中删除最后一个元素,然后把它插到顶端就可以。

def _move_last_to_top(pokers):
    last=pokers[-1]
    del pokers[-1]
    pokers.insert(0,last)

这里不理解我们可以用主函数DEBUG以下,然后还不理解的话,可以拿出一个1-5的扑克牌自己尝试一下这个过程。
轻松驾驭程序设计方法(分治法)-让程序设计成为手下败将_第2张图片

文章适合于所有的相关人士进行学习
各位看官看完了之后不要立刻转身呀
期待三连关注小小博主加收藏
小小博主回关快 会给你意想不到的惊喜呀
各位老板动动小手给小弟点赞收藏一下,多多支持是我更新得动力!!!

你可能感兴趣的:(python,开发语言,算法,深度学习入门基础)