UNSW COMP9021 2019T2

COMP9021 Principles of Programmi

2017-08-12  本文已影响0人  Sisyphus235

1.寻找符合条件的平方数组

requirement:找三个完全平方数,它们的数字从1-9中选择,且不重复,找到所有满足条件的平方数组。例如16,25,73984符合条件。

1.1 确定范围

要寻找符合条件的数字,一定要用到循环,这样就要先找到循环的范围。下限很容易确定,是数字1,上限的确定需要思考。
上限一定小于986754321,可这个数字不合适,循环和计算时间往往是成指数倍的关系,要尽可能缩小范围。接着思考,共有三个数,且都是完全平方数,那么最大的数应该是从986754321中拿掉两个最小的完全平方数剩下的部分。最小的完全平方数是1,4,这样就确定了上限的数字,应该是9876532。

1.2 确定条件

为了满足题中要求的不重复的1-9的数字,选中平方数使用的数字需要记录。接下来考虑到细节,虽然1-9的数字是题中要求的,但是在遍历数字的过程中一定还会有数字0,所以初始出现的数字不能是空集,应该是一个包含数字0的数据类型,比如dict或者set。
接着写一个函数记录出现过的数字:

def f(number, digits_seen_so_far):
    number_as_string = str(number)
    new_digits = digits_seen_so_far | set(number_as_string)
    #符号|用来快速连接set,比如{'1', '2'} | {'3'},输出{'1', '2', '3'}
    #符号|其实是OR,同样还有AND,用&,例如,{1, 2} & {1, 2, 3},输出是{2}
    if len(new_digits) == len(digits_seen_so_far) + len(number_as_string):
        return new_digits
    else:
        return None

1.3 寻找合适的数组

使用string进行比较,寻找合适的数组

from math import sqrt

def f(number, digits_seen_so_far):
    number_as_string = str(number)
    new_digits = digits_seen_so_far | set(number_as_string)
    if len(new_digits) == len(digits_seen_so_far) + len(number_as_string):
        return new_digits
    else:
        return None

N = round(sqrt(9876532))
#不要忘记round,不然N是float,不可以直接用于range函数
all_digits = {str(i) for i in range(10)}

for a in range(1, N+1):
    digits_seen_0 = {'0'}
    #初始条件要包含数字0,注意前后set中的数据类型保持一致,都是str
    x = a * a
    digits_seen_1 = f(x, digits_seen_0)
    if not digits_seen_1:
        continue
    for b in range(a + 1, N + 1):
        y = b * b
        digits_seen_2 = f(y, digits_seen_1)
        if not digits_seen_2:
            continue
        for c in range(b + 1, N + 1):
            z = c * c
            digits_seen_3 = f(z, digits_seen_2):
            if not digits_seen_3:
                continue
            if digits_seen_3 == all_digits:
                print(f'{x}, {y}, and {z} is a solution')

1.4 使用set优化程序

使用string比较判断出现过的数字效率低,换成set比较,使用int数据类型。

from math import sqrt

def f(number, digits_seen_so_far):
    while number:
        d = number % 10
        if d in digits_seen_so_far:
            return
        digits_seen_so_far.add(d)
        #{set}.add(number)是set添加元素的方法
        number //= 10
    return digits_seen_so_far

N = round(sqrt(9876532))
all_digits = set(range(10))

for a in range(1, N + 1):
    digits_seen_0 = {0}
    x = a * a
    digits_seen_1 = f(x, digits_seen_0)
    if not digits_seen_1:
        continue
    for b in range(a + 1, N + 1):
        y = b * b
        digits_seen_2 = f(y, digits_seen_1)
        if not digits_seen_2:
            continue
        for c in range(b + 1, N + 1):
            z = c * c
            digits_seen_3 = f(z, digits_seen_2)
            if not digits_seen_3:
                continue
            if digits_seen_3 == all_digits:
                print(f'{x}, {y} and {z} is a solution')

1.5 使用binary进阶优化

set的算法速度已经很快,但是对于更加庞大的运算依然有提升空间。程序的优化往往是从方便程序员思考,走向模拟计算机内存处理过程而实现的。
这个问题判断数字是否出现过,可以进一步进阶到二进制中判断。
引入位运算的概念:

1 << 0, 1 << 1, 1 << 2, 1 << 3
>>>(1, 2, 4, 8)
#含义是向左侧0,1,2,3位添加数字1,这样在binary中1代表1,10代表2,100代表4,1000代表8

这个题目中数字要求是1-9,所以可以把它替代为二进制的9位数,例如,如过出现数字1,则1<<1,已有的数字是10;再出现数字3,则1<<3,已有的数字是1010。直到9个数字都出现,那么二进制显示的数字是1111111110。如果在考虑数字0,可以放在defalut中,初始是二进制的1,它等于十进制的1;终止是二进制的111111111,它是十进制的2**10 - 1。
接着思考如何判断一个数字是否出现过,可以使用&,添加一个出现的数字,使用OR运算。例如,出现过的数字是1,2,3,那么二进制中的数字是1110。此时新的数字出现3,对应的二进制是1000,判断3是否出现过对比的是1000的1所对应的位置上的数字,1110对应的是数字1,这样1&1为True,说明出现过,如果原来没有出现过3,那么记录中的这一位应该是数字0,则0&1为False,说明没出现过。
这样程序优化为:

from math import sqrt

def f(number, digits_seen_so_far):
    while number:
        d = number % 10
        if (1 << d) & digits_seen_so_far:
            return
        digits_seen_so_far |=  1 << d
        number //= 10
    return digits_seen_so_far

N = round(sqrt(9876532))
all_digits = 2 ** 10 - 1

for a in range(1, N + 1):
    digits_seen_0 = 1
    x = a * a
    digits_seen_1 = f(x, digits_seen_0)
    if not digits_seen_1:
        continue
    for b in range(a + 1, N + 1):
        y = b * b
        digits_seen_2 = f(y, digits_seen_1)
        if not digits_seen_2:
            continue
        for c in range(b + 1, N + 1):
            z = c * c
            digits_seen_3 = f(z, digits_seen_2)
            if not digits_seen_3:
                continue
            if digits_seen_3 == all_digits:
                print(f'{x}, {y} and {z} is a solution')

2.寻找质数

例如,寻找50以内的质数,那么循环范围很容易确定,是range(2, 51),最简单的方法是遍历所有数字,只要有除了自身以外的数能整除,则不是质数。

def print_primes(L):
    for i in range(2, len(L)):
        if L[i]:
            print(i, end = ' ')
    print()
L = [True] * 50
print_primes(L)
for i in range(2, len(L)):
    j = 2
    while i * j < len(L):
        L[i * j] = False
        j += 1
    print_primes(L)

但很显然这样的两个循环嵌套做了很多重复运算。
优化程序,首先思考给定50的范围,循环范围是否可以缩小,之前是直接用range(2, 51),可实际上25之后就不需要遍历,因为25*2=50。

from math import sqrt

def print_primes(L):
    for i in range(2, len(L)):
        if L[i]:
            print(i, end = ' ')
    print()
L = [True] * 51
print_primes(L)
max_number = len(L) - 1
upper_bound = round(sqrt(max_number))
for i in range(2, upper_bound + 1):
    j = 2
    while i * j <= max_number:
        L[i * j] = False
        j += 1
    print_primes(L)

进一步思考发现,两个循环遍历i和j,j可以从i * i开始,如果i不是质数,则可以跳过。

from math import sqrt

def print_primes(L):
    for i in range(2, len(L)):
        if L[i]:
            print(i, end = ' ')
    print()
L = [True] * 26
print_primes(L)
max_number = len(L) - 1
upper_bound = round(sqrt(max_number))
for i in range(2, upper_bound + 1):
    if not L[i]:
        continue
    j = i * i
    while i * j <= max_number:
        L[i * j] = False
        j += 1
    print_primes(L)

3.Python运行效率初探

python初始会有一个固定的运算空间,当越界的时候会重新规划申请更多的运算空间,所以运行效率不是平稳改变的,在越界的时候会花更多的时间进行布置。
例如,list的append只需要在最后加一个数,程序复杂度应该是线性的,但实际上会有波动,这些波动就是python在重新安排运算空间。

%matplotlib inline
from matplotlib.pyplot import plot

from time import time

data = []
for i in range(1000, 50001, 1000):
    L = []
    before = time()
    for _ in range(i):
        L.append(None)
    after = time()
    data.append((i, after - before))
plot(*tuple(zip(*data)))
print()

例如,list的insert运算每次都要把所有存在于list中的数向后一位保存,复杂度是指数性的。

%matplotlib inline
from matplotlib.pyplot import plot

from time import time

data = []
for i in range(1000, 50001, 1000):
    L = []
    before = time()
    for _ in range(i):
        L.insert(0, None)
    after = time()
    data.append((i, after - before))
plot(*tuple(zip(*data)))
print()
上一篇下一篇

猜你喜欢

热点阅读