算法设计与分析笔记2———分治法

目录

一,算法的总体思想

二,递归的概念

三,分治法的使用条件

四,分治法的几个例子

1.二分搜索

2.棋盘覆盖

3.循环赛表

一,算法的总体思想

        分治法的思想便是分而治之。对于一个规模很大的问题,直接求解并不容易,如果能将问题拆分成若干个子问题,如果子问题的规模还不够小,那就继续分解下去,直到规模足够小,很容易求解为止。

        将求出的小规模的问题的解,合并为一个更大规模问题的解,自底向上逐步求出原来问题的解。由于递归可以完美的实现这一功能,因此常用递归实现分治法。

二,递归的概念

        直接或间接调用自身的算法是递归算法,这里不对递归过多介绍

        递归的最重要一点便是找到终止条件(或是边界条件),与分治法结合,便是上面说的,此时规模已经足够小,可以求解了。这时调用自身来对问题分解的步骤已经完成,开始进行求出小规模问题的解,并合并为更大规模问题解的步骤。

三,分治法的使用条件

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),进行如下操作:

  1. 将子方阵 a[i][j] 复制到子方阵 a[i][j + n] 中:a[i][j + n] = a[i][j] + n
  2. 将子方阵 a[i][j] 复制到子方阵 a[i + n][j] 中:a[i + n][j] = a[i][j] + n
  3. 将子方阵 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]

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值