【5】算法基础:分治

将一个问题划分为同一类型的若干子问题,子问题最好同规模;对这些子问题求解(一般使用递归);有必要的话合并这些子问题的解,以得到原始问题的答案。

通用分治递推式

示例1:棋盘覆盖问题

用一个占棋盘上三个邻接方块的L形(Tromino)来覆盖一个缺少了一个方块的2^n*2^n的棋盘。

https://blog.csdn.net/acm_JL/article/details/50938164

将棋盘最终化为2*2的格子,当有一个缺角时,L形的位置就显而易见了,当四个2*2拼成8*8的棋盘时,拼合处覆盖一个L形,则每个2*2都缺一角,又转化为2*2覆盖问题。将大的棋盘化小时,起始空缺所在的模块始终留出位置,L覆盖拼合处的另外三个模块。

这里给出某个坐标,求和这个位置的格子被同一个L形覆盖的其他格子坐标。

import math

class ChessBoard:
    def __init__(self,N,init_point,left_up=(0,0)):
        self.N=N
        self.length=int(math.pow(2,N))
        self.init_point=init_point
        self.left_up = left_up
    def __str__(self):
        return str(self.left_up)+" "+str(self.length)+" "+str(self.init_point)

def split(chess_board):
    length=chess_board.length
    if length==2:
        return [chess_board]
    else:
        length_half=length//2
        N_half=chess_board.N-1
        init_point_module=0
        if chess_board.init_point[0]<length_half and chess_board.init_point[1]>=length_half:
            init_point_module=1 # right-up
            chess_board_1 = ChessBoard(N_half, (chess_board.init_point[0], chess_board.init_point[1]-length_half),left_up=(chess_board.left_up[0],chess_board.left_up[1]+length_half))  # 1
            chess_board_2 = ChessBoard(N_half, (length_half-1,length_half-1),left_up=chess_board.left_up)  # 2
            chess_board_3 = ChessBoard(N_half, (0,length_half-1),left_up=(chess_board.left_up[0]+length_half,chess_board.left_up[1]))  # 3
            chess_board_4 = ChessBoard(N_half, (0,0),left_up=(chess_board.left_up[0]+length_half,chess_board.left_up[1]+length_half))  # 4
        elif chess_board.init_point[0]<length_half and chess_board.init_point[1]<length_half:
            init_point_module=2 # left-up
            chess_board_1 = ChessBoard(N_half, (length_half-1,0),left_up=(chess_board.left_up[0],chess_board.left_up[1]+length_half))  # 1
            chess_board_2 = ChessBoard(N_half, chess_board.init_point,left_up=chess_board.left_up)  # 2
            chess_board_3 = ChessBoard(N_half, (0, length_half - 1),left_up=(chess_board.left_up[0]+length_half,chess_board.left_up[1]))  # 3
            chess_board_4 = ChessBoard(N_half, (0, 0), left_up=(chess_board.left_up[0]+length_half,chess_board.left_up[1]+length_half))  # 4
        elif chess_board.init_point[0]>=length_half and chess_board.init_point[1]<length_half:
            init_point_module=3 # left-down
            chess_board_1 = ChessBoard(N_half, (length_half-1,0),left_up=(chess_board.left_up[0],chess_board.left_up[1]+length_half))  # 1
            chess_board_2 = ChessBoard(N_half, (length_half-1,length_half-1),left_up=chess_board.left_up)  # 2
            chess_board_3 = ChessBoard(N_half, (chess_board.init_point[0]-length_half, chess_board.init_point[1]),left_up=(chess_board.left_up[0]+length_half,chess_board.left_up[1]))  # 3
            chess_board_4 = ChessBoard(N_half, (0, 0),left_up=(chess_board.left_up[0]+length_half,chess_board.left_up[1]+length_half))  # 4
        else:
            init_point_module=4 # right-down
            chess_board_1 = ChessBoard(N_half, (length_half-1,0),left_up=(chess_board.left_up[0],chess_board.left_up[1]+length_half))  # 1
            chess_board_2 = ChessBoard(N_half, (length_half-1,length_half-1),left_up=chess_board.left_up)  # 2
            chess_board_3 = ChessBoard(N_half, (0, length_half - 1),left_up=(chess_board.left_up[0]+length_half,chess_board.left_up[1]))  # 3
            chess_board_4 = ChessBoard(N_half, (chess_board.init_point[0]-length_half, chess_board.init_point[1]-length_half),left_up=(chess_board.left_up[0]+length_half,chess_board.left_up[1]+length_half))  # 4
        chess_board_list=[chess_board_1,chess_board_2,chess_board_3,chess_board_4]
        result_list=[]
        for chess_board in chess_board_list:
            result=split(chess_board)
            result_list.extend(result)
        return result_list

def specific_point_location(specific_point):
    if specific_point[0]%2==0:
        x=specific_point[0]
    else:
        x = specific_point[0]-1
    if specific_point[1]%2==0:
        y=specific_point[1]
    else:
        y = specific_point[1]-1
    return (x,y)
if __name__=="__main__":
    chess_board=ChessBoard(N=3,init_point=(4,6),left_up=(0,0))
    chess_board_list = split(chess_board)
    chess_board_list=sorted(chess_board_list,key=lambda x:(x.left_up[0],x.left_up[1]))
    # for chess_board in chess_board_list:
    #     print(chess_board)
    specific_point=(2,1)
    left_up=specific_point_location(specific_point)
    left_up_chess_board_index=int(left_up[0]/2*chess_board.length/2+left_up[1]/2)
    left_up_chess_board=chess_board_list[left_up_chess_board_index]
    # print(left_up_chess_board)
    if left_up_chess_board.init_point[0]+left_up_chess_board.left_up[0]!=specific_point[0] or left_up_chess_board.init_point[1]+left_up_chess_board.left_up[1]!=specific_point[1]: # normal_point
        for i in range(2):
            for j in range(2):
                if not (i+left_up_chess_board.left_up[0]==specific_point[0] and j+left_up_chess_board.left_up[1]==specific_point[1]):
                    if not (i==left_up_chess_board.init_point[0] and j==left_up_chess_board.init_point[1]):
                        print((i+left_up_chess_board.left_up[0],j+left_up_chess_board.left_up[1]))
    else:
        if left_up_chess_board.init_point[0]==0 and left_up_chess_board.init_point[1]==1: # 3 right-up
            chess_board_1_index = int((left_up[0] / 2 - 1) * chess_board.length / 2 + left_up[1] / 2 + 1)
            chess_board_2_index=int((left_up[0] / 2 - 1 ) * chess_board.length / 2 + left_up[1] / 2)
            chess_board_4_index = int(left_up[0] / 2 * chess_board.length / 2 + left_up[1] / 2 + 1)
            result_pre_list=[chess_board_list[chess_board_1_index],chess_board_list[chess_board_2_index],chess_board_list[chess_board_4_index]]
            for result_pre in result_pre_list:
                if abs(result_pre.init_point[0]+result_pre.left_up[0]-specific_point[0])<=1 and abs(result_pre.init_point[1]+result_pre.left_up[1]-specific_point[1])<=1:
                    print((result_pre.init_point[0]+result_pre.left_up[0],result_pre.init_point[1]+result_pre.left_up[1]))
        elif left_up_chess_board.init_point[0]==0 and left_up_chess_board.init_point[1]==0: # 4 left-up
            chess_board_1_index = int((left_up[0] / 2 - 1) * chess_board.length / 2 + left_up[1] / 2)
            chess_board_2_index = int((left_up[0] / 2 - 1) * chess_board.length / 2 + left_up[1] / 2 - 1)
            chess_board_3_index = int(left_up[0] / 2 * chess_board.length / 2 + left_up[1] / 2 - 1)
            result_pre_list = [chess_board_list[chess_board_1_index], chess_board_list[chess_board_2_index],chess_board_list[chess_board_3_index]]
            for result_pre in result_pre_list:
                if abs(result_pre.init_point[0] + result_pre.left_up[0] - specific_point[0]) <= 1 and abs(result_pre.init_point[1] + result_pre.left_up[1] - specific_point[1])<=1:
                    print((result_pre.init_point[0]+result_pre.left_up[0],result_pre.init_point[1]+result_pre.left_up[1]))
        elif left_up_chess_board.init_point[0]==1 and left_up_chess_board.init_point[1]==0: # 1 left-down
            chess_board_2_index = int((left_up[0] / 2) * chess_board.length / 2 + left_up[1] / 2)
            chess_board_3_index = int((left_up[0] / 2 + 1) * chess_board.length / 2 + left_up[1] / 2 - 1)
            chess_board_4_index = int((left_up[0] / 2 + 1 ) * chess_board.length / 2 + left_up[1] / 2)
            result_pre_list = [chess_board_list[chess_board_2_index],chess_board_list[chess_board_3_index],chess_board_list[chess_board_4_index]]
            for result_pre in result_pre_list:
                if abs(result_pre.init_point[0] + result_pre.left_up[0] - specific_point[0]) <= 1 and abs(result_pre.init_point[1] + result_pre.left_up[1] - specific_point[1])<=1:
                    print((result_pre.init_point[0]+result_pre.left_up[0],result_pre.init_point[1]+result_pre.left_up[1]))
        elif left_up_chess_board.init_point[0]==1 and left_up_chess_board.init_point[1]==1: # 2 right-down
            chess_board_1_index = int((left_up[0] / 2) * chess_board.length / 2 + left_up[1] / 2 + 1)
            chess_board_3_index = int((left_up[0] / 2 + 1) * chess_board.length / 2 + left_up[1] / 2 - 1)
            chess_board_4_index = int((left_up[0] / 2 + 1 ) * chess_board.length / 2 + left_up[1] / 2 + 1)
            result_pre_list = [chess_board_list[chess_board_1_index], chess_board_list[chess_board_3_index],chess_board_list[chess_board_4_index]]
            for result_pre in result_pre_list:
                if abs(result_pre.init_point[0] + result_pre.left_up[0] - specific_point[0]) <= 1 and abs(result_pre.init_point[1] + result_pre.left_up[1] - specific_point[1])<=1:
                    print((result_pre.init_point[0]+result_pre.left_up[0],result_pre.init_point[1]+result_pre.left_up[1]))

 

示例2:折半查找

通过对比查找键值与列表中间元素的大小,确定下一次查找的位置。

 

示例3:二叉树

将二叉树定义为若干节点的一个有限集合,它要么为空,要么由两棵称为Tl和Tr的不相交二叉树构成(根的左右子树),二叉树是有序树的一种特例。由于二叉树可以不断划分为同样类型的两个更小的组成部分,许多关于二叉树的问题可以应用分治技术来解决。

可以将末端的节点补充叶节点构成一棵扩展二叉树,一棵包含n个内部节点的扩展二叉树总是具有n+1个外部节点(x=n+1)。

完全二叉树:每个节点仅具有0个或2个子女的二叉树。

拓展1:计算二叉树高度

一个二叉树的高度是根的左、右子树的最大高度加1(根所在的层),空树的高度定义为-1。

拓展2:二叉树遍历

三种经典方法:前序遍历、中序遍历、后序遍历,都是递归访问二叉树的节点,但是访问根、左子树、右子树的顺序不同。规定子树先左后右,则前序遍历:根、左、右,中序遍历:左、根、右,后序遍历:左、右、根。

拓展3:二叉树节点间的最大距离

求左子树中最深的节点和右子树中最深的节点,每颗子树最深的节点为其左子树中最深的节点和右子树中最深的节点中更深的那个。

class Node:
    def __init__(self,value,depth):
        self.value=value
        self.depth=depth
        self.left=None
        self.right=None
    def add_left(self,left):
        self.left=left
    def add_right(self,right):
        self.right=right

class Tree:
    def __init__(self,root):
        self.root=root

def compute_height(root):
    left_height=0
    right_height=0
    if root.left is not None:
        left_height = compute_height(root.left)
    if root.right is not None:
        right_height = compute_height(root.right)
    return max(left_height,right_height)+1

def pre_through(root):
    print(root.value)
    if root.left is not None:
        pre_through(root.left)
    if root.right is not None:
        pre_through(root.right)
def mid_through(root):
    if root.left is not None:
        mid_through(root.left)
    print(root.value)
    if root.right is not None:
        mid_through(root.right)
def post_through(root):
    if root.left is not None:
        post_through(root.left)
    if root.right is not None:
        post_through(root.right)
    print(root.value)

def find_deepest(root):
    if root.left is None and root.right is None:
        return root
    elif root.left is None:
        return find_deepest(root.right)
    elif root.right is None:
        return find_deepest(root.left)
    else:
        deep_left=find_deepest(root.left)
        deep_right=find_deepest(root.right)
        if deep_left.depth>=deep_right.depth:
            return deep_left
        else:
            return deep_right

def max_distance(root):
    left_deep_node=find_deepest(root.left)
    right_deep_node=find_deepest(root.right)
    return left_deep_node.depth+right_deep_node.depth

if __name__=="__main__":
    root = Node(0,0)
    node1 = Node(1,1)
    node2 = Node(2,1)
    node3 = Node(3,2)
    node4 = Node(4,2)
    node5 = Node(5,2)
    node6 = Node(6,2)
    node1.add_left(node3)
    node1.add_right(node4)
    node2.add_left(node5)
    node2.add_right(node6)
    root.add_left(node1)
    root.add_right(node2)
    tree=Tree(root)
    height=compute_height(tree.root)
    print(height)
    pre_through(tree.root)
    mid_through(tree.root)
    post_through(tree.root)
    distance=max_distance(tree.root)
    print(distance)

更多和树相关的在“【6】算法基础:变治 ”中。

 

示例4:巧克力谜题

有一个n*m的巧克力,将它掰成n*m个1*1的小块,只能沿直线掰,且不能几块同时掰,求最少掰完巧克力的次数。

(待更新)

示例5:大整数乘法

(待更新)

示例6:Strassen矩阵乘法

(待更新)

示例7:最近对问题

令P为笛卡尔平面上n>1个点构成的集合,每个点都不一样,当2<=n<=3时,可以通过蛮力求解,当n>3时,可以利用点集在x轴方向上的中位数m,在该点处做一条垂线,将点集分成大小分别为n/2向上取整和n/2向下取整的两个子集Pl和Pr,然后通过递归求解子问题Pl和Pr来得到最近点对问题的解dl和dr,d=min(dl,dr)。d不一定是所有点对的最小距离,因为距离最近的两个点可能分别位于分界线的两侧,因此在合并较小子问题的解时,需要检查是否存在这样的点。可以只关注以分割带为对称的、宽度为2d的垂直带中的点。为了方便,将P按x轴升序排列,另一个完全一样的Q按y轴升序排列。设S是来自Q,位于分割线2d宽度范围内的垂直带的点的列表,由于S是按照y轴升序排列的,扫描S,当遇到更近的点对时,更新目前为止的最小距离dmin,初始dmin=d。

import math

def compute_distance(p1,p2):
    return math.sqrt(math.pow(2,p1[0]-p2[0])+math.pow(2,p1[1]-p2[1]))
def closest_pair_force(P):
    if len(P)==2:
        return compute_distance(P[0],P[1])
    elif len(P)==3:
        d1=compute_distance(P[0],P[1])
        d2=compute_distance(P[0],P[2])
        d3 = compute_distance(P[1], P[2])
        return min(min(d1,d2),d3)
    else:
        print("ERROR")
def closest_pair(P):
    if len(P)<=3:
        return closest_pair_force(P)
    else:
        mid=int(len(P)/2)
        Pl=P[:mid]
        Pr=P[mid:]
        d1=closest_pair(Pl)
        d2=closest_pair(Pr)
        dmin=min(d1,d2)
        middle=P[mid]
        Sl=[]
        for p in Pl:
            if middle[0]-p[0]<=dmin:
                Sl.append(p)
        Sr=[]
        for p in Pr:
            if p[0]-middle[0]<=dmin:
                Sr.append(p)
        Sl = sorted(Sl, key=lambda x: x[1])
        Sr = sorted(Sr, key=lambda x: x[1])
        for sl in Sl:
            for sr in Sr:
                d=compute_distance(sl,sr)
                if d<dmin:
                    dmin=d
        return dmin

if __name__=="__main__":
    points=[[1,1],[2,2],[3,3],[4,4],[5,5],[1.5,1.5]]
    P=sorted(points,key=lambda x:x[0])
    dmin=closest_pair(P)
    print(dmin)

示例8:凸包问题

找到平面上n个给定点的凸包(能够完全包含给定n个点的以其中一些点为顶点的最小凸多边形),解决它的分治算法叫做快包(类似与快速排序)。假设集合S是平面上n>1个点pi(xi,yi)构成的(假设按照x轴升序排列,x相同则按y轴排列),易见:最左面的点p1和最右边的点pn一定是该集合的凸包顶点,设p1_pn是方向从p1到pn的直线,这条直线将点分为两个集合:S1是位于直线左侧或在直线上的点构成的集合,S2是在直线右侧或在直线上的点构成的集合(q3在q1_q2左侧表示q1q2q3构成一个逆时针的回路)。除了p1和pn,S中位于p1_pn上的点肯定不可能是凸包的顶点。S的凸包的边界是由一条上边界(上包)和一条下边界(下包)的多边形链条构成的,是一系列线段的序列,这些线段以p1、S1中的一些点、pn和p1、S2中的一些点、pn为端点,整个集合S的凸包是由上包和下包构成的,它们可以用同样的方法分别构造。

以上包为例,如果S1为空,上包就是以p1和pn为端点的线段,如果S1不空,该算法找到S1中的顶点pmax,它是距离直线p1_pn最远的点,如果距离最远的点有多个,就找能使角pmax.p1.pn最大的点,然后找到S1中所有在直线p1_pmax左边的点,这些点以及p1和pmax构成了集合S1.1。S1中在直线pman_pn左边的点以及pmax和pn构成了集合S1.2。易见:pmax是上包的顶点,包含在三角形p1.pmax.pn之中的点不可能是上包的顶点,同时位于p1_pmax和pmax_pn两条直线左边的点是不存在的。因此该算法可以继续递归构造p1 U S1.1 U pmax和pmax U S1.2 U pn的上包,然后把它们连接起来得到整个集合p1 U S1 U pn的上包。

平面上三个点q1(x1,y1)q2(x2,y2)q3(x3,y3)构成的三角形的面积等于下面这个行列式绝对值的二分之一

当且仅当q3位于直线q1_q2左侧时,该表达式符号位正。使用这个公式可以帮助我们检查一个点是否位于两个点确定的直线的左侧,并求到这个点到这跟直线的距离。

import math

def is_left_right(p1,p2,p3):
    ilr=p1[0]*p2[1]+p3[0]*p1[1]+p2[0]*p3[1]-p3[0]*p2[1]-p2[0]*p1[1]-p1[0]*p3[1]
    return ilr

def compute_distance(p1,p2,p3):
    # p1p2 :(y1-y2)x+(x2-x1)y+x1y2-x2y1=0
    # p3: d=|Ax3+By3+C|/sqrt(A^2+B^2)
    A=p1[1]-p2[1]
    B=p2[0]-p1[0]
    C=p1[0]*p2[1]-p2[0]*p1[1]
    d=abs(A*p3[0]+B*p3[1]+C)/math.sqrt(math.pow(A,2)+math.pow(B,2))
    return d
def compute_cos(p1,p2,p3):
    # p1p3 :(y1-y3)x+(x3-x1)y+x1y3-x3y1=0
    # p2p3 :(y2-y3)x+(x3-x2)y+x2y3-x3y2=0
    # cos : (A1A2+B1B2)/(sqrt(A1^2+B1^2)*sqrt(A2^2+B2^2))
    A1 = p1[1] - p3[1]
    B1 = p3[0] - p1[0]
    # C1 = p1[0] * p3[1] - p3[0] * p1[1]
    A2 = p2[1] - p3[1]
    B2 = p3[0] - p2[0]
    # C2 = p2[0] * p3[1] - p3[0] * p2[1]
    cos= (A1*A1+B1*B2)/(math.sqrt(math.pow(A1,2)+math.pow(B1,2))*math.sqrt(math.pow(A2,2)+math.pow(B2,2)))
    return cos

def split_S(p1,pn,S):
    S1=[]
    S2=[]
    for s in S:
        ilr = is_left_right(p1, pn, s)
        if ilr > 0:
            S1.append(s)
        elif ilr < 0:
            S2.append(s)
    return S1,S2
def split_up(p1,pn,S):
    S1 = []
    for s in S:
        ilr = is_left_right(p1, pn, s)
        if ilr > 0:
            S1.append(s)
    return S1
def split_down(p1,pn,S):
    S2 = []
    for s in S:
        ilr = is_left_right(p1, pn, s)
        if ilr < 0:
            S2.append(s)
    return S2

def find_up(p1,pn,S1):
    if len(S1)==0:
        return []
    S1_d = []
    S1_cos = []
    for s in S1:
        d = compute_distance(p1, pn, s)
        cos = compute_cos(p1, pn, s)
        S1_d.append(d)
        S1_cos.append(cos)
    d1_max_i = [0]
    for i in range(1, len(S1)):
        if S1_d[i] == S1_d[d1_max_i[0]]:
            d1_max_i.append(i)
        elif S1_d[i] > S1_d[d1_max_i[0]]:
            d1_max_i = [i]
    cos1_min_i = d1_max_i[0]
    for i in d1_max_i:
        if S1_cos[i] < S1_cos[cos1_min_i]:
            cos1_min_i = i
    S1_max = S1[cos1_min_i]
    S11=split_up(p1,S1_max,S1)
    S12=split_up(S1_max,pn,S1)
    up11=find_up(p1,S1_max,S11)
    up12=find_up(S1_max,pn,S12)
    up=[]
    up.extend(up11)
    up.append(S1_max)
    up.extend(up12)
    return up
def find_down(p1,pn,S2):
    if len(S2)==0:
        return []
    S2_d = []
    S2_cos = []
    for s in S2:
        d = compute_distance(p1, pn, s)
        cos = compute_cos(p1, pn, s)
        S2_d.append(d)
        S2_cos.append(cos)
    d2_max_i = [0]
    for i in range(1, len(S2)):
        if S2_d[i] == S2_d[d2_max_i[0]]:
            d2_max_i.append(i)
        elif S2_d[i] > S2_d[d2_max_i[0]]:
            d2_max_i = [i]
    cos2_min_i = d2_max_i[0]
    for i in d2_max_i:
        if S2_cos[i] < S2_cos[cos2_min_i]:
            cos2_min_i = i
    S2_max = S2[cos2_min_i]
    S21=split_down(p1,S2_max,S2)
    S22=split_down(S2_max,pn,S2)
    down21=find_down(p1,S2_max,S21)
    down22=find_down(S2_max,pn,S22)
    down=[]
    down.extend(down21)
    down.append(S2_max)
    down.extend(down22)
    return down
def find_convex_closure(S):
    p1 = S[0]
    pn = S[-1]
    S1,S2=split_S(p1,pn,S)
    up=find_up(p1,pn,S1)
    down=find_down(p1,pn,S2)
    result=[]
    result.append(p1)
    result.extend(up)
    result.append(pn)
    result.extend(down)
    result.append(p1)
    return result
if __name__=="__main__":
    S=[[0,2],[1,1],[1,3],[2,0],[2,2],[2,3],[3,1],[3,2],[3,4],[4,1],[4,2]]
    S=sorted(S,key=lambda x:(x[0],x[1]))
    result=find_convex_closure(S)
    print(result)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值