Python数据结构与算法分析(笔记与部分作业)

与世无争的帅哥 提交于 2020-03-16 03:40:50

最近为了给写搬砖脚本增加一些算法知识,脑残的看起来算法书。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个题目,疯了。

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!