分治法2

Chess-Board

问题描述:有 2kX2k 大小的棋盘,其中有一块和其他的不一样。使用4种 L 型的card将棋盘铺满棋盘。如下图所示:
这里写图片描述
问题分析:首先所给的4个card是可以将最小矩形的三个块组合的所有情况都考虑到了(C34=4),所以我们将问题规模减小为(2X2),实际上就是1时,问题都是可以解决的。问题的难点就在于如何将问题分解成多个类似的小问题,很自然将上面的大图减半,但是生成的4个小图的形式不一样,为了保证一样,我们只需要在他们的聚合点(也就中心点附近添加一个合适的card)如下图所示,就可以将问题分解成相似的小问题了。
这里写图片描述
代码实现:

# Import a library of functions called 'pygame'
import pygame
import time

# Define the colors we will use in RGB format
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
YELLOW = (255, 255, 0)

Colors = [RED, BLUE, GREEN, YELLOW]
# define a global queue named Boards
Boards = []

def Card(tag, left_upper):
    """
    tag:card的类型,为0就代表第一个方块为空,同理类推
    left_upper:card所在矩形左上角的坐标
    """
    global Boards
    blocks = []
    nums = [0, 1, 2, 3]
    nums.remove(tag)
    for i in nums:
        block = []
        row = int(i / 2)
        col = i % 2
        block.append((left_upper[1] + col) * 40 + 10)
        block.append((left_upper[0] + row)*40 + 10)
        block.append(40)
        block.append(40)
        blocks.append(block)
    blocks.append(Colors[tag])
    Boards.append(blocks)


def DrawBoard(dr, dc, size):
    pygame.init()
    window_size = [size * 40 + 20, size * 40 + 20]

    # Set the height and width of the screen
    screen = pygame.display.set_mode(window_size)

    pygame.display.set_caption("chess board")
    done = False
    clock = pygame.time.Clock()
    while not done:

        # This limits the while loop to a max of 10 times per second.
        # Leave this out and we will use all CPU we can.
        clock.tick(10)
        for event in pygame.event.get():  # User did something
            if event.type == pygame.QUIT:  # If user clicked close
                done = True

        # 画出相应的grid
        screen.fill(WHITE)
        for i in range(size + 1):
            pygame.draw.line(screen, BLACK, [i * 40 + 10, 10], [i * 40 + 10, size * 40 + 10], 3)
            pygame.draw.line(screen, BLACK, [10, i * 40 + 10], [size * 40 + 10, i * 40 + 10], 3)

        # Draw a rectangle outline
        pygame.draw.rect(screen, RED, [dc * 40 + 10,dr * 40 + 10, 40, 40], 0)
        for L in Boards:
            pygame.draw.rect(screen, L[3], L[0], 0)
            pygame.draw.rect(screen, L[3], L[1], 0)
            pygame.draw.rect(screen, L[3], L[2], 0)
            time.sleep(2)
            pygame.display.flip()

        pygame.display.flip()

        # Be IDLE friendly
       # pygame.quit()

def chBoard(tr, tc, dr, dc, size):
    """
    坐标都是从0开始的
    tr:表示要处理的子方格的左上角横坐标
    tc:表示要处理的子方格的左上角竖坐标
    dr:表示特殊方块的row
    dr:表示特殊方块的col
    size:表示问题的规模(2^k)
    """
    global tile
    tile += 1
    s = size / 2
    # 判断特殊方块落在哪个区域
    num_row = 0 if ((dr - tr) < s) else 1
    num_col = 0 if ((dc - tc) < s) else 1
    k = num_row * 2 + num_col
    # 填补中心点的另外三个方块
    Card(k, [tr + s - 1, tc + s - 1])
    if (s == 1):
        return

    # 分别对四个区域进行处理
    # 左上角覆盖
    if (k == 0):
        chBoard(tr, tc, dr, dc, s)
    else:
        chBoard(tr, tc, tr + s - 1, tc + s - 1, s)
    # 右上角覆盖
    if (k == 1):
        chBoard(tr, tc + s, dr, dc, s)
    else:
        chBoard(tr, tc + s, tr + s - 1, tc + s, s)
    # 左下角覆盖
    if (k == 2):
        chBoard(tr + s, tc, dr, dc, s)
    else:
        chBoard(tr + s, tc, tr + s, tc + s - 1, s)
    # 右下角覆盖
    if (k == 3):
        chBoard(tr + s, tc + s, dr, dc, s)
    else:
        chBoard(tr + s, tc + s, tr + s, tc + s, s)


if __name__ == "__main__":
    tile = 0
    chBoard(0, 0, 3, 5, 16)
    print(Boards)
    DrawBoard(3,5,16)

从上面的代码可以看出:
这里将一个大问题分解成了四个小问题,但是四个小问题,由于特殊块是否在其中,又呈现出两种不同的形态(区别在于特殊块);
通过可视化,我们可以发现在填充时也是一个区域内,不断递归式的进行填充。
这个问题的复杂度为 O(4n)

找到第K个最小数

问题:在一组无序的数中,找到第K个最小数,同时要在 O(n) 的时间复杂度内。
解决方法:借用快速排序的思想,每次我们确定一个轴点,如果这个轴点是第k个,就返回其值;否则不是就转到相应的子区间内,寻找。这样问题的复杂度就可以降为 O(n)
通过进一步分析,可以发现最坏的情况复杂度为 O(n2) ,比如,我们在一个从大到小的顺序里,找最小。
出现上面情况的主要原因在轴点划分的十分不均匀,所以我们可以采取下列措施使得轴点划分的十分均匀:
1. 将所有的元素划分成5个一组,一共 n/5
2. 对每个组进行排序
3. 抽取每个组的中间元素,组成一个新的向量,一共 n/5 .
4. 返回第一步进行循环,直到size为1。
5. 返回这个x。这个x就是partition的x。
整体的代码如下所示:

#include<iostream>
#include<vector>

using namespace std;
void PopSort(vector<int> &A ,int start,int end){
    for (int j = start; j < end; j++){
        for (int i = 0; i < end-j-1; i++){
            if (A[i + start] > A[i + start + 1]){
                int temp = A[i + start];
                A[i + start] = A[i + start + 1];
                A[i + start + 1] = temp;
            }
        }
    }
}

int Partition(vector<int> &A, int start, int end){
    int axis_point = A[start];
    while (start < end){
        while (A[end] >= axis_point && start < end)
            end--;
        A[start] = A[end];
        while (A[start] <= axis_point && start < end)
            start++;
        A[end] = A[start];
    }
    A[start] = axis_point;
    return start;
}

//这里求的是第k个最大的,但是vector的index是从0开始的
int Findkelement(vector<int> &A, int start, int end, int k){
    if (start == end) return A[start];
    int prvo = Partition(A, start, end);
    int j = prvo - start + 1;
    if (k == j) return A[prvo];
    if (k < j) return Findkelement(A, start, prvo-1, k); //如果这里不减1,当prvo等于1,k=1的时候,就进入了死循环。
    else return Findkelement(A, prvo + 1, end, k - j);
}
//改良后的代码
int Select(vector<int> &A, int k){
    //元素少于75的没有必要用复杂的复杂的方法。
    if (A.size() < 75) return Findkelement(A, 0, A.size() - 1, k);
    //分组并进行排序
    int groups = (A.size() + 4) / 5;
    for (int i = 0; i < groups-1; i++){
        PopSort(A, i * 5, (i + 1) * 5);
    }
    PopSort(A, (groups - 1) * 5, A.size());
    vector<int> Median;
    for (int j = 0; j < groups-1; j++){
        Median.push_back(A[3 + j * 5]);
    }
    Median.push_back(A[(groups - 1) * 5]);
    //选出median的中位数
    int x = Select(Median, (Median.size()+1) / 2);
    //根据中位数对元素进行分组
    vector<int> S1, S2;
    for (int i = 0; i < A.size(); i++){
        if (A[i] <= x) S1.push_back(A[i]);
        else S2.push_back(A[i]);
    }
    int value=0;
    //在对应的分组中继续找对应的元素
    if (k <= S1.size()) return Select(S1, k);
    else return Select(S2, k - S1.size());//这里必须要添加return否则就会有逻辑错误。
}

void main(){
    vector<int> A = { 3, 4, 5, 6, 7, 8,3,4,5,6,7,8,43,3,4,5,6,7,8,3,2,7,4,3,45,5,6,4,3,5,6,5,4,3,5,6,7,8,65,5,3,3,5,6,7,7,45,4,4,3,5,6,4,3,54,6,4,6,4,3,6,7,6,5,4,23,34,5,6,6,7,5,5,4,4,3,4,5,6,5,4,3,3,4,5,6,4,4,5,6,78,8,6,5,54,4,5,6,6,4,4,3, 92, 4, 6, 8, 0, 4, 2 };
    cout << Select(A,109) << endl;
}

对上面的算法进行复杂度分析
1. 对于M这个数组的中位数x,至少有 n5/10 数小于它
2. 而小于的每个数在A中至少大于等于3个数,所以在A中只有 3n5/10 小于它,同理也至少有这么多数大于它。所以x取在了中间。
3. 当 n>75 , 3n5/10n/4 ;所以我们的时间复杂度公式可以表示为 T(n)cn+T(n/5)+T(3n/4) ,又因为 n/5+3n/4=19n/20=εn0<ε<1 ,所以这个最差时间复杂度为 O(n) ;
4. 所以从3中我们可以看出为什么是5的原因,在这里5,75是相互对应的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值