Python广度优先搜索寻找朋友圈中关系最近的律师

一、问题描述

该问题受《算法图解》第6章寻找朋友圈中的芒果销售商启发,但是在它的基础上做了一些优化,使用了一些很棒的工具以及巧妙的方法(参考classic computer science problems in Python)。下面请看具体描述:

如果我们列出自己的朋友们以及每个人的职业,然后再列出朋友的朋友以及他们的职业,以此类推,向外围扩展,这样就得到了一个包含各种职业的朋友圈。现在,在这张关系网络里,我想找到距离我最近的律师。这里所谓的“距离”不是物理上的距离,而是人际关系上的距离。

如果A是我的朋友,并且是一位律师,而B是A的朋友,并且也是一名律师,但是B和我互相不认识。那么这种情况下,距离我较近的律师就是A。

二、 思路分析

显然,这是一个搜索问题。而且我们的需求是找到最近的,所以适合使用广度优先搜索。

 确定了搜索方法之后,还有一些具体的问题需要处理:

  1. 为了表示人以及人的职业,并且要方便地取出姓名和职业,我们最好建立一个Person类。但是这个类没有方法,只有两个属性,所以我们可以用namedtuple来快速建立,这样比较方便。
  2. 找到距离自己最近的律师之后,为了联系到他,我们需要知道从起点(要找律师的人)到这位律师,中间经过了哪些朋友来连接这条人际关系线路。所以,我们的搜索不能仅仅是返回这位律师的名字,而是要保存这条完整的路径。我们采用的方法是用一个Node类来“包裹”每个人。Node的每个实例都是一个节点,该节点除了保存自己是哪个人之外,还保存了一个指针,指向了父节点。因此,就可以表示“Anna是Finn的朋友,Finn是我的朋友”这样的人际关系线路。
  3. 从上面一点可以看出来,这条路径是倒着的,是从律师指向了要找律师的人。而我们要从找律师的人的视角出发,所以最终打印出来的信息应该是“我->Finn->Anna”这样的格式。因此,我们还需要一个方法来帮助我们翻转得到的人际关系线路,并且以清晰的格式打印出来。

以上就是解决这个问题的关键思路,下面请看代码实现:

三、代码实现

from __future__ import annotations
from collections import namedtuple,deque


# 使用具名元组建立一个Person类
Person = namedtuple('Person','name career')

class Node:
    '''节点类,每个节点都保存自己的父节点'''
    def __init__(self,person: Person, parent: Node=None) -> None:
        # 该节点自身的值
        self.person = person
        # 该节点的父节点
        self.parent = parent

def node_to_path(node: Node) -> str:
    '''还原从起点到终点的路径
    格式是:a->b->c
    '''
    path = [node.person.name]
    while node.parent:
        node = node.parent
        path.append(node.person.name)
    path.reverse()
    return '->'.join(path)

def bfs(circle: dict, person: Person, target_career: str) -> Node:
    '''在朋友圈中寻找距离当事人最近的目标职业者'''
    # 使用双端队列,可以高效做到先进先出
    # 为了保存人际关系线路,这里进队的是节点
    queue = deque([Node(person,None)])
    # 朋友圈中存在很多互为朋友的关系,
    # 为了避免死循环,我们要标记那些已经处理过的人
    explored = set()
    while queue:
        current_node = queue.popleft()
        if current_node.person.career == target_career:
            return current_node
        explored.add(current_node)
        if current_node.person in circle:
            for friend in circle[current_node.person]:
                if friend not in explored:
                    # 因为这些朋友都是当前节点的朋友
                    # 所以在创建节点的时候父节点就是当前节点
                    queue.append(Node(friend,current_node))
    return None


if __name__ == '__main__':
    # 人物清单
    michael = Person('Michael','Doctor')
    alex = Person('Alex','Actor')
    mamie = Person('Mamie','Actress')
    ned = Person('Ned','Teacher')
    patty = Person('Patty','Secretary')
    heidi = Person('Heidi','Nurse')
    jaggers = Person('Jaggers','Lawyer')
    dewey = Person('Dewey','Musician')
    chris = Person('Chris','Lawyer')

    # 朋友圈
    circle = {michael:[alex,mamie],
            alex:[ned,michael,patty],
            mamie:[heidi,jaggers],
            ned:[alex,dewey],
            patty:[chris,alex],
            heidi:[mamie]}
    
    # 寻找距离michael最近的律师
    solution = bfs(circle,michael,'Lawyer')
    if solution is None:
        print('No such a friend')
    else:
        print(node_to_path(solution))

 

如果这篇博文帮到了你,就请给我点个吧(#^.^#)

有疑问也欢迎留言~博主可nice啦,在线秒回ヾ(◍°∇°◍)ノ゙

你可能感兴趣的:(算法,Python,广度优先搜索,namedtuple,bfs,deque)