python算法精粹(二):路径搜索问题(DFS,BFS,A*算法的解析、对比)

本文探讨了如何使用Python实现深度优先搜索(DFS)、广度优先搜索(BFS)和A*算法解决迷宫问题。通过对比不同算法在迷宫求解中的表现,展示了A*算法在寻找最短路径时的高效性和实用性。此外,还介绍了传教士与食人族问题的BFS解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第二章 搜索问题

第一章 解决几个小问题
第三章 约束满足问题
第四章 图问题
第五章 遗传算法
第六章 k均值聚类
第七章 十分简单的神经网络
第八章 对抗搜索
第九章 其他问题



前言

本文基于人民邮电出版社的《算法精粹》一书,经典计算机科学问题的python实现。在整理读书笔记的同时,增加本人的思维过程,主要方便自己回顾学习过程;也发表出来方便各位python工程师或准工程师共同学习,共同进步。


一、DNA搜索

1.1 DNA储存方案

由于DNA是由4种不同的核苷酸(ACGT)组成,其中三个核苷酸看做是一个密码子。

也就是每三个字母是一个单词,其他没用。

from enum import IntEnum
from typing import Tuple, List
from numpy import random


def built_original(num: int) -> str:
    a = ['A', 'T', 'G', 'C']
    b: str = ''
    for i in range(num):
        b += a[random.randint(4)]
    return b


Nucleotide: IntEnum = IntEnum('Nucleotide', ('A', 'C', 'G', 'T'))
Codon = Tuple[Nucleotide, Nucleotide, Nucleotide]
Gene = List[Codon]
gene_str: str = built_original(100)
print(gene_str)


def string_to_gene(s: str) -> Gene:
    gene: Gene = []
    for i in range(0, len(s), 3):
        if (i + 2) >= len(s):  # 刨除多余部分,例如1000里面分成333个3数字,多出一个核苷酸应该被抛弃
            return gene
        codon: Gene = (Nucleotide[s[i]], Nucleotide[s[i + 1]], Nucleotide[s[i + 2]])
        gene.append(codon)
    return gene


my_gene: Gene = string_to_gene(gene_str)

1.2 线性搜索

线性搜索顾名思义是由0开始,一个个遍历过去,直到找到目标值或者没发现目标值就遍历全部。

def linear_contains(gene: Gene, key_codon: Codon) -> bool:
    ii = 0
    for codon in gene:
        if codon == key_codon:
            print('有', key_codon, '在第', ii, '位到第', ii + 2, '位。')
            return True
        ii += 3
    return False


acg: Codon = (Nucleotide.A, Nucleotide.C, Nucleotide.G)
gat: Codon = (Nucleotide.G, Nucleotide.A, Nucleotide.T)
print(linear_contains(my_gene, acg))  # 判断是否有ACG
print(linear_contains(my_gene, gat))  # 判断是否有GAT

1.3 二分搜索

二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。

首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。

# 二分搜索
def binary_contains(gene: Gene, key_codon: Codon) -> bool:
    low: int = 0
    high: int = len(gene) - 1
    while low <= high:
        mid: int = (low + high) // 2
        if gene[mid] < key_codon:
            low = mid + 1
        elif gene[mid] > key_codon:
            high = mid - 1
        else:
            return True


my_sorted_gene: Gene = sorted(my_gene)
print(binary_contains(my_sorted_gene, acg))  # 二分法判断是否有ACG
print(binary_contains(my_sorted_gene, gat))  # 二分法判断是否有ACG

1.4 整个程序

from enum import IntEnum
from typing import Tuple, List
from numpy import random


def built_original(num: int) -> str:
    a = ['A', 'T', 'G', 'C']
    b: str = ''
    for i in range(num):
        b += a[random.randint(4)]
    return b


Nucleotide: IntEnum = IntEnum('Nucleotide', ('A', 'C', 'G', 'T'))
Codon = Tuple[Nucleotide, Nucleotide, Nucleotide]
Gene = List[Codon]
gene_str: str = built_original(100)
print(gene_str)


def string_to_gene(s: str) -> Gene:
    gene: Gene = []
    for i in range(0, len(s), 3):
        if (i + 2) >= len(s):  # 刨除多余部分,例如1000里面分成333个3数字,多出一个核苷酸应该被抛弃
            return gene
        codon: Gene = (Nucleotide[s[i]], Nucleotide[s[i + 1]], Nucleotide[s[i + 2]])
        gene.append(codon)
    return gene


my_gene: Gene = string_to_gene(gene_str)


def linear_contains(gene: Gene, key_codon: Codon) -> bool:
    ii = 0
    for codon in gene:
        if codon == key_codon:
            print('有', key_codon, '在第', ii, '位到第', ii + 2, '位。')
            return True
        ii += 3
    return False


acg: Codon = (Nucleotide.A, Nucleotide.C, Nucleotide.G)
gat: Codon = (Nucleotide.G, Nucleotide.A, Nucleotide.T)
print(linear_contains(my_gene, acg))  # 判断是否有ACG
print(linear_contains(my_gene, gat))  # 判断是否有GAT


# 二分搜索,搜索之前一定记得先排序
def binary_contains(gene: Gene, key_codon: Codon) -> bool:
    low: int = 0
    high: int = len(gene) - 1
    while low <= high:
        mid: int = (low + high) // 2
        if gene[mid] < key_codon:
            low = mid + 1
        elif gene[mid] > key_codon:
            high = mid - 1
        else:
            return True


my_sorted_gene: Gene = sorted(my_gene)
print(binary_contains(my_sorted_gene, acg))  # 二分法判断是否有ACG
print(binary_contains(my_sorted_gene, gat))  # 二分法判断是否有ACG

在这里插入图片描述

二、求解迷宫问题

2.1 创建一个随机的二维迷宫

代码如下(示例):

from enum import Enum
from typing import List, NamedTuple, Callable, Optional
import random


class Cell(str, Enum):  # 采用特殊标记符来绘制一个简易的迷宫,定义各个标记符的样子
    EMPTY = "0"
    BLOCKED = "x"
    START = "S"
    GOAL = "G"
    PATH = "*"


class MazeLocation(NamedTuple):  # 采用特殊标记符来绘制一个简易的迷宫,定义行列数
    row: int
    column: int


class Maze:  # 生成一个迷宫
    def __init__(self, rows: int = 10, columns: int = 10, sparseness: float = 0.2,
                 start: MazeLocation = MazeLocation(0, 0),
                 goal: MazeLocation = MazeLocation(9, 9)) -> None:  # 初始化迷宫的初始数据,标记行列数,定义起终点,障碍的稀疏度
        self._rows = rows
        self._columns = columns
        self.start = start
        self.goal = goal
        self._grid: List[List[Cell]] = [[Cell.EMPTY for c in range(columns)] for r in
                                        range(rows)]  # 生成一个 rows*columns 大小的矩阵 ,默认值设为无特殊点
        self._randomly_fill(rows, columns, sparseness)
        self._grid[start.row][start.column] = Cell.START  # 把起始点列出来
        self._grid[goal.row][goal.column] = Cell.GOAL

    def _randomly_fill(self, rows, columns, sparseness):  # 根据稀疏度来判断该点是不是障碍
        for row in range(rows):
            for column in range(columns):
                if random.uniform(0, 1) < sparseness:
                    self._grid[row][column] = Cell.BLOCKED

    def __str__(self):
        output: str = ""
        for row in self._grid:
            output += "".join([c.value for c in row]) + "\n"
        return output


if __name__ == "__main__":
	m: Maze = Maze(rows=10, columns=20, sparseness=0.2, start=MazeLocation(0, 0), goal=MazeLocation(9, 19))
	print(m)

运行后可以得到一个二维迷宫,其中大小为10*20,起点是(0,0),终点是(19,19),生成障碍的概率是0.2,换而言之障碍的稀疏率为20%。
在这里插入图片描述

·由注释可以看出来,0表示为该坐标可以行进,x表示为障碍,S和G分别是起点个目标点。

2.2 采用深度优先搜索(DFS)求解迷宫问题

对上面生成迷宫的Maze类进行修改如下:
增加了4个方法:
goal_test —— 判断是否到达了终点;
successors —— 记录运动的路线;
mark —— 成功之后根据记录的最优路线对迷宫地图进行标注,用*代替0表示通过该点
clear —— 记录之后删除痕迹,*还原0

from enum import Enum
from typing import List, NamedTuple, Callable, Optional
import random
# from math import sqrt

from text import dfs, node_to_path, Node


class Cell(str, Enum):  # 采用特殊标记符来绘制一个简易的迷宫,定义各个标记符的样子
    EMPTY = "0"
    BLOCKED = "x"
    START = "S"
    GOAL = "G"
    PATH = "*"


class MazeLocation(NamedTuple):  # 采用特殊标记符来绘制一个简易的迷宫,定义行列数
    row: int
    column: int


class Maze:  # 生成一个迷宫
    def __init__(self, rows: int = 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值