文章目录
数据结构
列表(list)
一个列表可以存储任意大小的数据集合
列表是一个用list类定义的序列,它包括了创建、操作和处理列表的方法。列表中的元素可以通过下标来访问。
列表中使用的函数
-
len()------返回列表的元素个数
list1 = [1,2,3,4,5] print(len(list1)) >>> 5
-
下标运算符**[]**
myList[index]
列表下标是基于0的,下标范围是从 0 — len(myList) - 1 -
列表切片**[start:end]**
list1 = [1,2,3,4,5] print(list1[1:4]) >>> [2, 3, 4]
这个片段是下标从start到end-1的元素构成的一个子列表
list1 = [1,2,3,4,5]
print(list1[:4])
print(list1[3:])
print(list1[:])
>>>
[1, 2, 3, 4]
[4, 5]
[1, 2, 3, 4, 5]
起始下标和结尾下标是可以省略的
-
+、*和in/not in 运算符
可以使用连接运算符( + )来组合俩个列表,使用复制运算符(*)复制列表中的元素。
list1 = [1, 2] list2 = [3, 4] list3 = list1 + list2 list4 = 3 * list3 print(list3, list4, sep="\n") >>> [1, 2, 3, 4] [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]
-
使用for循环遍历函数
list1 = [1, 2, 3, 4, 5, 6, 7] for value in list1: print(value,end='\n') >>> 1 2 3 4 5 6 7
-
比较列表
可以使用比较运算符(>、>= 、<=、==、!=)对列表进行比较。为了进行比较,俩个列表必须包含同样类型的元素。比较采用的的是字典顺序:首先比较前两个元素,如果它们不同就决定了比较的结果;如果他们相同,那就继续比较接下来的两个元素,一直重复这个过程,直到比较完所有的元素。list1 = ['green', 'yellow','red'] list2 = ['red', 'yellow', 'green'] print(list1 == list2) print(list1 != list2) print(list1 >= list2) print(list1 <= list2) >>> False True False True
-
列表解析
列表解析提供了一种创建顺序元素列表的简洁方式
list1 = [x for x in range(20)] list2 = [x for x in list1 if x % 2 == 0] print(list1,list2,sep="\n") >>> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
列表方法
append(x)------将元素添加到列表结尾
count(x)------返回元素x在列表中的出现次数
extend(list)------将列表中的所有元素追加到列表中
index(x)------返回元素x在列表中第一次出现的下标
insert(index,object)------将元素x插入列表中指定下标index处
pop(i)------删除给定位置的元素并且返回它,参数i是可选的。如果没有指定它,那么删除list.pop() 并且返回列表中的最后一个元素。
remove(x)------删除列表中第一次出现的x
reverse()------将列表中的所有元素倒序
sort()------以升序对列表中的元素排序
min()------查找列表元素的最小值,若是字符串,则按照字母顺序比较
max()------查找列表元素的最大值,若是字符串,则按照字母顺序比较
sum()------对列表求和
list1 = [1, 2, 3, 4, 5, 6]
list1.append(10)
print(list1)
count = list1.count(1)
print(count)
list2 = [1, 2, 3]
list1.extend(list2)
print(list1)
index = list1.index(2)
print(index)
list1.insert(0,'python')
print(list1)
list1.pop(0)
print(list1)
list1.remove(1)
print(list1)
list1.reverse()
print(list1)
list1.sort()
print(list1)
>>>
[1, 2, 3, 4, 5, 6, 10]
1
[1, 2, 3, 4, 5, 6, 10, 1, 2, 3]
1
['python', 1, 2, 3, 4, 5, 6, 10, 1, 2, 3]
[1, 2, 3, 4, 5, 6, 10, 1, 2, 3]
[2, 3, 4, 5, 6, 10, 1, 2, 3]
[3, 2, 1, 10, 6, 5, 4, 3, 2]
[1, 2, 2, 3, 3, 4, 5, 6, 10]
-
将字符串分成列表
str 类中包含了split方法,它对于将字符串中的条目分成列表是非常有用的。且经常用于自己输入数据。
items = "Taco Tuesday hey my body".split() print(items) date = "2020/6/22".split('/') print(date) >>> ['Taco', 'Tuesday', 'hey', 'my', 'body'] ['2020', '6', '22']
-
输入列表
在实际的处理数据的运算中,可能需要人为手动输入数据
list1 = [] for value in range(10): list1.append(eval(input("请输入:"))) print(list1) s = input("Enter 10 numbers separated by space from one line:") items = s.split() lst = [eval(x) for x in items] >>> 请输入:2 请输入:3 请输入:4 请输入:3 请输入:46 请输入:6 请输入:3 请输入:46 请输入:34 请输入:56 [2, 3, 4, 3, 46, 6, 3, 46, 34, 56] Enter 10 numbers separated by space from one line:1 2 3 4 52 35 23 55323 5 23 423 42 [1, 2, 3, 4, 52, 35, 23, 55323, 5, 23, 423, 42]
-
列表移位
在实际的数据操作中,需要将列表中的元素进行左移和右移
# 列表左移 def Turn_left(list1): temp = list1[0] for i in range(1,len(list1)): list1[i-1] = list1[i] list1[-1] = temp return list1 # 列表右移 def Turn_right(list1): for i in range(0,len(list1),-1): list1[i] = list1[i-1] return list1 if __name__ == '__main__': list1 = [1,2,3,4,5,6,7,8] print(Turn_left(list1)) print(Turn_right(list1)) >>> [2, 3, 4, 5, 6, 7, 8, 1] [2, 3, 4, 5, 6, 7, 8, 1]
-
简化代码
列表可以大大简化某些任务的,可以用来使用下标访问一些有规律的数值,例如月份,年份,周等
多维列表
-
一张表或矩阵中的数据可以存储在一个二维列表中
-
二维列表中的值可以通过行下表和列下标来访问
-
使用输入值初始化列表
matrix = [] numberOfRows = eval(input("Enter the number of rows: ")) numberOfColumns = eval(input("Enter the number of columns: ")) for row in range(numberOfRows): matrix.append([]) for column in range(numberOfColumns): matrix[row].append(random.randint(0, 99)) print(matrix) matrix = [] numberOfRows = eval(input("Enter the number of rows: ")) numberOfColumns = eval(input("Enter the number of columns: ")) for row in range(numberOfRows): matrix.append([]) value = eval(input("Enter an element and press Enter: ")) print(matrix)
多维列表在这里不深入研究,有兴趣的可以学习numpy用法
元组(tuple)
在我们希望某些数据不可被更改时,我们就可以用到元组,**所谓元组的不可变指的是元组所指向的内存中的内容不可变。**考虑到数据的安全性,元组使用的效率高。
元组和列表类似,但是元组中的元素是固定的;也就是说,一旦一个元组被创建,就无法对元组中的元素进行添加、删除、替换或重新排列。
元组基本操作
-
创建元组
tup1 = ('physics', 'chemistry', 1997, 2000) tup2 = (1, 2, 6, 4, 5) tup3 = ("a", "b", "c", "d") tuple4 = ("1",'tuple',) print(tup1,tup2,tup3)
元组中只包含一个元素时,需要在元素后面添加逗号来消除歧义
tuple1 = (39,)
-
访问元组
tup1 = ('physics', 'chemistry', 1997, 2000) tup2 = (1, 2, 6, 4, 5) print(tup1[0],tup1[1:3])
-
修改元组
元组中的元素是不允许修改的,但是我们可以进行组合
tup1 = ('physics', 'chemistry', 1997, 2000) tup2 = (1, 2, 6, 4, 5) tup3 = (49,39) tup3 = tup1 + tup2 print(tup3) >>> ('physics', 'chemistry', 1997, 2000, 1, 2, 6, 4, 5)
在这里我们可以看到前面已经有了tup3,但是打印出来却显示后面组合的tup3,这是为什么呢,这是因为tup3元组指向了新的元组元素。如下:
tup1 = ('physics', 'chemistry', 1997, 2000) tup2 = (1, 2, 6, 4, 5) tup3 = (49,39) print(tup3,id(tup3)) tup3 = tup1 + tup2 print(tup3,id(tup3)) >>> (49, 39) 2609945989064 ('physics', 'chemistry', 1997, 2000, 1, 2, 6, 4, 5) 2609945293112
二者在内存中指向的地址是不一样的,理解元组的地址指向重要
-
删除元组
元组中的元素值是不允许删除的,但是我们却可以使用del语句来删除整个元组
tup1 = ('physics', 'chemistry', 1997, 2000) tup2 = (1, 2, 6, 4, 5) print(tup1,tup2) del tup1, tup2 print(tup2, tup1) >>> ('physics', 'chemistry', 1997, 2000) (1, 2, 6, 4, 5) Traceback (most recent call last): File "D:/python_work/list.py", line 5, in <module> print(tup2, tup1) NameError: name 'tup2' is not defined
当元组不存在时,会显示异常信息
-
元组运算符
与列表一样,我们可以使用 +、in/not in ,**len()**来进行运算,可以进行组合和运算,产生新的元组
tup1 = ('physics', 'chemistry', 1997, 2000) tup2 = (1, 2, 6, 4, 5) tup3 = tup1 * 2 tup4 = tup1 + tup2 print(tup3,tup4,sep='\n') c = 2 in tup2 value = tup2 == tup1 print(value,c) print(len(tup2)) >>> ('physics', 'chemistry', 1997, 2000, 'physics', 'chemistry', 1997, 2000) ('physics', 'chemistry', 1997, 2000, 1, 2, 6, 4, 5) False True 5
-
比较元组
同列表一样,可以对其中的元素进行比较
tup1 = (1997, 2000) tup2 = (1, 2, 6, 4, 5) value = tup2 >= tup1 print(value) >>> False
tup1 = (1, 1, 3, 2) tup2 = (1, 2, 6, 4, 5) value = tup2 >= tup1 print(value) >>> True
tup1 = (2, 1, 3, 2) tup2 = (1, 2, 6, 4, 5) value = tup2 >= tup1 print(value) >>> False
元组的比较也是采用的字典顺序,首先比较前两个元素,如果它们不同就决定了比较的结果;如果他们相同,那就继续比较接下来的两个元素,一直重复这个过程,直到比较完所有的元素。
-
元组的索引,截取
同列表一样,可以指定范围来截取元组
tup1 = ('physics', 'chemistry', 1997, 2000) tup2 = (1, 2, 6, 4, 5) tp = tup2[3:] print(tp) >>> (4, 5)
-
无关闭分隔符
任意无符号的对象,以逗号隔开,默认为元组,如下实例:
s = 1,2,34,"mojito" print(s,type(s)) >>> (1, 2, 34, 'mojito') <class 'tuple'>
元组内置函数
1、len(tup1):计算元组个数
2、max(tuple):返回元组中的最大值
3、min(tuple):返回元组中的最小值
4、tuple(iterable):将可迭代系列转换为元组
关于元组不可变
前面已经讲解过**元组的不可变指的是元组所指向的内存中的内容不可变。**在修改元组中,我们也可以看到其内存地址的变化等等,现在看下面实例:
tup1 = ('physics', 'chemistry', 1997, 2000)
tup2 = (1, 2, 6, 4, 5)
tup2[0] = 3
>>>
Traceback (most recent call last):
File "D:/python_work/list.py", line 3, in <module>
tup2[0] = 3
TypeError: 'tuple' object does not support item assignment
tup1 = ('physics', 'chemistry', [1997, 2000])
tup2 = (1, 2, 6, 4, 5)
tup1[2] [0]= 3
print(tup1)
>>>
('physics', 'chemistry', [3, 2000])
这里修改的是元组中的列表元素,所以理解元组的不可变概念是很重要的。
集合(set)
集合和列表类似,可以使用它们来存储一个元素集合。但是,不同于列表,集合中的元素是不重复且不是按照特定顺序放置的,且元素类型为,string、tuple、frozen-set、数字等不可变类型(哈希值)
集合的基本操作
-
创建集合
s1 = set() s2 = {1, 3, 5} s3 = set((1, 3, 5)) s4 = set([x * 2 for x in range(1,10)]) s5 = set('abac') print(s1,s2,s3,s4,s5,sep='\n') >>> set() {1, 3, 5} {1, 3, 5} {2, 4, 6, 8, 10, 12, 14, 16, 18} {'a', 'c', 'b'}
-
操作和访问集合
可以通过add(e),update(e,a,d,…)或remove(e)方法来对一个集合添加或删除元素。可以使用函数len,min,max,min和sum对集合操作,可以使用for循环遍历一个集合中的所有元素。可以使用in或not in 运算符来判断一个元素是否在一个集合当中。
1、len()
s1 = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'} s2 = {1, 2, 3, 4, 5, 6, 7, 8} length = len(s1) print(length)
2、add()------添加单个元素添加的位置是随机的
s1 = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'} s2 = {1, 2, 3, 4, 5, 6, 7, 8} s1.add('python') print(s1) >>> {'orange', 'pear', 'banana', 'python', 'apple'}
3、update()-----添加多个数据元素,且参数可以是列表,元组,字典等
s1 = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'} s2 = {1, 2, 3, 4, 5, 6, 7, 8} s1.update(s2,(1,)) print(s1) >>> {1, 2, 3, 4, 5, 6, 7, 'orange', 'banana', 8, 'apple', 'pear'}
4、remove()------删除元素,如果删除一个集合中不存在的元素,remove()方法将抛出一个KeyErrror异常。
s1 = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'} s2 = {1, 2, 3, 4, 5, 6, 7, 8} s1.update(s2,(1,)) print(s1) s1.remove('apple') print(s1) >>> {1, 2, 3, 4, 5, 6, 7, 8, 'pear', 'banana', 'orange', 'apple'} {1, 2, 3, 4, 5, 6, 7, 8, 'pear', 'banana', 'orange'}
5、min、max、sum
s1 = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'} s2 = {1, 2, 3, 4, 5, 6, 7, 8} min1 = min(s1) max1 = max(s2) sum1 = sum(s2) print(min1,max1,sum1,sep='\n') >>> apple 8 36
6、for循环遍历
s1 = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'} s2 = {1, 2, 3, 4, 5, 6, 7, 8} for value in s1: print(value,end="\n") >>> pear orange banana apple
集合的数学运算
-
子集和超集
s1 = {1,2,3} s2 = {1, 2, 3, 4, 5, 6, 7, 8} value = s1.issubset(s2) # s1 is a subset of s2 print(value) >>> True
s1 = {1,2,4} s2 = {1, 2, 3, 4, 5, 6, 7, 8} value = s2.issuperset(s1) # s1 is a subset of s2 print(value) >>> True
-
相等性测试-----使用运算符 == 和 != 来检测两个集合是否包含相同的元素
s1 = {1,2,4} s2 = {1,4,2} value = s1 == s2 print(value) >>> True
集合中判断两个集合的相等性,不判断两个集合的顺序,只判断集合元素是否相同
使用传统的比较符(>、>=、<=、<)来比较集合毫无意义,因为集合的元素并未排序,但是,当这些操作符用在集合上时有着特殊的含义:
-
如果s1是s2的一个真子集,则s1<s2返回True。
-
如果s1是s2的一个子集,则s1 <= s2返回True。
-
如果s1是s2的一个真超集,则s1>s2返回True。
-
如果s1是s2的一个超集,则s1>=s2返回True。
-
集合运算
python提供了求并集、交集、差集和对称差集合的运算方法。
并集
s1 = {0,2,4} s2 = {1,3,5} s1.union(s2) s3 = s1.union(s2) s4 = s1 | s2 print(s3,s4) >>> {0, 1, 2, 3, 4, 5} {0, 1, 2, 3, 4, 5}
交集
s1 = {1,2,4} s2 = {1,3,5} s1.union(s2) s3 = s1.union(s2) s4 = s1 | s2 print(s3,s4) >>> {1} {1}
差集
s1 = {1,2,4} s2 = {1,3,5} s3 = s1.difference(s2) s4 = s1 - s2 print(s3,s4) >>> {2, 4} {2, 4}
对称差(异或)------对称差集合是包含了除它们共同元素之外所有这两个集合之中的元素。
s1 = {1,2,4} s2 = {1,3,5} s3 = s1.symmetric_difference(s2) print(s3) >>> {2, 3, 4, 5}
字典
一个字典是一个存储键值对集合的容器对象,其使用关键组实现快速获取、删除和更新值。每一个条目都由一个关键字,然后跟着一个冒号,再跟着一个值组成。形式:Key:Value。关键字必须是哈希类型(不可变类型)
字典的基础操作
-
创建字典
dic1 = {1:"关羽",2:"赵云"} dic2 = set()
-
添加、修改和查询
dic1 = {1:"关羽",2:"赵云"} dic1[3] = "曹操" dic1[1] = "曹操" print(dic1) print(dic1[3]) >>> {1: '曹操', 2: '赵云', 3: '曹操'} 曹操
-
删除条目
dic1 = {1:"关羽",2:"赵云"} del dic1[1] print(dic1) >>> {2: '赵云'}
-
len函数------获得一个字典中条目的数目
dic1 = {1:"关羽",2:"赵云"} lengh = len(dic1) print(lengh) >>> 2
-
检测关键字是否在字典中
可以使用in或者not in 运算符来判断一个关键字是否在一个字典中dic1 = {1:"关羽",2:"赵云"} value = 1 in dic1 print(value) >>> True
-
相等性检测
dic1 = {"red":41,"blue":31} dic2 = {"blue":3,"red":41} value = dic1 == dic2 print(value) >>> False
注:在进行比较时,是不考虑字典中的顺序的,因为字典中的条目是没有顺序的
字典方法
1、清空字典
dic1 = {"red":41,"blue":31}
dic2 = {"blue":3,"red":41}
dic1.clear()
print(dic1)
>>>
{}
2、get()------dict.get()的方法通过key获取value。
dic1 = {"red":41,"blue":31}
dic2 = {"blue":3,"red":41}
value = dic1.get("blue")
print(value)
>>>
31
注:当key值不存在时,就会返回None
3、setdefault()------获取或添加键值对,不同于get()的是,当访问的key值不存在时,dict.setfault()的方法会将该值添加到原字典中,相应的value为None
dic1 = {"red":41,"blue":31}
dic2 = {"blue":3,"red":41}
value = dic1.setdefault("black")
print(value,dic1,sep="\n")
>>>
None
{'red': 41, 'blue': 31, 'black': None}
4、update()用一个字典更新另一个字典,若被更新的字典已含有对应的key值,这个key值对应的原value会被替换
dic1 = {"red": 41, "blue": 31}
dic2 = {"blue": 3, "red": 41}
dic1.update(dic2)
print(dic1)
>>>
{'red': 41, 'blue': 3}
5、items()------获取字典的所有键值对,其获取的是一个字典视图,包含所有的字典项,每个元素为一个键值对。
data = {"x": 12, "y": 45, "z": 66}
print(data.items())
>>>
dict_items([('x', 12), ('y', 45), ('z', 66)])
6、keys()------获取字典的所有键
data = {"x": 12, "y": 45, "z": 66}
print(data.keys())
>>>
dict_keys(['x', 'y', 'z'])
7、values()------获取字典的所有值
data = {"x": 12, "y": 45, "z": 66}
print(data.values())
>>>
dict_values([12, 45, 66])
8、pop()获取指定键关联值并删除键值对,并删除该键值对
data = {"x": 12, "y": 45, "z": 66}
print(data.pop('x'), data,sep='\n')
>>>
12
{'y': 45, 'z': 66}
9、popitem()------用于删除字典中最后一个键值对
data = {"x": 12, "y": 45, "z": 66}
print(data.popitem(), data, sep='\n')
>>>
('z', 66)
{'x': 12, 'y': 45}
10、fromkeys------快速生成字典
seq = ["周","吴","郑"]
dict1 = dict.fromkeys(seq,10)
print(dict1)
>>>
{'周': 10, '吴': 10, '郑': 10}
字符串
字符串对象是不可变的,若具有相同的字符串对象,则是指向一个id数。
字符串的基础操作
-
字符串的创建
s1 = str() s2 = "字符串"
-
字符串的基本操作
1.字符串长度:len(str)
s1 = "hello world!" len = len(s1) print("字符串长度:", len)
2.下标字符串:s[start:ending:step],默认步长为1
s1 = "hello world!" print(s1[0:5])
3.截取运算符:str[start:end],s[1:4]
s1 = "hello world!" print(s1[1:4])
4.连接运算符:s1+“to”+s2
s1 = "hello world!" s2 = "my friend" s3 = s1 +" to "+ s2 print(s3)
5.in和not in 运算符
s1 = "hello world!" s2 = "my friend" s3 = "hello" if s3 in s2 : print("s3 in s2") elif s3 in s1: print("s3 in s1") else: print("either")
6.比较字符串:==;!=;<;>;>=;<= 比较的是字符串的长度和值,返回的是布尔变量
7.迭代字符串:s = "hello world " for value in s: print(value)
测试字符串
isalnum()------若该字符串中的字符是字母数字且至少有一个字符则返回True
isalpha()------如果这个字符串中的字符是字母且至少有一个字符则返回True
isdigit()------若该字符串中只含有数字字符则返回True
isidentifier()------若这个字符串是python标识符则返回True
islower()------若这个字符串中全是小写且至少有一个字符则返回True
isupper()------若这个字符串中的所有字符全是大写的且至少有一个字符则返回true
isspace()------若这个字符串中只包含空格则返回true
# 检测密码
def testingPassword():
password = input("请输入密码:")
count = 0
if (len(password) >= 8) and (password.isalnum()):
for value in password:
if value.isdigit():
count += 1
if count >= 2:
print("Valid Password")
else:
print("Invalid Password")
搜索子串
endswith(s1:str)–如果字符串是以字串s1结尾则返回True
startswith(s1:str)–如果字符串是以字串s2开始则返回True
find(s1):int–返回s1在这个字符串的最低下标,如果字符串不存在s1,则返回-1
rfind(s1):int–返回s1在这个字符串的最高下标,如果字符串中不存在s1,则返回-1
count(substring):–返回在字符串中出现的无覆盖的次数
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200513105538562.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjcwMjAzOA==,size_16,color_FFFFFF,t_70
转换字符串
capitalize()–返回这个字符串并只大写第一个字符
lower()–返回这个复制的字符串并将所有字母转换成小写的
upper()–返回这个复制的字符串并将所有字母转换成大写的
title()–返回这个复制的字符串并大写每个单词的首字母
swapcase()–返回这个复制的字符串,将小写字母转换成大写,将大写字母转换成小写
replace(old,new)–返回一个新的字符串,它用一个新字符串替代旧字符串所有出现的地方
删除字符串中的空格
lstrip(str):–去掉前端空白字符的字符串
rstrip(str):–返回去掉末端空白字符的字符串
strip(str):–返回两端空白字符的字符串
格式化字符串:
center(width):str–返回在给定宽度域上居中的字符串副本
ljust(width):str–返回在给定宽度域上左对齐的字符串文本
rjust():str–返回在给定宽度域上右对齐的字符串文本
format():–格式化一个字符串
-
f-string
f-string用大括号 {} 表示被替换字段,其中直接填入替换内容
f-string采用 {content:format} 设置字符串格式,其中 content 是替换并填入字符串的内容,可以是变量、表达式或函数等,format 是格式描述符。采用默认格式时不必指定 {:format}name = "sky" age = 17 print(f"My name is {name}, age is {age}.") # 输出:My name is sky, age is 187 fruits = {"apple":"red","banana":"yellow"} s = F"The apple is {fruits['apple']},the banana is {fruits['banana']}" # f-string内的引号和整体的外部引号不能一致,否则会解析错误 print(s) # 输出:The apple is red,the banana is yellow s = "I love U" print(f"the reverse is '{s[::-1]}'") # 输出:the reverse is 'U evol I' # 逆置还可以:"".join(reversed(s))
-
join()函数
s = "Life is painting a picture, not doing a sum." s1 = " ".join(s) print(s1)
常见数据结构总结
算法复杂度
算法复杂度分为时间复杂度和空间复杂度
其作用:时间复杂度是指执行算法所需要的计算工作量;而空间复杂度是指执行这个算法所需要的内存空间(算法的复杂性体现在运行该算法时的计算机所需要资源的多少上,计算机资源最重要的就是时间和空间(即寄存器)资源,因此复杂度分为时间和空间复杂度)
简单来说,时间复杂度指的是语句执行次数,空间复杂度指的是算法所占的存储空间
计算时间复杂度的方法:
- 用常数1代替运行时间中的所有加法常数
- 修改后的运行次数函数中,只保留最高阶项
- 去除最高阶项的系数
常见的时间复杂度高低排序:O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3)
实例计算
O(1)
- O(1)就是最低的时空复杂度了,也就是耗时/耗空间与输入数据大小无关,无论输入数据增大多少倍,耗时/耗空间都不变。哈希算法就是典型的O(1)时间复杂度,无论数据规模多大,都可以在一次计算后找到目标
print("I will return find you love you live without shame")
a = b
temp = b
b = a
O(log n)
-
对数阶,如二分搜索算法。操作的数量与输入数据的规模 N的比例为log2(n)。n = 1000,000 -> 30 operations
例如:1,3,5,7,9;找出7
假设全部遍历的时间频度为n;
二分查找每次砍掉一半,即为n/2;
即n/2^x = 1,x = log 2 n
def binary_search(data_list, target):
"""
:param data_list: 传入的有序列表
:param target: 传入要查找的目标值
"""
low = 0 # 最小数下标
high = len(data_list) - 1 # 最大数的下标
index = 1 # 用index来记录查找的次数
while low <= high:
mid = (low + high) // 2 # 取中间值
if data_list[mid] == target:
return "一共查找了%d次,此数字在列表中的下标为:%d" % (index, mid)
elif data_list[mid] > target:
high = mid - 1 # 如果中间值比目标值大,则在mid左半边找
else:
low = mid + 1 # 如果中间值比目标值小,则在mid右半边找
index += 1
return "一共找了%d次,找不到这样的值!" % index
ret1 = binary_search(list(range(1, 1000)), 888)
ret2 = binary_search(list(range(1, 1000)), 10000)
print(ret1)
print(ret2)
O(n)
for i in range(n): # O(n)
print('Hello world')
O(n^2)
for i in range(n): # O(n^2)
for j in range(n):
print('Hello world')
O(n^3)
for i in range(n): # O(n^3)
for j in range(n):
for k in range(n):
print('Hello world')
python数据结构
链表
数据结构是计算机科学必须掌握的一门学科,在C语言中,可以使用“指针+结构体”来实现链表;而在python中,则可以采用“引用+类”来实现链表。
链表的定义:是一组数据项的集合,其中每个数据项都是一个节点的一部分,每个节点还包含指向下一个节点的链接。
链表的结构:data 为自定义的数据,next为下一个节点的地址,head为保存首位节点的地址:
单向链表:
单向链表(单链表)是链表的一种,它由节点组成,每个节点都包含下一个节点的指针。
示意图:
这里参考了关于链表的实现原理:传送门
(https://img-blog.csdnimg.cn/20200818084806219.png#pic_center)
单链表删除节点:
单链表增加节点:
单链表的特点是:节点的链接方向是单向的;相对于数组来说,单链表的的随机访问速度较慢,但是单链表删除/添加数据的效率很高。
class Node:
def __init__(self,data):
self.data = data
self.next = None
class chain:
def __init__(self):
self.head = None
def is_empty(self):
return not self.head
def length(self):
count = 0
cur = self.head
while cur != None:
count += 1
cur = cur.next
return count
def add(self,data):
node = Node(data)
node.next = self.head
self.head = node
def append(self,data):
cur = self.head
while cur.next != None:
cur = cur.next
node = Node(data)
cur.next = node
def traverse(self):
cur = self.head
while cur != None:
print(cur.data, end='-***-')
cur = cur.next
print()
def insert(self,index,data):
if index < 0:
self.add(data)
elif index > self.length() -1:
self.append(data)
else:
cur = self.head
for i in range(index - 1):
cur = cur.next
node = Node(data)
node.next = cur.next
cur.next = node
def remove(self,data):
cur = self.head
pre = None
while cur != None:
if cur.data == data:
if cur == self.head:
self.head = self.head.next
return
pre.next = cur.next #前节点等于后节点
return
pre = cur
cur = cur.next
def search(self, data):
cur = self.head
while cur != None:
if cur.data == data:
print("数据%f存在于链表中" % cur.data)
return True
cur = cur.next
print("数据不存在于链表中")
return False
if __name__ == '__main__':
chain = chain()
chain.add(0)
chain.traverse()
for i in range(1,9):
chain.append(i)
chain.traverse()
chain.length()
chain.insert(3,100)
chain.traverse()
chain.remove(100)
chain.traverse()
chain.insert(-2,99)
chain.traverse()
chain.insert(100,1000)
chain.traverse()
chain.search(5)
>>>
0-***-
0-***-1-***-2-***-3-***-4-***-5-***-6-***-7-***-8-***-
0-***-1-***-2-***-100-***-3-***-4-***-5-***-6-***-7-***-8-***-
0-***-1-***-2-***-3-***-4-***-5-***-6-***-7-***-8-***-
99-***-0-***-1-***-2-***-3-***-4-***-5-***-6-***-7-***-8-***-
99-***-0-***-1-***-2-***-3-***-4-***-5-***-6-***-7-***-8-***-1000-***-
数据5.000000存在于链表中
双向链表:
双向链表(双链表)是链表的一种。和单链表一样,双链表也是由节点组成,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。
class Node:
def __init__(self,data):
self.data = data
self.pre = None
self.next = None
class chain:
def __init__(self):
self.head = None
def is_empty(self):
return self.head == None
def add(self,data):
node = Node(data)
if self.is_empty():
self.head = node
else:
node.next = self.head
self.head.pre = node
self.head = node
def append(self,data):
node = Node(data)
if self.is_empty():
self.head = node
else:
cur = self.head
while cur.next != None:
cur = cur.next
cur.next = node
node.pre = cur
def length(self):
cur = self.head
count = 0
while cur != None:
count += 1
cur = cur.next
return count
def travel(self):
cur = self.head
while cur != None:
print(cur.data,end="--***--")
cur = cur.next
def search(self,data):
cur = self.head
while cur != None:
if cur.data == data:
print("%s存在于链表中"%str(cur.data))
else:
return None
def insert(self,index,data):
if index <= 0:
self.add(data)
elif index > self.length() - 1:
self.append(data)
else:
cur = self.head
for i in range(index - 1):
cur = cur.next
node = Node(data)
node.pre = cur
node.next = cur.next
cur.next = node
def remove(self,data):
if self.is_empty():
return
else:
cur = self.head
if cur.data == data:
if cur.next == None:
self.head = None
else:
cur.next.pre = None
self.head = cur.next
else:
while cur != None:
if cur.data == data:
cur.pre.next = cur.next
cur.next.pre = cur.pre
break
cur = cur.next
if __name__ == '__main__':
chain = chain()
chain.add(0)
chain.append(1)
chain.insert(1,100)
chain.insert(2, 101)
chain.remove(101)
chain.travel()
>>>
0--***--100--***--1--***--
栈
栈的实现
栈(stack)是一种数据结构,又称为堆栈。其是一种运算受限的线性表,其限制是仅允许在表的一端进行插入和删除运算。
运作方式:后进先出原则
栈允许进行插入和删除的操作的一端为栈顶(top),另外一端为栈底(bottom),栈底固定,而栈顶浮动;栈中的元素个数为POP。
栈的实现过程:
1、创建一个Stack类
对栈进行初始化参数设计
具体实现代码如下:
class Stack(object):
def __init__(self, limit = 10):
self.stack = [] # 存放元素
self.limit = limit # 栈容量极限
2.push进栈
2.push进栈
压入push:将新元素放置栈顶
当新元素入栈时,栈顶上移,新元素放在栈顶。
具体代码实现如下:
def push(self, data):
if len(self.stack) >= self.limit:
print(‘StackOverflowerError’)
pass
self.stack.append(data)
3.pop退栈
弹出pop:从栈顶移出一个数据
栈顶元素拷贝出来
栈顶下移
拷贝出来的栈顶作为函数的返回值
具体代码如下:
def peek(self):
if self.stack:
return self.stack.pop()
else:
raise IndexError('pop from an empty stack') # 空栈不能被弹出
4.添加其它函数
peek:查看堆栈的最上面的元素
is_empty:判断栈是否为空
size:返回栈的大小
具体代码实现如下:
def peek(self):
if self.stack:
return self.stack[-1]
def is_empty(self):
return not bool(self.stack)
def size(self):
return len(self.stak)
完整代码:
完整代码:
class Stack(object):
def __init__(self, limit=10):
self.stack = [] # 存放元素
self.limit = limit # 栈容量极限
def push(self, data): # 判断栈是否溢出
if len(self.stack) >= self.limit:
print('StackOverflowError')
pass
self.stack.append(data)
def pop(self):
if self.stack:
return self.stack.pop()
else:
raise IndexError('pop from an empty stack') # 空栈不能被弹出
def peek(self): # 查看堆栈的最上面的元素
if self.stack:
return self.stack[-1]
def is_empty(self): # 判断栈是否为空
return not bool(self.stack)
def size(self): # 返回栈的大小
return len(self.stack)
if __name__ == '__main__':
s = Stack()
s.push(5)
s.push(2)
s.push(3)
print(s.pop())
print(s.pop())
print(s.pop())
队列(Queue)
相信大家都在在买奶茶的时候排队过,队列就是这样一种数据结构。
在计算机科学中,队列是一个集合,其中集合重的实体按顺序保存,集合上的主要操作是向后端位置添加数据,称为入队,从前端删除数据,称为出队。
class Queue.Queue(maxsize = 0)
其中,队列分为三种,分别为基本FIFO队列,First in First Out ,先进先出。Queue提供了一个基本的FIFO容器。maxsize是一个int型,指明了队列中能存放的数据个数的上限,插入会导致阻塞,直到队列中的数据被消费掉。如果maxsize小于或者等于0,队列大小没有限制。
队列的接口:
接口 | 描述 |
---|---|
add(x) | 入队 |
delete() | 出队 |
clear() | 清空队列 |
isEmpty() | 判断队列是否为空 |
isfull() | 判断队列是否未满 |
length() | 队列的当前长度 |
capability() | 队列的容量 |
常用操作:
添加任务
向队列中添加任务,直接调用put()函数
import queue
q = queue.Queue(maxsize= 1)
q.put(100)
put()
函数完整的函数签名如下Queue.put(item, block=True, timeout=None)
,如你所见,该函数有两个可选参数。- 默认情况下,在队列满时,该函数会一直阻塞,直到队列中有空余的位置可以添加任务为止。如果 timeout 是正数,则最多阻塞 timeout 秒,如果这段时间内还没有空余的位置出来,则会引发
Full
异常。
import queue
q = queue.Queue(maxsize= 1)
q.put(100)
q.put(50,True,1)
Traceback (most recent call last):
File "D:/python_work/二分查找/Queue.py", line 4, in <module>
q.put(50,True,1)
File "C:\Python\Python36\lib\queue.py", line 141, in put
raise Full
queue.Full
-
当 block 为 false 时,timeout 参数将失效。同时如果队列中没有空余的位置可添加任务则会引发
Full
异常,否则会直接把任务放入队列并返回,不会阻塞。 -
当 block 为 false 时,timeout 参数将失效。同时如果队列中没有空余的位置可添加任务则会引发
Full
异常,否则会直接把任务放入队列并返回,不会阻塞。1 >>> import queue 2 >>> q = queue.Queue(maxsize=1) 3 >>> q.put(100) 4 >>> q.put(100,False,2) 5 Traceback (most recent call last): 6 File "<stdin>", line 1, in <module> 7 File "E:\Python37-32\lib\queue.py", line 136, in put 8 raise Full 9 queue.Full 10 # 创建一个容量为 1 的队列,在第二次放入任务时指定为非阻塞模式,则会立刻引发 Full 异常
-
另外,还可以通过
Queue.put_nowait(item)
来添加任务,相当于Queue.put(item, False)
,不再赘述。同样,在队列满时,该操作会引发Full
异常。
获取任务
从队列中获取任务,直接调用get()函数即可
import queue
q = queue.Queue(maxsize= 3)
q.put(100)
value = q.get()
print(value)
>>>
100
其它常用方法
判断队列是否为空,返回True,或者为False。
判断队列是否为满,返回True,或者False
>>> import queue
>>> q = queue.Queue(maxsize=1)
>>> q.empty()
True
>>> q.full()
False
>>> q.put(100)
>>> q.empty()
False
>>> q.full()
True
-
队列对比
-
FIFO
-
queue.Queue是FIFO队列,出队顺序和入队顺序是一致的
import queue q = queue.Queue() for index in range(10): q.put(index) while not q.empty(): print(q.get(), end=", ") ## 输出结果如下 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
-
LIFO队列
-
queue.LifoQueue()是LIFO队列,出队顺序跟入队顺序完全是相反的,类似于栈。
import queue q = queue.LifoQueue() # 创建一个 LIFO 队列 for index in range(10): q.put(index) while not q.empty(): print(q.get(), end=", ") ## 输出结果如下 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
-
优先级队列
优先级队列中的任务顺序跟放入时的顺序是无关的,而是按照任务的大小来排序,最小值先被取出。那任务比较大小的规则是如何呢。
如果是内置类型,比如数值或者字符串,则按照自然顺序来比较排序
import queue q = queue.PriorityQueue() for index in range(10,0,-1): q.put(index) while not q.empty(): print(q.get(), end=", ") ## 输出结果如下 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
-
如果是列表或者元组,则先比较第一个元素,然后比较第二个,以此内推,直到比较出结果
import queue q = queue.PriorityQueue() q.put(["d","b"]) q.put(["c","b"]) while not q.empty(): print(q.get(), end=", ") ## 输出结果如下 ['c', 'b'], ['d', 'b'],
树
树在计算机科学的许多领域中使用,包括操作系统,图形,数据库系统和计算机网络。树数据结构与他们的植物表亲有许多共同之处。树数据结构具有根,分支和叶。
词汇和定义
节点:节点是树的基本部分。它可以有一个名称,我们称之为“键”。节点也可以有附加信息。我们将这个附加信息称为“有效载荷”。虽然有效载荷信息不是许多树算法的核心,但是在利用树的应用中通常是关键的。
边:边是树的另一个基本部分。边连接两个节点以显示它们之间存在关系。每个节点(除根之外)都恰好从另一个节点的传入连接。每个节点可以具有多个输出边
根:树的根是树中唯一没有传入边的节点
路径:路径是由边连接节点的有序列表。
子节点:具有来着相同传入边的节点C的集合称为该节点的子节点。
父节点:具有和它相同传入边的所连接的节点称为父节点
兄弟:树中作为同一父节点的子节点的节点被称为兄弟节点。
子树:子树是由父节点和该父节点的所有后代组成的一组节点和边
叶节点:叶节点是没有子节点的节点。
层数:节点n的层数为从根节点到该节点所经过的分支数目
高度:树的高度等于树中任何节点的最大层数
现在已经定义了基本词汇,我们可以继续对树的正式定义。 事实上,我们将提供一个树的两个定义。 一个定义涉及节点和边。 第二个定义,将被证明是非常有用的,是一个递归定义。
定义一:树由一组节点和一组连接节点的边组成。树具有以下属性:
- 树的一个节点被指定为根节点。
- 除了根节点之外,每个节点 n 通过一个其他节点 p 的边连接,其中 p 是 n 的父节点。
- 从根路径遍历到每个节点路径唯一。
- 如果树中的每个节点最多有两个子节点,我们说该树是一个二叉树。
列表表示树
myTree = ['a', #root
['b', #left subtree
['d', [], []],
['e', [], []] ],
['c', #right subtree
['f', [], []],
[] ]
]
注意,我们可以使用标准列表索引来访问列表的子树。树的根是 myTree[0]
,根的左子树是 myTree[1]
,右子树是 myTree[2]
。 ActiveCode 1 说明了使用列表创建一个简单的树。一旦树被构建,我们可以访问根和左右子树。 该列表方法的一个非常好的属性是表示子树的列表的结构遵守树定义的结构; 结构本身是递归的!具有根值和两个空列表的子树是叶节点。列表方法的另一个很好的特性是它可以推广到一个有许多子树的树。在树超过二叉树的情况下,另一个子树只是另一个列表。
myTree = ['a', ['b', ['d',[],[]], ['e',[],[]] ], ['c', ['f',[],[]], []] ]
print(myTree)
print('left subtree = ', myTree[1])
print('root = ', myTree[0])
print('right subtree = ', myTree[2])
列表表示
class Tree:
def __init__(self,root=['root', [], []]):
self.root = root
def __repr__(self):
return str(self.root)
def insertLeft(self, newBranch):
t = self.root.pop(1)
if len(t) > 1:
self.root.insert(1, [newBranch, [], t])
else:
self.root.insert(1, [newBranch, [], []])
return self.root
def insertRight(self,newBranch):
t = self.root.pop(2)
if len(t) > 1:
self.root.insert(2,[newBranch,[],t])
else:
self.root.insert(2,[newBranch,[],[]])
return self.root
if __name__ == '__main__':
tree = Tree()
newBranch = 1
tree.insertLeft(newBranch)
print(tree)
tree.insertLeft(2)
print(tree)
节点表示:
使用节点和引用方法的类定义
class BinaryTree:
def __init__(self,root):
self.key = root
self.left = None
self.right = None
def insertLeft(self,newNode):
if self.left == None:
self.left = BinaryTree(newNode)
else:
t = BinaryTree(newNode)
t.left = self.left
self.left = t
def insertRight(self,newNode):
if self.right == None:
self.right = BinaryTree(newNode)
else:
t = BinaryTree(newNode)
t.right = self.right
self.right = t
def getRight(self):
return self.right
def getLeft(self):
return self.left
def setRoot(self,obj):
self.key = obj
def getRootVal(self):
return self.key
if __name__ == '__main__':
r = BinaryTree('a')
print(r.getRootVal())
print(r.getLeft())
r.insertLeft('b')
print(r.getLeft())
print(r.getLeft().getRootVal())
r.insertRight('c')
print(r.getRight())
print(r.getRight().getRootVal())
r.getRight().setRoot('hello')
print(r.getRight().getRootVal())
树的遍历
我们访问树节点的三种方式,有三种常用的模式来访问树中的所有节点。这些模式之间的差异是每个节点被访问的顺序。我们称这种访问节点的方式为’遍历‘。我们将看到三种遍历方式称为前序,中序和后序。
前序
在前续遍历中,我们首先访问根节点,然后递归地做左侧子树的前序遍历,随后是右侧子树的递归前序遍历。
假设你想从前到后读这本书。前序遍历给你正确的顺序。从树的根(Book节点)开始,我们将遵循前序遍历指令。我们递归调用左孩子的 preorder
,在这种情况下是 Chapter1
。我们再次递归调用左孩子的 preorder
来得到 Section 1.1
。由于 Section 1.1
没有子节点,我们不再进行任何额外的递归调用。当我们完成 Section 1.1
,我们将树向上移动到Chapter1
。此时,我们仍然需要访问 Chapter1
的右子树 Section 1.2
。和前面一样,我们访问左子树,它将我们带到 Section 1.2.1
,然后访问 Section 1.2.2
。在 Section 1.2
完成后,我们返回到 Chapter1
。然后,我们返回到 Book
节点,并按照相同过程遍历 Chapter2
。
def preorder(tree):
if tree:
print(tree.getRootVal())
preorder(tree.getLeftChild())
preorder(tree.getRightChild())
外部函数编写的前序遍历的版本
def preorder(tree):
if tree:
print(tree.getRootVal())
preorder(tree.getLeftChild())
preorder(tree.getRightChild())
内部方法实现
def preorder(self):
print(self.key)
if self.leftChild:
self.leftChild.preorder()
if self.rightChild:
self.rightChild.preorder()
后续遍历
def postorder(tree):
if tree != None:
postorder(tree.getLeftChild())
postorder(tree.getRightChild())
print(tree.getRootVal())
中序遍历
def inorder(tree):
if tree != None:
inorder(tree.getLeftChild())
print(tree.getRootVal())
inorder(tree.getRightChild())