1、算法是独立存在的一种解决问题的方法和思想。
2、算法的五大特征:
(1)输入:算法具有0个或多个输入
(2)输出:算法至少有1个或多个输出
(3)有穷性:算法在有限地步骤之后会自动结束而不会无限循环,并且每一个步骤可以再可接受的时间内完成
(4)确定性:算法中的每一步都有确定的含义,不会出现二义性
(5)可行性:算法的每一步都是可行的,也就是说每一步都能够执行有限的次数完成
3、【操作】如果a+b+c=1000,且a^2+b^2=c^2(a、b、c为自然数),如何求出所有a、b、c可能的组合?
"""如果a+b+c=1000,且a^2+b^2=c^2(a,b,c为自然数),如何求出所有a、b、c可能的组合?"""
import time
start_time = time.time()
for a in range (0, 1001):
for b in range(0, 1001):
for c in range(0, 1001):
if a+b+c == 1000 and a**2+b**2 == c**2:
print('a,b,c:%d,%d,%d'%(a,b,c))
#T = 1000*1000*1000*2
#T(n) = O(n*n*n) = O(n3)
#即时间复杂度为T(n)=n^3*2;T(n)=n^3*10;T(n)=n^3;T(n)=k*g(n)+c,系数k,c并不影响走势;
#称g(n)是T(n)的一个渐进函数。
end_time = time.time()
print('time:%d'%(end_time-start_time))
print('finish')
输出:
a,b,c:0,500,500
a,b,c:200,375,425
a,b,c:375,200,425
a,b,c:500,0,500
time:121
finish
"""【改进】如果a+b+c=1000,且a^2+b^2=c^2(a,b,c为自然数),如何求出所有a、b、c可能的组合?"""
import time
start_time = time.time()
for a in range (0, 1001):
for b in range(0, 1001):
c=1000-a-b #转换为关系式,省去一个c循环
if a**2+b**2 == c**2:
print('a,b,c:%d,%d,%d'%(a,b,c))
#注:每台机器执行的总时间不同,但是执行基本运算数大体形同
#时间复杂度:T(n) = O(n*n*(1+1)) = O(n*n) = O(n2)
end_time = time.time()
print('time:%d'%(end_time-start_time))
print('finish')
输出:
a,b,c:0,500,500
a,b,c:200,375,425
a,b,c:375,200,425
a,b,c:500,0,500
time:0
finish
4、算法效率衡量——可用时间复杂度——由运算多少个基本运算来衡量:单纯依靠运行的时间来比较算法的优劣并不一定是客观准确的!程序的运行离不开计算机环境(包括硬件和操作系统),这些客观原因会影响程序运行的速度并反应在程序的执行时间上。
(1)“大O记法”:对于单调的整数函数f,如果存在一个整数函数g和实常数c>0,使得对于充分大的n总有f(n)<=c*g(n),就说函数g是f的一个渐近函数(忽略常数),记为f(n)=O(g(n))。也就是说,在趋向无穷的极限意义下,函数f的增长速度受到函数g的约束,亦即函数f与函数g的特征相似。
(2)时间复杂度:假设存在函数g,使得算法A处理规模为n的问题示例所用时间为T(n)=O(g(n)),则称O(g(n))为算法A的渐近时间复杂度,简称时间复杂度,记为T(n)
5、时间复杂度的几条基本计算规则:
(1)基本操作,即只有常数项,认为其时间复杂度为O(1)
(2)顺序结构,时间复杂度按加法进行计算
(3)循环结构,时间复杂度按乘法进行计算
(4)分支结构,时间复杂度取最大值
(5)判断一个算法的效率时,往往只需要关注操作数量的最高次项,其它次要项和常数项可以忽略
(6)在没有特殊说明时,我们所分析的算法的时间复杂度都是指最坏时间复杂度
6、【操作】验证不同方法创建列表的效率
"""不同方法创建列表的效率"""
from timeit import Timer
def test1():
l = []
for i in range(1000):
l = l + [i] #方法1:加操作
def test2():
l = []
for i in range(1000):
l.append(i) #方法2:往空列表里加值
def test3():
l = [i for i in range(1000)] #方法3:列表生成器
def test4():
l = list(range(1000)) #方法4:range(1000)转换为列表
t1 = Timer("test1()", "from __main__ import test1")
print("concat ",t1.timeit(number=1000), "seconds")
t2 = Timer("test2()", "from __main__ import test2")
print("append ",t2.timeit(number=1000), "seconds")
t3 = Timer("test3()", "from __main__ import test3")
print("comprehension ",t3.timeit(number=1000), "seconds")
t4 = Timer("test4()", "from __main__ import test4")
print("list range ",t4.timeit(number=1000), "seconds")
# concat 1.1191953000000001 seconds
# append 0.09260209999999991 seconds
# comprehension 0.05035319999999999 seconds
# list range 0.01132449999999996 seconds
7、【操作】验证不同方法往列表里添加元素的效率
"""往列表添加元素的效率"""
from timeit import Timer
def test5():
l = []
for i in range(1000):
l.append(i) #往尾部添加
def test6():
l = []
for i in range(1000):
l.insert(0, i) #往头部添加
t5 = Timer("test5()", "from __main__ import test5")
print("append ",t5.timeit(number=1000), "seconds")
t6 = Timer("test6()", "from __main__ import test6")
print("insert ",t6.timeit(number=1000), "seconds")
# append 0.06438039999999999 seconds
# insert 0.3003407 seconds
8、数据结构:
我们为了解决问题,需要将数据保存下来,然后根据数据的存储方式来设计算法实现进行处理,那么数据的存储方式不同就会导致需要不同的算法进行处理。我们希望算法解决问题的效率越快越好,于是我们就需要考虑数据究竟如何保存的问题,这就是数据结构。
9、算法与数据结构的区别:
数据结构只是静态的描述了数据元素之间的关系。高效的程序需要在数据结构的基础上设计和选择算法。
程序 = 数据结构 + 算法
总结:算法是为了解决实际问题而设计的,数据结构是算法需要处理的问题载体
10、抽象数据类型(ADT):
是指一个数学模型以及定义在此数学模型上的一组操作。即把数据类型和数据类型上的运算捆在一起,进行封装。引入抽象数据类型的目的是把数据类型的表示和数据类型上运算的实现与这些数据类型和运算在程序中的引用隔开,使它们相互独立。最常用的数据运算有五种:插入、删除、修改、查找、排序。