关于递归问题: 把握住如何能调用一个完全一样的自己, 如何"进" 如何"归"
- 汉诺塔
- 八皇后
- 快速排序
汉诺塔
def hanoi(n, src='a', buf='b', dst='c'):
"""
n个盘子, 从src, 以buf为缓存, 移到dst
"""
if n == 1:
print(f'{src}->{dst}', end='\t')
else:
hanoi(n-1, src, dst, buf)
hanoi(1, src, buf, dst)
hanoi(n-1, buf, src, dst)
hanoi(4)
a->b a->c b->c a->b c->a c->b a->b a->c b->c b->a c->a b->c a->b a->c b->c
八皇后
回溯法求解n皇后问题, 类比走迷宫
def is_valid(alist, i_row, v_column):
for i in range(i_row): # 这里要检验第i_row个元素, 与前面0, ... , i_row-1分别比较, 0开始共i_row个元素, 所以是range(i_row)
if alist[i] == v_column or abs(i_row-i) == abs(alist[i]-v_column):
return False
return True
def n_queen(n, alist, row=0):
"""
回溯法求解n皇后问题: 在第row行第alist[row]列放置皇后(每行只有一个皇后)
"""
if row == n - 1:
# 输出正确结果肯定是在这里
for col in range(n):
if is_valid(alist, row, col):
alist[row] = col
print(alist) # 可以用yield来代替print, 从而产生结果的序列, 但因为这是row = n-1的调用, 所以yield不到row=0那一层
return False
else:
# 回溯的部分在这里
for col in range(n):
if is_valid(alist, row, col):
alist[row] = col
n_queen(n, alist, row+1)
# 如果运行到这里, 说明上句的调用返回了, 这里应该进行下一次循环
return False
def solve_nqueen(n):
alist = [-99]*n
n_queen(n, alist)
solve_nqueen(8)
一句话来说, 回溯法就好像在走迷宫, 遇见岔路就做个标记, 然后尝试每一条岔路, 如果走不通就回到上一个标记.
这里本来是出一个结果就打印一次的, 如果想把最终结果保存到某种数据结构中怎么办.
最直接的想法就是把print改成yield.
递归中, 只有在最后一行的时候回yield, 而这个yield只能被他的caller也就是递归栈里倒数第二行的n_queen看到.
怎么办? 每次递归都yield from一下.
修改为用yield实现
def is_valid(alist, i_row, v_column):
for i in range(i_row): # 这里要检验第i_row个元素, 与前面0, ... , i_row-1分别比较, 0开始共i_row个元素, 所以是range(i_row)
if alist[i] == v_column or abs(i_row-i) == abs(alist[i]-v_column):
return False
return True
def n_queen(n, alist, row=0):
"""
回溯法求解n皇后问题: 在第row行第alist[row]列放置皇后(每行只有一个皇后)
"""
if row == n - 1:
# 输出正确结果肯定是在这里
for col in range(n):
if is_valid(alist, row, col):
alist[row] = col
yield tuple(alist) # 可以用yield来代替print, 从而产生结果的序列, 但因为这是row = n-1的调用, 所以yield不到row=0那一层
# 这里, 不能yield alist.
# yield alist的意思是抛出一个列表(地址), 所以最终生成的是一系列(92个)相同的列表(地址).
# 后面solve_nqueen函数中用了list(<generator>)来生成结果列表, 这样, 列表的每个元素就都是同一个地址了.
# 因为alist的作用域是整个递归栈, 在后续的程序执行当中, 会修改alist中的元素.
# 所以, 最终的结果将会是重复了92次的最后一次尝试.
# 解决: 用tuple()新建一个元组对象, 或者用alist[:] alist.copy()新建复制的列表对象, 都可以.
return
else:
# 回溯的部分在这里
for col in range(n):
if is_valid(alist, row, col):
alist[row] = col
yield from n_queen(n, alist, row+1)
# 如果运行到这里, 说明上句的调用返回了, 这里应该进行下一次循环
return
def solve_nqueen(n):
alist = [-99]*n
res = tuple(n_queen(n, alist))
print(len(res))
print(res)
if __name__ == '__main__':
solve_nqueen(8)
92
((0, 4, 7, 5, 2, 6, 1, 3), (0, 5, 7, 2, 6, 3, 1, 4), (0, 6, 3, 5, 7, 1, 4, 2), (0, 6, 4, 7, 1, 3, 5, 2), (1, 3, 5, 7, 2, 0, 6, 4), (1, 4, 6, 0, 2, 7, 5, 3), (1, 4, 6, 3, 0, 7, 5, 2), (1, 5, 0, 6, 3, 7, 2, 4), (1, 5, 7, 2, 0, 3, 6, 4), (1, 6, 2, 5, 7, 4, 0, 3), (1, 6, 4, 7, 0, 3, 5, 2), (1, 7, 5, 0, 2, 4, 6, 3), (2, 0, 6, 4, 7, 1, 3, 5), (2, 4, 1, 7, 0, 6, 3, 5), (2, 4, 1, 7, 5, 3, 6, 0), (2, 4, 6, 0, 3, 1, 7, 5), (2, 4, 7, 3, 0, 6, 1, 5), (2, 5, 1, 4, 7, 0, 6, 3), (2, 5, 1, 6, 0, 3, 7, 4), (2, 5, 1, 6, 4, 0, 7, 3), (2, 5, 3, 0, 7, 4, 6, 1), (2, 5, 3, 1, 7, 4, 6, 0), (2, 5, 7, 0, 3, 6, 4, 1), (2, 5, 7, 0, 4, 6, 1, 3), (2, 5, 7, 1, 3, 0, 6, 4), (2, 6, 1, 7, 4, 0, 3, 5), (2, 6, 1, 7, 5, 3, 0, 4), (2, 7, 3, 6, 0, 5, 1, 4), (3, 0, 4, 7, 1, 6, 2, 5), (3, 0, 4, 7, 5, 2, 6, 1), (3, 1, 4, 7, 5, 0, 2, 6), (3, 1, 6, 2, 5, 7, 0, 4), (3, 1, 6, 2, 5, 7, 4, 0), (3, 1, 6, 4, 0, 7, 5, 2), (3, 1, 7, 4, 6, 0, 2, 5), (3, 1, 7, 5, 0, 2, 4, 6), (3, 5, 0, 4, 1, 7, 2, 6), (3, 5, 7, 1, 6, 0, 2, 4), (3, 5, 7, 2, 0, 6, 4, 1), (3, 6, 0, 7, 4, 1, 5, 2), (3, 6, 2, 7, 1, 4, 0, 5), (3, 6, 4, 1, 5, 0, 2, 7), (3, 6, 4, 2, 0, 5, 7, 1), (3, 7, 0, 2, 5, 1, 6, 4), (3, 7, 0, 4, 6, 1, 5, 2), (3, 7, 4, 2, 0, 6, 1, 5), (4, 0, 3, 5, 7, 1, 6, 2), (4, 0, 7, 3, 1, 6, 2, 5), (4, 0, 7, 5, 2, 6, 1, 3), (4, 1, 3, 5, 7, 2, 0, 6), (4, 1, 3, 6, 2, 7, 5, 0), (4, 1, 5, 0, 6, 3, 7, 2), (4, 1, 7, 0, 3, 6, 2, 5), (4, 2, 0, 5, 7, 1, 3, 6), (4, 2, 0, 6, 1, 7, 5, 3), (4, 2, 7, 3, 6, 0, 5, 1), (4, 6, 0, 2, 7, 5, 3, 1), (4, 6, 0, 3, 1, 7, 5, 2), (4, 6, 1, 3, 7, 0, 2, 5), (4, 6, 1, 5, 2, 0, 3, 7), (4, 6, 1, 5, 2, 0, 7, 3), (4, 6, 3, 0, 2, 7, 5, 1), (4, 7, 3, 0, 2, 5, 1, 6), (4, 7, 3, 0, 6, 1, 5, 2), (5, 0, 4, 1, 7, 2, 6, 3), (5, 1, 6, 0, 2, 4, 7, 3), (5, 1, 6, 0, 3, 7, 4, 2), (5, 2, 0, 6, 4, 7, 1, 3), (5, 2, 0, 7, 3, 1, 6, 4), (5, 2, 0, 7, 4, 1, 3, 6), (5, 2, 4, 6, 0, 3, 1, 7), (5, 2, 4, 7, 0, 3, 1, 6), (5, 2, 6, 1, 3, 7, 0, 4), (5, 2, 6, 1, 7, 4, 0, 3), (5, 2, 6, 3, 0, 7, 1, 4), (5, 3, 0, 4, 7, 1, 6, 2), (5, 3, 1, 7, 4, 6, 0, 2), (5, 3, 6, 0, 2, 4, 1, 7), (5, 3, 6, 0, 7, 1, 4, 2), (5, 7, 1, 3, 0, 6, 4, 2), (6, 0, 2, 7, 5, 3, 1, 4), (6, 1, 3, 0, 7, 4, 2, 5), (6, 1, 5, 2, 0, 3, 7, 4), (6, 2, 0, 5, 7, 4, 1, 3), (6, 2, 7, 1, 4, 0, 5, 3), (6, 3, 1, 4, 7, 0, 2, 5), (6, 3, 1, 7, 5, 0, 2, 4), (6, 4, 2, 0, 5, 7, 1, 3), (7, 1, 3, 0, 6, 4, 2, 5), (7, 1, 4, 2, 0, 6, 3, 5), (7, 2, 0, 5, 1, 4, 6, 3), (7, 3, 0, 2, 5, 1, 6, 4))
别人的代码
import random
#冲突检查,在定义state时,采用state来标志每个皇后的位置,其中索引用来表示纵坐标,基对应的值表示横坐标,例如: state[0]=3,表示该皇后位于第1行的第4列上
def conflict(state, nextX):
nextY = len(state)
for i in range(nextY):
#如果下一个皇后的位置与当前的皇后位置相邻(包括上下,左右)或在同一对角线上,则说明有冲突,需要重新摆放
if abs(state[i]-nextX) in (0, nextY-i):
return True
return False
#采用生成器的方式来产生每一个皇后的位置,并用递归来实现下一个皇后的位置。
def queens(num, state=()):
for pos in range(num):
if not conflict(state, pos):
#产生当前皇后的位置信息
if len(state) == num-1:
yield (pos, )
#否则,把当前皇后的位置信息,添加到状态列表里,并传递给下一皇后。
else:
for result in queens(num, state+(pos,)):
yield (pos, ) + result
#为了直观表现棋盘,用X表示每个皇后的位置
def prettyprint(solution):
def line(pos, length=len(solution)):
return '. ' * (pos) + 'X ' + '. '*(length-pos-1)
for pos in solution:
print(line(pos))
if __name__ == "__main__":
prettyprint(random.choice(list(queens(8))))
# print(list(queens(8)))
. . . . X . . .
. . X . . . . .
X . . . . . . .
. . . . . X . .
. . . . . . . X
. X . . . . . .
. . . X . . . .
. . . . . . X .
快速排序
53 27 88 31 95 47
27 88 31 95 47 - 53
|
47 27 88 31 95 - 53
|
47 27 31 95 88 - 53
|
47 27 31 95 88 - 53
||
47 27 31 53 95 88
-----------------------------
47 27 31
27 31 - 47
|
31 27 - 47
||
31 27 47
--------
31 27
27 31
-------------------
95 88
88 95
这个算法的大致思路是:
- 取最左元素为pivot (left的位置空了)
- 从最右开始找一个比pivot小的值放在最左 (最右的位置空了)
- 赋值
- 右侧指针左移1位
- 从最左开始找一个比pivot大的值放在最右 (最左的位置空了)
- 赋值
- 左侧指针右移1位
- 最终得到pivot的位置, 以及被pivot分开的左右两个列表, 分别对两个列表进行同样的操作, 直到列表元素足够少
import random
def partition(alist, left, right):
pivot = alist[left]
while left < right:
while left < right and alist[right] >= pivot:
right -= 1
if left < right:
alist[left] = alist[right]
left += 1
while left < right and alist[left] <= pivot:
left += 1
if left < right:
alist[right] = alist[left]
right -= 1
pivot_loc = left
return pivot_loc
def quick_sort(alist, left=0, right=None):
if right == None:
right = len(alist) - 1
if left >= right:
# 这里只需考虑2个元素的情况, 得到pivot_loc后, 要么是0 要么是1,
# 下一步将会pivot_loc+1或者-1, 而left=0, right=1, 必然造成使得left>=right,
# 这时其实什么都不用做就可以了.
return
pivot_loc = partition(alist, left, right)
quick_sort(alist, left, pivot_loc - 1)
quick_sort(alist, pivot_loc + 1, right)
if __name__ == '__main__':
alist = [random.randrange(0, 1000) for _ in range(100)]
quick_sort(alist)
print(alist)
[10, 32, 23, 23, 117, 65, 65, 99, 99, 135, 118, 118, 118, 283, 183, 189, 204, 204, 212, 212, 244, 261, 189, 277, 283, 285, 303, 301, 303, 318, 308, 318, 308, 305, 305, 373, 373, 377, 556, 423, 423, 509, 483, 488, 504, 488, 521, 509, 535, 535, 533, 618, 609, 618, 649, 626, 712, 658, 686, 686, 691, 721, 721, 732, 732, 732, 749, 623, 623, 781, 784, 784, 781, 798, 809, 810, 798, 817, 822, 822, 836, 836, 904, 858, 858, 888, 888, 898, 900, 900, 869, 900, 917, 917, 917, 903, 952, 957, 979, 979]