最近為了給寫搬磚腳本增加一些算法知識,腦殘的看起來算法書。Python數據結構與算法分析,本人英語比較差,看的是翻譯版本的。
網上有免費的原版的:https://runestone.academy/runestone/books/published/pythonds/index.html
不廢話,開筆記,第一章Python基礎,最后的反向思路就稍微卡住了我一下。
第1章,導論
計算機科學的研究對象是問題、解決問題的過程,以及通過該過程得到的解決方案。算法就是解決方案。
計算機科學可以定義為:研究問題及其解決方案,以及研究目前無解的問題的科學。
編程是指通過編程語言將算法編碼以使其能被計算機執行的過程。如果沒有算法,就不會有程序。
Python支持面向對象編程范式。這意味着Python認為數據是問題解決過程中的關鍵點。在Python以及其他所有面向對象編程語言中,類都是對數據的構成(狀態)以及
數據能做什么(行為)的描述。由於類的使用者只能看到數據項的狀態和行為,因此類與抽象數據類型相似的。
在面向對象編程范式中,數據項被稱為對象。一個對象就是類的一個實例。
上兩個書中的完整代碼:
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)
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題,后續補上
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個方案。
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模塊
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__':
# 生成測試對象 t1 = Timer('test1()', "from __main__ import test1")
# 進行測試 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()執行時間
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))
/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)
抄寫書中比較列表與字典的包含操作
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記法使得算法可以根據隨問題規模增長而其主導作用的部分進行歸類。
編程練習:
設計一個試驗,證明列表的索引操作為常數階。
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))
10000, 0.951 30000, 0.792 50000, 0.930 70000, 0.906 90000, 0.894 110000, 0.884
設計一個實驗,證明字典的取值操作和賦值操作為常數階
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))
10000, 0.934 30000, 0.853 50000, 0.851 70000, 0.928 90000, 0.883 110000, 0.851 130000, 0.838
設計一個實驗,針對列表和字典比較del操作的性能
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小的元素,並解釋為何該算法的階是線性的。
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)嗎?
用快速排序
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方法能夠很好的模擬棧的運行。
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)
利用棧來配備括號
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('((())))'))
普通情況:匹配符號
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('{([()])}'))
將十進制數轉換成二進制數
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))
裝換各種進制的算法:
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))
書中通過棧的優勢轉化並計算中序到后序的表達式。
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實現后續表達式的計算
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,先進先出。
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)
用隊列模擬傳土豆。
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。
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模擬一個雙端隊列
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)
用雙端隊列判斷回文
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)是構建鏈表的基本數據結構。每一個節點對象都必須持有至少兩份信息。
首先,節點必須包含列表元素,我們稱之為節點的數據變量。其次,節點必須保存指向下一個節點的引用。
首先定義節點:
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 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)
有序鏈表數據模型
需要重新定義鏈表類的一些方法
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個題目,瘋了。