Python數據結構與算法分析(筆記與部分作業)


最近為了給寫搬磚腳本增加一些算法知識,腦殘的看起來算法書。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個題目,瘋了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM