Chess-Board
问题描述:有
2kX2k
大小的棋盘,其中有一块和其他的不一样。使用4种
L
型的card将棋盘铺满棋盘。如下图所示:
问题分析:首先所给的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,至少有
⌊n−5/10⌋
数小于它
2. 而小于的每个数在A中至少大于等于3个数,所以在A中只有
3∗⌊n−5/10⌋
小于它,同理也至少有这么多数大于它。所以x取在了中间。
3. 当
n>75
,
3∗⌊n−5/10⌋≥n/4
;所以我们的时间复杂度公式可以表示为
T(n)≤cn+T(n/5)+T(3n/4)
,又因为
n/5+3n/4=19n/20=εn,0<ε<1
,所以这个最差时间复杂度为
O(n)
;
4. 所以从3中我们可以看出为什么是5的原因,在这里5,75是相互对应的。