线性结构-栈

栈(Stack)它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
ADT定义: stack()/ push(data)/ pop()/ peek()/ isEmpty()/ size()

class Stack():
    #定义栈类型 先进后出 last-in first-out(LIFO)
    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)

栈的应用举例

1) 进制转换

在十进制转常见的二进制、八进制、十六进制的过程中,需要重复对余数入栈,直至商为0,最后再将栈内的数据进行出栈操作,输出的字符串就是转换后的结果。
十进制转二进制函数如下:

def divideBy2(decNumber):
    remstack = Stack()
    while decNumber > 0:
        rem = decNumber % 2
        remstack.push(rem)
        decNumber = decNumber // 2

    binString = ""
    while not remstack.isEmpty():
        binString = binString + str(remstack.pop())

    return binString

print(divideBy2(15))

稍通用的一个十进制转换的函数如下:

def baseConverter(decNumber, base):
    digits = "0123456789ABCDEF"
    remstack = Stack()
    while decNumber > 0:
        rem = decNumber % base
        remstack.push(rem)
        decNumber = decNumber // base

    newString = ""
    while not remstack.isEmpty():
        newString = newString + str(digits[remstack.pop()])

    return newString

print(baseConverter(255, 2))
print(baseConverter(255, 16))

2) 表达式转换

表达式分为中缀表达式、前缀表达式、后缀表达式。比如中缀表达A+BC, 换成前缀表达式就是+ABC, 换成后缀表达式是ABC+ ;再比如中缀表达式(A+B)(C+D), 换成前缀表达式为 +AB+CD, 换成后缀表达式为 AB+CD+ ;另外中缀表达A+BC 转换成全括号表达式为 (A+(BC))。
稍复杂表达式的举例: (A+B)C-(D-E)(F+G) ==> -+ABC-DE+FG (前缀) ==> AB+CDE-FG+- (后缀) 。
编写中缀表达式转后缀表达式函数,扫描操作过程如下:

  • 如果单词是操作数,直接添加到后缀表达式列表的未尾;
  • 如果单词是"(",则入栈操作;
  • 如果单词是")",则反复出栈,并追回到后缀表达式中,直到遇到"("为止;
  • 如果遇到操作符,则入栈操作(入栈时如果栈不为空,且栈顶操作符优先级大于当前操作符优先级,需要先将栈顶弹出并添加到后缀表达式中,然后再将当前操作符入栈);
    代码如下:
def matches(open, close):
    opens = "([{"
    closers = ")]}"
    return opens.index(open) == closers.index(close)

def convert(str):
    opdata = "ABCDEFGIHKLMNOPQRSTUVWXYZ"
    opsigns = {}
    opsigns["*"] = 3
    opsigns["/"] = 3
    opsigns["+"] = 2
    opsigns["-"] = 2
    opsigns["("] = 1
    orgstr = str.split()
    postfixList = []
    opstack = Stack()

    for token in orgstr:
        if token in opdata:
            postfixList.append(token)
        if token == "(":
            opstack.push(token)
        if token == ")":
            top = opstack.pop()
            while top != "(":
                postfixList.append(top)
                top = opstack.pop()
        if token in "+-*/":
            while not opstack.isEmpty() and opsigns[opstack.peek()] >= opsigns[token]:
                postfixList.append(opstack.pop())
            opstack.push(token)

    while not opstack.isEmpty():
        postfixList.append(opstack.pop())
    return(" ".join(postfixList))

print(convert("A + ( B - C ) / D"))

后缀表达式的求值 (求值时需要将扫描到的操作数入栈)
编写后缀表达式求值函数,操作过程如下:

  • 创建operandStack用于暂存操作数
  • 将后缀表达式拆分成单词列表
  • 从左向右扫描单词列表. 如果扫描到操作数,将其转换int,并入栈.
  • 如果扫描到一个操作符,连续弹出两个操作数,并开始求值,最后将值再次入栈.
  • 扫描结束时,栈顶的值就是表达式的值
    代码如下:
def doMath(opsigns, opdata1, opdata2):
    if opsigns == "*":
        return opdata1 * opdata2
    elif opsigns == "/":
        return opdata1 / opdata2
    elif opsigns == "+":
        return opdata1 + opdata2
    else:
        return opdata1 - opdata2

def postfixEval(postfixExpr):
    operandStack = Stack()
    tokenList = postfixExpr.split()
    for token in tokenList:
        if token in '0123456789':
            operandStack.push(int(token))
        else:
            opdata2 = operandStack.pop()
            opdata1 = operandStack.pop()
            result = doMath(token, opdata1, opdata2)
            operandStack.push(result)
    return operandStack.pop()

print(postfixEval("5 7 3 - 2 / +"))

线性数据结构-队列(Queue)

队列(Queue)是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队首。
常见的有: 打印队列、操作系统的任务进程队列、键盘输入队列等。
ADT定义: Queue()/ enqueue(item)/ dequeue()/ isEmpty()/ size()

class Queue():
    #定义队列类,先进先出First-in First-out(FIFO)
    def __init__(self):
        self.items = []

    def isEmpty(self):
        return self.items == []

    def enqueue(self, item):
        self.items.insert(0, item)

    def size(self):
        return len(self.items)

    def dequeue(self):
        return self.items.pop()

队列的应用举例

1) 约瑟夫(热土豆)问题;类似问题一般通过队列模拟的方式显示结果。

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()

print(hotPotato(["Eric", "Hellen", "Robin", "Tom", "Elision", "Jack"], 7))

2) 打印任务

一个实验室,在做生意的一个小时内,大约有10名学生在场,这一小时中,每人会发起2次左右的打印每次1~20页。打印机支持两种模式:以草稿模式 每分钟10页;以正常模式 每分钟5页。问题:如何设定打印机的模式,让大家都不会等太久的前提下尽量提高打印质量?
此问题需要一段程序来模拟这种打印任务场景,然后对程序运行的结果进行分析,以支持对打印机模式设定的决策。
问题建模:
首先对问题进行抽象,确定相关的对象和过程
对象:打印任务、打印队列、打印机。

  • 打印任务的属性:提交时间 、打印页数;
  • 打印队列的属性: 具有FIFO性质的队列;
  • 打印机的属性: 打印速度、是否忙;
    过程:生成和提交打印任务。
  • 确定生成概率:实例为每小时会有10个学生提交的20个作业,这样,概率是每180秒会有1个作业生成 并提交,概率为每秒 1/180. <=== 20作业/3600秒
  • 确定打印页数:实例是1~20页,那么就是1~20页之间概率相同。
  • 实施打印:
    a) 当前的打印作业:正在打印的作业 ;
    b) 打印结束倒计时:新作业开始打印时,开始倒计时,回0表示打印完毕,可以处理下一个作业;

模拟时间
统一的时间框架:以秒均匀流逝的时间,设定结束时间
同步所有过程:在一个时间单位内,对生成打印任务和实施打印两个过程各处理一次。

模拟流程

  • 创建打印队列对象;
  • 时间按照秒的单位流逝:
    a) 按照概率生成打印作业,加入打印队列;
    b) 如果打印机空闲,且队列不空,则取出队首作业打印,记录此作业等待时间 ;
    c) 如果打印机忙,则按照打印速度进行秒打印;
    d) 如果当前作业打印完成,则打印机进入空闲;
  • 时间用尽,开始统计平均等待时间 ;
    代码如下:
class Printer:

    def __init__(self,ppm):
        self.pagerate = ppm
        self.currentTask = None
        self.timeRemaining = 0  #任务倒计时

    def tick(self):
        if self.currentTask != None :
            self.timeRemaining = 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

import random
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)   # 以1/180的机率 生成新的打印任务
    if num == 180:
        return True
    else:
        return False

def simulation(numSeconds,pagesPerMinute):

    labprinter = Printer(pagesPerMinute)
    printQueque = Queue()
    waitingtimes = []

    for currentSecond in range(numSeconds):
        if newPrintTask():
            task = Task(currentSecond)
            printQueque.enqueue(task)

        if (not labprinter.busy()) and (not printQueque.isEmpty()):
            nexttask = printQueque.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,printQueque.size()))

for i in range(10):
    simulation(3600,10)

双端队列(Deque)

双端队列队首、队尾都可以添加或删除数据。
ADT定义 Deque()/ addFront(item)/ addRear(item)/ removeFront()/ removeRear/ isEmpty()/ size()
代码如下:

class Deque:
    # 本例list[0]为尾端, list[-1]为首端

    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.index(0, item)

    def removeFront(self):
        return self.items.pop()

    def removeRear(self):
        return self.items.pop(0)

    def size(self):
        return len(self.items)

双端队列应用举例 - 回文词的判断

回文词的判断: 两端同时出队并判断是否相同,最后队列长度为1或0
代码如下:

def palchecher(aString):
    chardeque = Deque()
    for char in aString:
        chardeque.addFront(char)

    stillEqual = True
    while chardeque.size() > 1 and stillEqual:
        if chardeque.removeFront() != chardeque.removeRear():
            stillEqual = False

    return stillEqual

print(palchecher("abcdcba"))
print(palchecher("abcdcbe"))

线性数据结构-链表

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

1) 无序列表(unordered list)

#ADT定义 List()/ add(item)/ remove(item)/ search(item)/ isEmpty()/ size()/ append(item)/ index(item)/ pop()/ pop(pos)
使用链表实现无序表, 代码如下:

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 UnorderedList:
    def __init__(self):
        self.head = None

    def add(self, item):
        #本例的add方法将数据插入到链表第一项
        newNode = Node(item)
        newNode.setNext(self.head)
        self.head = newNode

    def size(self):
        current = self.head
        count = 0
        while current:
            count += 1
            current = current.getNext()
        return count

    def search(self, item):
        current = self.head
        found = False
        while current and not found:
            if current.data == item:
                found = True
            else:
                current = current.getNext()
        return found

    def remove(self, item):
        current = self.head
        previous = Node
        found = False
        while not found:
            if current.getData() == item:
                found = True
            else:
                previous = current
                current = current.getNext()
        if previous == None:
            self.head = current.getNext()
        else:
            previous.setNext(current.getNext())

    def traverse(self):
        current = self.head
        while current:
            print(current.getData(), end= " ")
            current = current.getNext()

unorderedseq = UnorderedList()
unorderedseq.add(10)
unorderedseq.add(20)
unorderedseq.add(30)
unorderedseq.remove(20)
unorderedseq.traverse()
print(unorderedseq.search(10))
print(unorderedseq.size())

2) 有序列表(orderedList)

ADT定义 OrderedList() / add(item)/ remove(item)/ search(item)/ size()/ index(item)/ pop()/ pop(pos)
使用链表实现有序列表,代码如下:

class OrderedList:
    def __init__(self):
        self.head = None

    def add(self, item):
        current = self.head
        previous = None
        stop = False
        while current and not stop:
            if item <= current.data:
                stop = True
            else:
                previous = current
                current = current.getNext()
        newNode = Node(item)
        if previous == None:
            newNode.setNext(self.head)
            self.head = newNode
        else:
            newNode.setNext(current)
            previous.setNext(newNode)

    def remove(self, item):
        current = self.head
        previous = None
        found = False
        while current and not found:
            if current.data == item:
                found = True
            else:
                previous = current
                current = current.getNext()
        if previous == None:
            self.head = self.head.getNext()
        else:
            previous.setNext(current.getNext)

    def search(self, item):
        current = self.head
        found = False
        while current and not found:
            if current.data == item:
                found = True
            elif current.data > item :
                break
            else:
                current = current.getNext()
        return found

    def traverse(self):
        current = self.head
        while current:
            print(current.data, end=" ")
            current = current.getNext()

orderedseq = OrderedList()
orderedseq.add(50)
orderedseq.add(30)
orderedseq.add(35)
orderedseq.remove(30)
orderedseq.traverse()
print(orderedseq.search(45))

链表的另一种代码风格

代码如下:

class LinkedNode:
    # 定义单链表中的节点
    def __init__(self, data):
        self.data = data
        self.next = None

class LinkedList:
    #  定义链表类
    def __init__(self):
        self._head = None

    def isEmpty(self):
        return self._head == None

    def addheader(self, data):
        newNode = LinkedNode(data)
        newNode.next = self._head
        self._head = newNode

    def append(self, data):
        currentNode = self._head
        newNode = LinkedNode(data)
        if self._head == None:
            self._head = newNode
        else:
            while currentNode.next != None:
                currentNode = currentNode.next
            currentNode.next = newNode

    def tailpos(self):
        currentNode = self._head
        while currentNode.next :
            currentNode = currentNode.next
        return currentNode

    def pop(self):
        currentNode = self._head
        previousNode = None
        while currentNode.next:
            previousNode = currentNode
            currentNode = currentNode.next
        previousNode.next = None
        return currentNode.data

    def size(self):
        currentNode = self._head
        count = 0
        while currentNode:
            count += 1
            currentNode = currentNode.next
        return count

    def searchpos(self, pos):
        currentNode = self._head
        count = 1
        if pos < 1:
            return None
        else:
            while currentNode and count != pos:
                currentNode = currentNode.next
                count += 1
        return currentNode

    def search(self, data):
        currentNode = self._head
        while currentNode.data != data and currentNode:
            currentNode = currentNode.next
        if currentNode.data == data:
            return currentNode
        elif currentNode == None:
            return None

    def traverse(self):
        currentNode = self._head
        while currentNode:
            print(currentNode.data, end=' ')
            currentNode = currentNode.next
        print()

应用举例: 将两个有序链表合并且重新排序,最后输出排序后的结果

方法1,比较出两个原链表中较小的数据,将较小的数据再生成新node,将新node追加到新链表中(牺牲了空间,保留了原链表的结构)。
代码如下:

def orderMerge(lista,listb,listc):

    currentNodea = lista._head
    currentNodeb = listb._head
    currentNodec = listc._head
    while currentNodea and currentNodeb:
        if currentNodea.data < currentNodeb.data:
            newNode = LinkedNode(currentNodea.data)
            currentNodea = currentNodea.next  #链表A的当前节点向后移动一个
        else:
            newNode = LinkedNode(currentNodeb.data)
            currentNodeb = currentNodeb.next  #链表B的当前节点向后移动一个
        if listc._head == None:
            listc._head = newNode
        else:
            currentNodec.next = newNode  # 首先让链表C当前节点的next指向newNode
        currentNodec = newNode  # 然后再将链表C当前节点对象点至newNode

    if currentNodea:
        currentNodec.next = currentNodea
    else:
        currentNodec.next = currentNodeb

方法2,采摘结点法:将较小对象链接到链表C当前节点的next,再将链表C当前节点跳至最小对象,最后最小对象(a或b)当前结点的指针向后移动一位,节省了空间,同时也改变了原链表的结构。
代码如下:

def orderMerge2(lista,listb,listc):

    currentNodea = lista._head
    currentNodeb = listb._head
    currentNodec = listc._head

    while currentNodea and currentNodeb:
        if currentNodea.data < currentNodeb.data:
            if listc._head == None:
                listc._head = currentNodea
            else:
                currentNodec.next = currentNodea
            currentNodec = currentNodea
            currentNodea = currentNodea.next
        else:
            if listc._head == None:
                listc._head = currentNodeb
            else:
                currentNodec.next = currentNodeb
            currentNodec = currentNodeb
            currentNodeb = currentNodeb.next

    if currentNodea:
        currentNodec.next = currentNodea
    else:
        currentNodec.next = currentNodeb

测试如下:

linkedA = LinkedList()
linkedB = LinkedList()
source1 = [1, 3, 5]
for i in source1:
    linkedA.append(i)

source2 = [2, 4, 6, 8, 10, 11]
for i in source2:
    linkedB.append(i)

linkedC = LinkedList()
orderMerge(linkedA, linkedB, linkedC)

linkedC.traverse()
linkedA.traverse()
linkedB.traverse()

linkedB.pop()
print(linkedB.tailpos().data)
print(linkedB.size())

注: 纯属个人笔记,不喜勿喷。