目录
一,算法的总体思想
分治法的思想便是分而治之。对于一个规模很大的问题,直接求解并不容易,如果能将问题拆分成若干个子问题,如果子问题的规模还不够小,那就继续分解下去,直到规模足够小,很容易求解为止。
将求出的小规模的问题的解,合并为一个更大规模问题的解,自底向上逐步求出原来问题的解。由于递归可以完美的实现这一功能,因此常用递归实现分治法。
二,递归的概念
直接或间接调用自身的算法是递归算法,这里不对递归过多介绍
递归的最重要一点便是找到终止条件(或是边界条件),与分治法结合,便是上面说的,此时规模已经足够小,可以求解了。这时调用自身来对问题分解的步骤已经完成,开始进行求出小规模问题的解,并合并为更大规模问题解的步骤。
三,分治法的使用条件
1.该问题缩小的一定规模就可以很好的解决;
2.该问题可以分解成若干个规模较小的相同问题;
这是使用分治法的前提
3.利用该问题分解出的子问题的解,可以合并成该问题的解;
能否使用分治法,完全取决于是否满足该条特征,如果满足了1,2条特征,不满足这条,可以考虑贪心算法或是动态规划算法
4.该问题分解出的各个子问题都是相互独立的,即子问题之间不包含公共的子问题;
如果子问题之间不相互独立,则会重复求解多个相同的子问题,此时可以使用备忘录算法或是动态规划算法。
四,分治法的几个例子
1.二分搜索
给出已按照升序排好序的n个元素a[0:n-1],现要从已有的n个元素中查找特定元素x
分析:
1.如果只有一个元素,则可以很容易地查找特定元素,即满足分治法的条件1;
2.比较x与a的中间元素a[mid],如果x = a[mid],则找到特定元素x
若x < a[mid],则说明特定元素x在mid左侧,
若x > a[mid],则说明特定元素x在mid右侧。
都是在a中查找,而且子问题的解也是问题的解,满足条件2,3。
def Binary_search(l, r, a, x) :
while l < r :
m = (l + r) // 2
if a[m] == x :
return m
if a[m] < x :
l = m + 1
elif a[m] > x :
r = m
return -1
a = list(i for i in range(20))
print(a)
x = int(input())
print(Binary_search(0, 20, a, x))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
6 #输入
6 #输出
2.棋盘覆盖
在一个2^k * 2^k 个方格组成的棋盘中,恰有一个方格与其它方格不同,称该方格为一特殊方格,且称该棋盘为一特殊棋盘。
问题: 用4种不同形态的L型骨牌, 覆盖给定特殊棋盘上除特殊方格以外的所有方格,且任何2个不得重叠
分析:此时有一个特殊方格,若将2^k * 2^k的棋盘,分为4个2^(k - 1) * 2^(k - 1)的棋盘,则特殊方格一定在某个棋盘中,而另外三个没有特殊方格,此时若将三个没有特殊方格的连接部分用一个L型的骨牌覆盖,则变成了都有一个特殊方格的2^(k - 1) * 2^(k - 1)棋盘,这便是划分出的相同的子问题,对划分后的棋盘重复进行上述操作,直到棋盘的大小降为1。
import numpy as np
tile = 0
def Chess_Board(size, si, sj, ti, tj, b) :
global tile
if size == 1 :
return
else :
tile += 1
t = tile
s = size // 2
if ti < (si + s) and tj < (sj + s) :
Chess_Board(s, si, sj, ti, tj, b)
else :
b[si + s - 1][sj + s - 1] = t
Chess_Board(s, si, sj, si + s - 1, sj + s - 1, b)
if ti < (si + s) and tj >= (sj + s) :
Chess_Board(s, si, sj + s, ti, tj, b)
else :
b[si + s - 1][sj + s] = t
Chess_Board(s, si, sj + s, si + s - 1, sj + s, b)
if ti >= (si + s) and tj < (sj + s) :
Chess_Board(s, si + s, sj, ti, tj, b)
else :
b[si + s][sj + s - 1] = t
Chess_Board(s, si + s, sj, si + s, sj + s - 1, b)
if ti >= (si + s) and tj >= (sj + s) :
Chess_Board(s, si + s, sj + s, ti, tj, b)
else :
b[si + s][sj + s] = t
Chess_Board(s, si + s, sj +s, si + s, sj + s, b)
# 输入棋盘规模n与特殊方块的位置x,y
n = int(input())
lod = input().split()
x = int(lod[0])
y = int(lod[1])
size = int(2 ** n)
board = np.zeros((size, size))
board[x][y] = -1
print(board)
Chess_Board(size, 0, 0, x, y, board)
print(board)
用二维数组构造棋盘,判断特殊方格是否在左上角的子棋盘,如果在,则进入子棋盘分析在子棋盘中的位置,如果不在,将左上角棋盘的右下角元素作为特殊方格,进入下一轮迭代中
#输入
3
0 1
#棋盘展示
[[ 0. -1. 0. 0. 0. 0. 0. 0.]
[ 0. 0. 0. 0. 0. 0. 0. 0.]
[ 0. 0. 0. 0. 0. 0. 0. 0.]
[ 0. 0. 0. 0. 0. 0. 0. 0.]
[ 0. 0. 0. 0. 0. 0. 0. 0.]
[ 0. 0. 0. 0. 0. 0. 0. 0.]
[ 0. 0. 0. 0. 0. 0. 0. 0.]
[ 0. 0. 0. 0. 0. 0. 0. 0.]]
#结果展示
[[ 3. -1. 4. 4. 8. 8. 9. 9.]
[ 3. 3. 2. 4. 8. 7. 7. 9.]
[ 5. 2. 2. 6. 10. 10. 7. 11.]
[ 5. 5. 6. 6. 1. 10. 11. 11.]
[13. 13. 14. 1. 1. 18. 19. 19.]
[13. 12. 14. 14. 18. 18. 17. 19.]
[15. 12. 12. 16. 20. 17. 17. 21.]
[15. 15. 16. 16. 20. 20. 21. 21.]]
合并排序,归并排序,快速排序在算法篇幅中有详细的过程
3.循环赛表
设计满足以下要求的比赛日程表:
(1)每个选手必须与其他n-1个选手各赛一次;
(2)每个选手一天只能赛一次;
(3)循环赛一共进行n-1天。
使用分治法,当比赛人数只有两个人时,排表变得简单,然后找到与两组不冲突的选手,填充未排的地方即可
用分治法实现:
def Table(k) :
if k == 0 :
a[0][0] = 1
return # 此时只有一个选手
Table(k - 1)
n = 1 << (k - 1)
for i in range(n) :
for j in range(n) :
a[i][j + n] = a[i][j] + n
a[i + n][j] = a[i][j] + n
a[i + n][j + n] = a[i][j]
n = int(input('输入k的值:'))
a = [[0] * (1 << n)for _ in range(1 << n)]
Table(n)
for line in a :
print(line)
双层for循环的作用是根据已填充的子方阵,将其复制到对应位置的子方阵中,以构建更大规模的循环赛日程表。
具体来说,假设当前已经填充了一个大小为 n
的子方阵,其中元素值范围为 1 到 n
。通过遍历 i
和 j
的取值范围,对于每个位置 (i, j)
,进行如下操作:
- 将子方阵
a[i][j]
复制到子方阵a[i][j + n]
中:a[i][j + n] = a[i][j] + n
。 - 将子方阵
a[i][j]
复制到子方阵a[i + n][j]
中:a[i + n][j] = a[i][j] + n
。 - 将子方阵
a[i][j]
复制到子方阵a[i + n][j + n]
中:a[i + n][j + n] = a[i][j]
。
代码中使用了位运算中的左移操作 <<
。左移操作将一个数的二进制表示向左移动指定的位数,相当于在其二进制表示的末尾添加相应数量的零。
具体来说,代码中的 1 << (k - 1)
表达式将数字 1 左移 (k - 1)
位,得到一个新的数。左移操作相当于将该数的二进制表示向左移动 (k - 1)
位,右侧补零。
例如,假设 k
的值为 3,那么 (k - 1)
的值为 2。则 1 << (k - 1)
的结果为二进制数 100,即十进制数 4。这表示在代码中会使用大小为 4 的子方阵进行日程表构造。
输入k的值:3
[1, 2, 3, 4, 5, 6, 7, 8]
[2, 1, 4, 3, 6, 5, 8, 7]
[3, 4, 1, 2, 7, 8, 5, 6]
[4, 3, 2, 1, 8, 7, 6, 5]
[5, 6, 7, 8, 1, 2, 3, 4]
[6, 5, 8, 7, 2, 1, 4, 3]
[7, 8, 5, 6, 3, 4, 1, 2]
[8, 7, 6, 5, 4, 3, 2, 1]