第二章 搜索问题
第一章 解决几个小问题
第三章 约束满足问题
第四章 图问题
第五章 遗传算法
第六章 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 =