Python数据结构与算法分析(笔记与部分作业)
最近为了给写搬砖脚本增加一些算法知识,脑残的看起来算法书。Python数据结构与算法分析,本人英语比较差,看的是翻译版本的。
网上有免费的原版的:https://runestone.academy/runestone/books/published/pythonds/index.html
不废话,开笔记,第一章Python基础,最后的反向思路就稍微卡住了我一下。
第1章,导论
计算机科学的研究对象是问题、解决问题的过程,以及通过该过程得到的解决方案。算法就是解决方案。
计算机科学可以定义为:研究问题及其解决方案,以及研究目前无解的问题的科学。
编程是指通过编程语言将算法编码以使其能被计算机执行的过程。如果没有算法,就不会有程序。
Python支持面向对象编程范式。这意味着Python认为数据是问题解决过程中的关键点。在Python以及其他所有面向对象编程语言中,类都是对数据的构成(状态)以及
数据能做什么(行为)的描述。由于类的使用者只能看到数据项的状态和行为,因此类与抽象数据类型相似的。
在面向对象编程范式中,数据项被称为对象。一个对象就是类的一个实例。
上两个书中的完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | def gcd(m,n): while m % n ! = 0 : oldm = m oldn = n m = oldn n = oldm % oldn return n class Fraction: def __init__( self ,top,bottom): self .num = top self .den = bottom def __str__( self ): return str ( self .num) + "/" + str ( self .den) def show( self ): print ( self .num, "/" , self .den) def __add__( self ,otherfraction): newnum = self .num * otherfraction.den + \ self .den * otherfraction.num newden = self .den * otherfraction.den common = gcd(newnum,newden) return Fraction(newnum / / common,newden / / common) def __eq__( self , other): firstnum = self .num * other.den secondnum = other.num * self .den return firstnum = = secondnum x = Fraction( 1 , 2 ) y = Fraction( 2 , 3 ) print (x + y) print (x = = y) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | class LogicGate: def __init__( self ,n): self .name = n self .output = None def getLabel( self ): return self .name def getOutput( self ): self .output = self .performGateLogic() return self .output class BinaryGate(LogicGate): def __init__( self ,n): super (BinaryGate, self ).__init__(n) self .pinA = None self .pinB = None def getPinA( self ): if self .pinA = = None : return int ( input ( "Enter Pin A input for gate " + self .getLabel() + "-->" )) else : return self .pinA.getFrom().getOutput() def getPinB( self ): if self .pinB = = None : return int ( input ( "Enter Pin B input for gate " + self .getLabel() + "-->" )) else : return self .pinB.getFrom().getOutput() def setNextPin( self ,source): if self .pinA = = None : self .pinA = source else : if self .pinB = = None : self .pinB = source else : print ( "Cannot Connect: NO EMPTY PINS on this gate" ) class AndGate(BinaryGate): def __init__( self ,n): BinaryGate.__init__( self ,n) def performGateLogic( self ): a = self .getPinA() b = self .getPinB() if a = = 1 and b = = 1 : return 1 else : return 0 class OrGate(BinaryGate): def __init__( self ,n): BinaryGate.__init__( self ,n) def performGateLogic( self ): a = self .getPinA() b = self .getPinB() if a = = 1 or b = = 1 : return 1 else : return 0 class UnaryGate(LogicGate): def __init__( self ,n): LogicGate.__init__( self ,n) self .pin = None def getPin( self ): if self .pin = = None : return int ( input ( "Enter Pin input for gate " + self .getLabel() + "-->" )) else : return self .pin.getFrom().getOutput() def setNextPin( self ,source): if self .pin = = None : self .pin = source else : print ( "Cannot Connect: NO EMPTY PINS on this gate" ) class NotGate(UnaryGate): def __init__( self ,n): UnaryGate.__init__( self ,n) def performGateLogic( self ): if self .getPin(): return 0 else : return 1 class Connector: def __init__( self , fgate, tgate): self .fromgate = fgate self .togate = tgate # 这里是关键,将整个连接器作为后面端口的输入。每个与非门都定义了该方法。 tgate.setNextPin( self ) def getFrom( self ): return self .fromgate def getTo( self ): return self .togate def main(): g1 = AndGate( "G1" ) g2 = AndGate( "G2" ) g3 = OrGate( "G3" ) g4 = NotGate( "G4" ) c1 = Connector(g1,g3) c2 = Connector(g2,g3) c3 = Connector(g3,g4) print (g4.getOutput()) main() |
第一个相对比较好理解,第二个理解也还好,书中由于篇幅限制,没有写全。
但自己写真心写不出来,这个一种反向思考的思路,只能看懂。
小结:
计算机科学是研究如何解决问题的学科。
计算机科学利用抽象这一工具来表示过程和数据。
抽象数据类型通过隐藏数据的细节来使程序员能够管理问题的复杂度。
Python是一门强大、易用的面向对象编程的语言。
列表、元祖以及字符串使Python的内建有序集合。
字典和集是无序集合。
类使得程序员能够实现抽象数据类型。
程序员既可以重写标准方法,也可以构建新的方法。
类可以通过继承层次结构来组织。
类的构建方法总是先调用其父类的构建方法,然后才处理自己的数据和行为。
编程练习:
1、实现简单的方法getNum和getDen,它们分别返回分数的分子与分母
2、修改Fraction类的构造方法,传入就约分。
3、实现简单算数运算__sub__,__mul__,__truediv__
4、实现__gt__,__ge__
5、修改Fraction类的构造方法,使其检查并确保分子与分母均为整数,不是就报错。
后面还有10题,后续补上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | import numbers def gcd(m, n): while m % n ! = 0 : oldm = m oldn = n m = oldn n = oldm % oldn return n class Fraction: def __init__( self , top, bottom): if all ([ isinstance (x, numbers.Integral) for x in (top, bottom)]): common = gcd(top, bottom) self .num = top / / common self .den = bottom / / common else : raise TypeError( '分子分母必须为整数RR' ) def __str__( self ): return str ( self .num) + "/" + str ( self .den) def show( self ): print ( self .num, "/" , self .den) def __add__( self , otherfraction): newnum = self .num * otherfraction.den + \ self .den * otherfraction.num newden = self .den * otherfraction.den # common = gcd(newnum, newden) # return Fraction(newnum // common, newden // common) # 返回的值可以自己先约分了。 return Fraction(newnum, newden) # 减法 def __sub__( self , otherfraction): newnum = self .num * otherfraction.den - \ self .den * otherfraction.num newden = self .den * otherfraction.den return Fraction(newnum, newden) # 乘法 def __mul__( self , otherfraction): newnum = self .num * otherfraction.num newden = self .den * otherfraction.den return Fraction(newnum, newden) # 除法 def __truediv__( self , otherfraction): newnum = self .num * otherfraction.den newden = self .den * otherfraction.num return Fraction(newnum, newden) # 返回分子 def getNum( self ): return self .num # 返回分母 def getDen( self ): return self .den def __eq__( self , other): firstnum = self .num * other.den secondnum = other.num * self .den return firstnum = = secondnum # 原则定义了__eq__可以不定义__ne__ def __ne__( self , other): firstnum = self .num * other.den secondnum = other.num * self .den return firstnum ! = secondnum # 大于 def __gt__( self , other): firstnum = self .num * other.den secondnum = other.num * self .den return firstnum > secondnum # 大于等于 def __ge__( self , other): firstnum = self .num * other.den secondnum = other.num * self .den return firstnum > = secondnum if __name__ = = '__main__' : x = Fraction( 2 , 8 ) y = Fraction( 1 , 3 ) print (y.getNum()) print (y.getDen()) print (x + y) print (x - y) print (x * y) print (x / y) print (x = = y) print (x ! = y) print (x > = y) print (x < = y) print (x < y) |
第2章 算法分析
算法分析关系的是基于所使用的计算资源比较算法。
计算资源,一个是算法在解决问题时要占用的空间和内存,还有一个是算法执行所需的时间进行分析和比较。
数量级(order of magnitude) 被称为大O记数法,记作O(f(n))。它提供了步骤数的一个有用的近似方法。
常见的大O函数
f(n) 名称
1 常数
logn 对数
n 线性
nlogn 线性对数
n2 平方
n3 立方
2的n次 指数
异序词检查示例
书中用了4个方案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | def anagramSolution1(s1, s2): '''清点法,时间复杂度为O(n2)''' # 将第二个字符串转换为列表,初始化数据,严谨一点应该一开始判断字符串长度。 alist = list (s2) pos1 = 0 stillOK = True # 开始对比字符串s1的每个字符 while pos1 < len (s1) and stillOK: pos2 = 0 found = False # 对s2的列表进行逐一取字,取到了循环停止 while pos2 < len (alist) and not found: if s1[pos1] = = alist[pos2]: found = True else : pos2 + = 1 # 取到了把s2的那个相同的字换成None if found: alist[pos2] = None # 否则外层的循环停止,stillOK为False else : stillOK = False pos1 + = 1 return stillOK def anagramSolution2(s1, s2): '''排序法,复杂度为O(n2)或者O(nlongn)''' alist1 = list (s1) alist2 = list (s2) alist1.sort() alist2.sort() pos = 0 matches = True while pos < len (s1) and matches: if alist1[pos] = = alist2[pos]: pos + = 1 else : matches = False return matches ''' 蛮力法,我觉得复杂度为2的n次,对数O(n2) 书中没有写函数,我自己写一个吧. ''' def anagramSolution3(s1, s2): alist1 = tuple (s1) import itertools matches = False # 返回一个迭代器,取值为元祖 alist2 = itertools.permutations(s2, len (s2)) # 跟着书中写了while循环,真心用不惯while循环。 while not matches: try : if alist1 = = next (alist2): matches = True except StopIteration: break return matches def anagramSolution4(s1, s2): '''记数法 复杂度为O(n),但这个算法用空间换来了时间''' c1 = [ 0 ] * 26 c2 = [ 0 ] * 26 # 对列表类的每个字母进行累加 for i in range ( len (s1)): pos = ord (s1[i]) - ord ( 'a' ) c1[pos] + = 1 for i in range ( len (s1)): pos = ord (s2[i]) - ord ( 'a' ) c2[pos] + = 1 j = 0 stillOK = True # 对两个列表的29个元素各个元素进行逐一比对 while j < 26 and stillOK: if c1[j] = = c2[j]: j + = 1 else : stillOK = False return stillOK if __name__ = = '__main__' : print (anagramSolution4( 'abcde' , 'abcea' )) |
列表生成的函数测试,用了timeit模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | from timeit import Timer def test1(): l = [] for i in range ( 1000 ): l = l + [i] def test2(): l = [] for i in range ( 1000 ): l.append(i) def test3(): l = [i for i in range ( 1000 )] def test4(): l = list ( range ( 1000 )) if __name__ = = '__main__' :<br> # 生成测试对象 t1 = Timer( 'test1()' , "from __main__ import test1" )<br> # 进行测试 print ( "concat" , t1.timeit(number = 1000 ), "milliseconds" ) t2 = Timer( 'test2()' , "from __main__ import test2" ) print ( "append" , t2.timeit(number = 1000 ), "milliseconds" ) t3 = Timer( 'test3()' , "from __main__ import test3" ) print ( "comprehension" , t3.timeit(number = 1000 ), "milliseconds" ) t4 = Timer( 'test4()' , "from __main__ import test4" ) print ( "list range" , t4.timeit(number = 1000 ), "milliseconds" ) |
对列表进行索引查寻、索引赋值、追加(append())、弹出(pop())的大O效率都为O(1)
抄写书中代码对比pop(0)与pop()执行时间
1 2 3 4 5 6 7 8 9 10 11 | import timeit popzero = timeit.Timer( 'x1.pop(0)' , 'from __main__ import x1' ) popend = timeit.Timer( 'x2.pop()' , 'from __main__ import x2' ) x1 = list ( range ( 1000000 )) print (popzero.timeit(number = 1000 )) x2 = list ( range ( 1000000 )) print (popend.timeit(number = 1000 )) |
1 2 3 | / usr / local / bin / python3. 7 "/Users/shijianzhong/study/Problem Solving with Algorithms and Data Structures using Python/chapter_2/t2_10.py" 0.31737086999999997 6.246100000001364e - 05 |
对字典进行取值、赋值、删除、包含的大O效率都为O(1)
抄写书中比较列表与字典的包含操作
1 2 3 4 5 6 7 8 9 10 11 12 | import timeit import random for i in range ( 10000 , 1000001 , 20000 ): t = timeit.Timer( 'random.randrange(%d) in x' % i, 'from __main__ import random, x' ) x = list ( range (i)) lst_time = t.timeit(number = 1000 ) x = {j: None for j in range (i)} d_time = t.timeit(number = 1000 ) print ( "%d, %10.3f, %10.3f" % (i, lst_time, d_time)) |
小结:
算法分析是一种独立于实现的算法度量方法。
大O记法使得算法可以根据随问题规模增长而其主导作用的部分进行归类。
编程练习:
设计一个试验,证明列表的索引操作为常数阶。
1 2 3 4 5 6 7 8 9 10 | import timeit import random for i in range ( 10000 , 1000001 , 20000 ): index_test = timeit.Timer( 'index = random.randrange(%d); x[index]' % i, 'from __main__ import x, random' ) x = list ( range (i)) res = index_test.timeit() print ( '%d, %10.3f' % (i, res)) |
1 2 3 4 5 6 | 10000 , 0.951 30000 , 0.792 50000 , 0.930 70000 , 0.906 90000 , 0.894 110000 , 0.884 |
设计一个实验,证明字典的取值操作和赋值操作为常数阶
1 2 3 4 5 6 7 8 9 10 11 12 13 | import timeit import random for i in range ( 10000 , 1000001 , 20000 ): # 随机取key 赋值None dict_test = timeit.Timer( 'key = random.randrange(%d); x[key]=None' % i, 'from __main__ import x, random' ) # 创建一个字典,value为True x = {x: True for x in range (i)} res = dict_test.timeit() print ( '%d, %10.3f' % (i, res)) |
1 2 3 4 5 6 7 | 10000 , 0.934 30000 , 0.853 50000 , 0.851 70000 , 0.928 90000 , 0.883 110000 , 0.851 130000 , 0.838 |
设计一个实验,针对列表和字典比较del操作的性能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import timeit import random for i in range ( 10000 , 1000001 , 20000 ): # 只做了列表的del测试,字典的用timeit感觉不好做. l_test = timeit.Timer( 'del x1[0]' , 'from __main__ import x1' ) # d_test = timeit.Timer('del x2[0]', # 'x2 = {x: None for x in range(%d)}' % i) x1 = list ( range (i)) res0 = l_test.timeit(number = 1000 ) # x2 = {x: None for x in range(i)} # res1 = d_test.timeit(number=1) # x1 = {x: True for x in range(i)} # res1 = dict_test.timeit(number=50) # 随机取key 赋值None print ( '%d, %10.3f' % (i, res0)) |
水平有限,只做了del的列表测试,字典不好测试,因为重复测试需要在同一个字典重复删除key,如何制作不重复的key,让我很困难。
给定一个数字列表,其中的数字随机排列,编写一个线性阶算法,找出第k小的元素,并解释为何该算法的阶是线性的。
1 2 3 4 5 6 7 8 9 10 11 | def find_k_num(in_list, k): # 选择排序 for i in range ( len (in_list), 0 , - 1 ): for n in range (i - 1 ): if in_list[n] > in_list[n + 1 ]: in_list[n], in_list[n + 1 ] = in_list[n + 1 ], in_list[n] return in_list[k] if __name__ = = '__main__' : x = list ( reversed ( list ( range ( 10 )))) print (find_k_num(x, 3 )) |
针对前一个练习,能将算法的时间复杂度优化到O(nlogn)吗?
用快速排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | def run(in_list): # 退出基线 if len (in_list) < 2 : return in_list else : base_num = in_list[ 0 ] small_l = [i for i in in_list[ 1 : len (in_list)] if i < = base_num] large_l = [i for i in in_list[ 1 : len (in_list)] if i > base_num] # 进入递归 return run(small_l) + [base_num] + run(large_l) def fast_k_find(in_list, k): res = run(in_list) # print(res) return res[k] if __name__ = = '__main__' : x = list ( reversed ( list ( range ( 10 )))) # print(x) print (fast_k_find(x, 3 )) |
第三章 基本数据结构
栈、队列、双端队列、和列表都是有序的数据集合,其元素的顺序取决与添加顺序或移出顺序。一旦某个元素被添加进来,它与前后元素的相对位置保持不变。这样的数据集合经常被称为线性数据结构。
Python定义的列表使用append与pop方法能够很好的模拟栈的运行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class Stack: def __init__( self ): self .items = [] def isEmpty( self ): return self .items = = [] def push( self , item): self .items.append(item) def pop( self ): return self .items.pop() def peek( self ): return self .items[ len ( self .items) - 1 ] def size( self ): return len ( self .items) |
压栈与弹栈的时间复杂度都为O(1)
利用栈来配备括号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | from t3_1 import Stack def parChecker(symbolString): s = Stack() balanced = True index = 0 # 读取内部的每一个括号 while index < len (symbolString) and balanced: symbol = symbolString[index] # 左括号压入 if symbol = = '(' : s.push(symbol) else : # 在处理括号的时候,不应该出现栈内为空 if s.isEmpty(): balanced = False # 右括号弹出 else : s.pop() index + = 1 # 只有在处理完后的栈内为空,才能说明括号配对 if balanced and s.isEmpty: return True else : return False if __name__ = = '__main__' : print (parChecker( '((())))' )) |
普通情况:匹配符号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | from t3_1 import Stack def matches( open , close): opens = '([{' closers = ')]}' return opens.index( open ) = = closers.index(close) def parChecker(symbolString): s = Stack() balanced = True index = 0 # 读取内部的每一个括号 while index < len (symbolString) and balanced: symbol = symbolString[index] # ([{压入 if symbol in '([{' : s.push(symbol) else : # 在处理括号的时候,不应该出现栈内为空 if s.isEmpty(): balanced = False # 右括号弹出 else : # 对取到的符号与弹出的符号进行对比是否一对 if not matches(s.pop(), symbol): balanced = False index + = 1 # 只有在处理完后的栈内为空,才能说明括号配对 if balanced and s.isEmpty: return True else : return False if __name__ = = '__main__' : print (parChecker( '{([()])}' )) |
将十进制数转换成二进制数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | from t3_1 import Stack def divideBy2(decNumber): remstack = Stack() # 除2取余数,只要商大于0就可以 while decNumber > 0 : rem = decNumber % 2 remstack.push(rem) decNumber / / = 2 binString = '' # 弹栈取出各个数字 while not remstack.isEmpty(): binString + = str (remstack.pop()) return binString if __name__ = = '__main__' : print (divideBy2( 65 )) |
装换各种进制的算法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | from t3_1 import Stack def baseConverter(decNumber, base): remstack = Stack() # 考虑到16进制的数字,做了一串数字给16进制使用 digits = '0123456789ABCDEF' # 除2取余数,只要商大于0就可以 while decNumber > 0 : rem = decNumber % base remstack.push(rem) decNumber / / = base binString = '' # 弹栈取出各个数字 while not remstack.isEmpty(): binString + = digits[remstack.pop()] return binString if __name__ = = '__main__' : print (baseConverter( 165 , 16 )) |
书中通过栈的优势转化并计算中序到后序的表达式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | from t3_1 import Stack import string def infixToPostfix(infixexpr): prec = {} prec[ '*' ] = 3 prec[ '/' ] = 3 prec[ '+' ] = 2 prec[ '-' ] = 2 prec[ '(' ] = 1 opStack = Stack() postfixList = [] # 将等式切割 tokenList = infixexpr.split() # print(tokenList) for token in tokenList: # print(token) # 如果是大写字母的话 if token in string.ascii_uppercase: postfixList.append(token) # 出现左小括号,代表对应的一个中序表达式 elif token = = '(' : opStack.push(token) # 碰到右括号,说明这个中序表达式先转换成后序表达式 elif token = = ')' : topToken = opStack.pop() while topToken ! = '(' : postfixList.append(topToken) topToken = opStack.pop() else : # 如果栈里面的运算符优先级更高或相同,先从栈里面取出来,并将它们添加到列表的末尾 # 这个循环会把里面所有倒序的符号优先级都对比一遍。 while ( not opStack.isEmpty) and \ (prec[opStack.peek()] > = prec[token]): postfixList.append(opStack.pop()) opStack.push(token) # 将符号表中的所有符号取出 while not opStack.isEmpty(): postfixList.append(opStack.pop()) print (postfixList) return ' ' .join(postfixList) if __name__ = = '__main__' : print (infixToPostfix( 'A + B * C' )) |
用Python实现后续表达式的计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | from t3_1 import Stack def postfixEval(postfixExpr): operandStack = Stack() tokenList = postfixExpr.split() for token in tokenList: # 假如是数字就压入,数字范围设置不好,可以用isdecimal判断更佳 if token in '0123456789' : operandStack.push( int (token)) else : # 当碰到符号,取出最顶层的两个数字进行运算,并将结果压入 operator2 = operandStack.pop() operator1 = operandStack.pop() result = toMath(token, operator1, operator2) operandStack.push(result) # 最终的结果就是栈内的最后一个数字 return operandStack.pop() def toMath(op, op1, op2): if op = = '*' : return op1 * op2 elif op = = '/' : return op1 / op2 elif op = = '+' : return op1 + op2 else : return op1 - op2 if __name__ = = '__main__' : print (postfixEval( '1 2 + 3 *' )) |
书中为了解释栈的作用,lifo后进先出的用处,用了例子还是非常不错的。
队列
FIFO,先进先出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class Queue: def __init__( self ): self .items = [] def isEmpty( self ): return self .items = = [] # 插入队列 def enqueue( self , item): self .items.index( 0 , item) # 取出队列元素 def dequeue( self ): return self .items.pop() def size( self ): return len ( self .items) |
这个自定义的队列,取元素的时间复杂度为O(1),插元素为O(n)
用队列模拟传土豆。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | from t3_9 import Queue def hotPotato(namelist, num): simqueue = Queue() # 先将人压入队列 for name in namelist: simqueue.enqueue(name) # 主要队列里面人数大于两个 while simqueue.size() > 1 : # 开始玩游戏,将第一个出来的放入尾部 for i in range (num): simqueue.enqueue(simqueue.dequeue()) # 一圈下来拿到土豆的滚蛋 simqueue.dequeue() # 将最后那个人返回 return simqueue.dequeue() if __name__ = = '__main__' : print (hotPotato( list ( 'abcdefg' ), 10 )) |
书中的用队列实现的模拟打印机任务,书中看的很累,代码抄下来后,发现很号理解了,用for循环里面的每一个数字代表秒速,确实很好的idea。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | import random class Queue: def __init__( self ): self .items = [] def isEmpty( self ): return self .items = = [] # 插入队列 def enqueue( self , item): self .items.insert( 0 , item) # 取出队列元素 def dequeue( self ): return self .items.pop() def size( self ): return len ( self .items) class Printer: def __init__( self , ppm): self .pagerate = ppm self .currentTask = None self .timeRemaining = 0 def tick( self ): if self .currentTask ! = None : self .timeRemaining - = 1 if self .timeRemaining < = 0 : self .currentTask = None def busy( self ): if self .currentTask ! = None : return True else : return False def startNext( self , newtask): self .currentTask = newtask self .timeRemaining = newtask.getPages() \ * 60 / self .pagerate class Task: def __init__( self , time): self .timestamp = time self .pages = random.randrange( 1 , 21 ) def getStamp( self ): return self .timestamp def getPages( self ): return self .pages def waitTime( self , currenttime): return currenttime - self .timestamp def newPrintTask(): num = random.randrange( 1 , 181 ) if num = = 180 : return True else : return False def simulation(numSeconds, pagesPerMinute): labprinter = Printer(pagesPerMinute) printQueue = Queue() waitingtimes = [] # 通过for循环来模拟时间运行,每一个数字代表一秒 for currentSecond in range (numSeconds): # 通过随机函数,平均3分种产生一个任务 if newPrintTask(): task = Task(currentSecond) printQueue.enqueue(task) # 只要在打印机空,且有任务下,打印机开始运行 if ( not labprinter.busy()) and ( not printQueue.isEmpty()): nexttask = printQueue.dequeue() waitingtimes.append(nexttask.waitTime(currentSecond)) labprinter.startNext(nexttask) # 消耗的打印时间,帮助打印机完成任务清零 labprinter.tick() averageWait = sum (waitingtimes) / len (waitingtimes) print ( "Average Wait %6.2f secs %3d tasks remaining" % (averageWait, printQueue.size())) if __name__ = = '__main__' : for i in range ( 10 ): simulation( 3600 , 5 ) |
双端队列
用Python模拟一个双端队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | class Deque: def __init__( self ): self .items = [] def isEmpty( self ): return self .items = = [] # 头部放入数据 def addFront( self , item): self .items.append(item) # 尾部放入数据 def addRear( self , item): self .items.insert( 0 , item) # 头部取出数据 def removeFront( self ): self .items.pop() # 尾部取出数据 def removeRear( self ): self .items.pop( 0 ) def size( self ): return len ( self .items) |
用双端队列判断回文
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | from t3_14 import Deque def plachecker(aString): chardeque = Deque() for ch in aString: chardeque.addRear(ch) stillEqual = True while chardeque.size() > 1 and stillEqual: first = chardeque.removeFront() last = chardeque.removeRear() # 如果首尾不想等 if first ! = last: stillEqual = False return stillEqual if __name__ = = '__main__' : print (plachecker( '121' )) |
上面的双端队列还是比较好理解的,后面用Python模拟实现链表。
链表
为了实现无序列表,我们要构建链表。我个人的感觉有点像生成器。
节点(Node)是构建链表的基本数据结构。每一个节点对象都必须持有至少两份信息。
首先,节点必须包含列表元素,我们称之为节点的数据变量。其次,节点必须保存指向下一个节点的引用。
首先定义节点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class Node: def __init__( self , initdata): self .data = initdata self . next = None def getData( self ): return self .data def getNext( self ): return self . next def setData( self , newdata): self .data = newdata def setNext( self , newnext): self . next = newnext |
定义链表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | class Node: def __init__( self , initdata): self .data = initdata self . next = None def getData( self ): return self .data def getNext( self ): return self . next def setData( self , newdata): self .data = newdata def setNext( self , newnext): self . next = newnext # 定义空链表的类 # 这个类本身不包含任何节点对象,而只有指向整个链表结构中第一个节点的引用。 # 链表中,第一个节点很重要,后续的数据都要从第一个节点开始找 class Unordredlist: def __init__( self ): self .head = None # 判断链表是否为空,就看头部是不是None def isEmpty( self ): return self .head is None # 添加节点 def add( self , item): # 创建节点 temp = Node(item) # 设置该节点指向的下一个节点,实际为None temp.setNext( self .head) # 设置链表头部更新 self .head = temp def length( self ): current = self .head # 定义计数器 count = 0 # 只要头不是None,就循环读取,直到都到None尾巴为止 while current ! = None : count + = 1 current = current.getNext() return count def search( self , item): current = self .head found = False # 当没找到None的并且没有找到的情况下,一直找,找到返回True while current ! = None and not found: if current.getData() = = item: found = True else : current = current.getNext() return found # 删除节点,稍微复杂点 def remove( self , item): current = self .head previous = None found = False while not found: if current.getData() = = item: found = True else : previous = current current = current.getNext() # 第一个元素就是要被删除的情况下 if previous is None : self .head = current.getNext() # 删除前面的那个元素指向,被删除元素的指向的下一个元素 else : previous.setNext(current.getNext()) # 自己写append,在链表的最后添加一个元素,因为没有保存链表的最后元素,还是一样从头开始找 def append( self , item): current = self .head # 如果是空链表 if self .head is None : self .add(item) else : while True : if current.getNext() is None : # 设置假如节点的最后指向,其实是None temp = Node(item) temp.setNext(current.getNext()) current.setNext(temp) break else : current = current.getNext() # 格式化输出 def __repr__( self ): l = [] current = self .head if current is not None : while True : l.append(current.getData()) if current.getNext() is None : break current = current.getNext() return str (l) if __name__ = = '__main__' : u_list = Unordredlist() # u_list.add(1) u_list.add( 2 ) u_list.add( 3 ) print (u_list) print (u_list.length()) print (u_list.search( 3 )) u_list.remove( 3 ) print (u_list.search( 3 )) # u_list.append(4) print (u_list.search( 4 )) u_list.remove( 2 ) print (u_list) |
有序链表数据模型
需要重新定义链表类的一些方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | class Node: def __init__( self , initdata): self .data = initdata self . next = None def getData( self ): return self .data def getNext( self ): return self . next def setData( self , newdata): self .data = newdata def setNext( self , newnext): self . next = newnext class Unordredlist: def __init__( self ): self .head = None #草写书中查寻方法,书中的while写法,我真的很部习惯,我一般喜欢用while True,这种应该是C语言过来的人。 def search( self , item): current = self .head found = False stop = False while current ! = Node and not found and not stop: if current.getDate() = = item: found = True else : # 后面的数字不用查了 if current.getDate() > item: stop = True else : current = current.getNext() return found def add( self , item): current = self .head previous = None stop = False while current ! = None and not stop: # 数据大于添加数,需要停止了。 if current.getData() > item: stop = True else : previous = current current = current.getNext() temp = Node(item) # 空链表的情况下 if previous is None : temp.setNext( self .head) self .head = temp # 并给这个设置上节点与下节点 else : temp.setNext(current) previous.setNext(temp) |
Python列表是基于数组实现的。
小结:
线性数据结构以有序的方式来维护其数据。
栈是简单的数据结构,其排序原则是LIFO,既后进先出
栈的基本操作有push压,pop弹,isEmpty是否为空
队列是简单的数据结构,其排序原理是FIFO,既先进先出
队列的基本操作有enqueue入队,dequeue出队,和isEmpty
表达式有3种写法:前序、中序和后序
栈在计算和转换表达式的算法中十分有用
栈具有反转特性
队列有助于构建时序模拟。
模拟程序使用随机数生成器模拟实际情况,并且帮助我们回答"如果"问题。
双端队列是栈和队列的组合。
双端队列基本操作有的操作可以参考from collections import deque
列表是元素的集合,其中每个元素都有一个相对于其他元素的位置(这个不是Python的列表)
链表保证逻辑顺序,对实际的存储顺序没有要求。
修改链表头部是一种特殊情况。
课后编程作业:28个题目,疯了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现