数据结构(python)
Day1
时间复杂度:实现同一个功能模块可以通过不同的算法,就好比去一个地方可以通过不同的路线不同的交通工具一样。时间复杂度就是量化这些算法达成最终目的所要用的步骤时间。
对程序运行的时间进行计算可以使用
import time
start = time.time()
#要执行的代码
end = time.time()
time = end - start #对不同算法有个大致时间上的概念
大O记法:对不同时间复杂度进行量级上的划分。 就像是我们用的硬盘有些是TB级别,有些是GB级别,虽说TB级别下的硬盘容量也都不尽相同,但也可将他们划为一类。
时间复杂度的几条基本计算规则
基本操作,即只有常数项,认为其时间复杂度为O(1)
顺序结构,时间复杂度按加法进行计算
循环结构,时间复杂度按乘法进行计算
分支结构,时间复杂度取最大值
判断一个算法的效率时,往往只需要关注操作数量的最高次项,其它次要项和常数项可以忽略
在没有特殊说明时,我们所分析的算法的时间复杂度都是指最坏时间复杂度
常见时间复杂度
时间消耗:
O
(
1
)
<
O
(
l
o
g
n
)
<
O
(
n
)
<
O
(
n
l
o
g
n
)
<
O
(
n
2
)
<
O
(
n
3
)
<
O
(
2
n
)
<
O
(
n
!
)
<
O
(
n
n
)
O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)
O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)
1⃣️个栗子:
#第一种
for a in range(0, 1001):
for b in range(0, 1001):
for c in range(0, 1001):
if a**2 + b**2 == c**2 and a+b+c == 1000:
print("a, b, c: %d, %d, %d" % (a, b, c))
时间复杂度: T ( n ) = O ( n ∗ n ∗ n ) = O ( n 3 ) T(n) = O(n*n*n) = O(n^3) T(n)=O(n∗n∗n)=O(n3)
#第二种
for a in range(0, 1001):
for b in range(0, 1001-a):
c = 1000 - a - b
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 ( n 2 ) T(n) = O(n*n*(1+1)) = O(n*n) = O(n^2) T(n)=O(n∗n∗(1+1))=O(n∗n)=O(n2)
由此可见,我们尝试的第二种算法要比第一种算法的时间复杂度好多的。
关于列表
from timeit import Timer
def t1():
li = []
for i in range(10000):
li.append(i)
def t2():
li = []
for i in range(10000):
li = li + [i]
# li += [i]
def t3():
li = [i for i in range(10000)]
def t4():
li = list(range(10000))
def t5():
li = []
for i in range(10000):
li.extend([i])
def t6():
li = []
for i in range(10000):
li.append(i)
def t7():
li = []
for i in range(10000):
li.insert(0, i)
timer1 = Timer("t1()", "from __main__ import t1")
print("append:", timer1.timeit(1000))
timer2 = Timer("t2()", "from __main__ import t2")
print("+:", timer2.timeit(1000))
timer3 = Timer("t3()", "from __main__ import t3")
print("[i for i in range]:", timer3.timeit(1000))
timer4 = Timer("t4()", "from __main__ import t4")
print("list(range()):", timer4.timeit(1000))
timer5 = Timer("t5()", "from __main__ import t5")
print("extend:", timer5.timeit(1000))
timer6 = Timer("t6()", "from __main__ import t6")
print("append", timer6.timeit(1000))
timer7 = Timer("t7()", "from __main__ import t7")
print("insert(0)", timer7.timeit(1000))
结果:
append: 0.6904560429999833
+: 133.750434496
[i for i in range]: 0.373120168000014
list(range()): 0.1752699119999761
extend: 0.9450534559999824
append 0.6623780410000109
insert(0) 18.39896798999996
可见:
Day2
顺序表
内存:不同类型的变量都存在内存里,并且占用的空间各不相同,比如int 占用的内存空间为四个字节(也就是32位,ps:每个平台不一样,现在普遍是这样,以前16位机的时候int就是占16位的)。
顺序表:各个变量存储在相邻的内存空间,就是大家都挨着住。
物理地址都可以通过数据的类型推算出来,比如刚说的int类型,上图的c就是4,因为一个int四个字节嘛 (PS:计算机寻址最小单位为字节)
python的列表就是顺序存储的。lst = [22 , 42 , 125 , 6]
但是,list是可以存放不止一种数据类型的,比如:lst = [22 , “abc” , 35 , 78.6]
那么问题来了,上述图(基本布局)关于地址的计算方法不再适用了,因为每个数据类型占用的字节数不同,无法方便的计算出列表某一元素的地址。
这便要引出元素外置的顺序表,下图形象的描绘了含有不同数据类型列表中获取元素的过程。
简单来说就是,地址的地址。虽说各个元素的地址无规律可循,但是至少这些地址都是整型数据吧,那就把这些地址存储到连续的地址里,这样获取元素的时候,就是先基于lst[0]地址的地址,推算出要求的元素的地址的地址,然后就能一路找到元素本身啦
顺序表真正存储的时候还会增添一个表头信息的
顺序表有两种基本的实现方式:一体式和分离式