一、队列
1.什么是队列
| 队列是一种有次序的数据集合,其特征是新数据项的添加总发生在一端(通常称为“尾rear”端),而现存数据项的移除总发生在另一端(通常称为“首front”端) |
| 当数据项加入队列,首先出现在队尾,随着队受数据项的溢出,它逐渐接近队首。 |
| |
| 新加入的数据项必须在数据集末位等待,而等待时间最长的数据项则是队首,这种次序安排的原则称为(FIFO:First-in first-out)先进先出或“先到先服务first-come first-served” |
| |
| 队列仅有一个入口和一个出口,不允许数据项直接插入队中,也不允许从中间移除数据项 |
| |
| |
| 计算机科学中队列的例子: |
| |
| 一台打印机面向多个用户/程序提供服务,打印速度比打印请求提交的速度要慢得多,有任务正在打印时,后来的打印请求就要排成队列,以FIFO的形式等待被处理。 |
| |
| |
| 操作系统核心采用多个队列来队系统中同时运行的进程进行调度,进程数远多于CPU核心数 |
| 有些进程还要等待不同类型 I/O事件,调度原则综合了"先到先服务"及“资源充分利用”两个出发点 |
| |

| |
| 键盘敲击并不马上显示在屏幕上,需要有个队列性质的缓冲区,将尚未显示的敲击字符暂存其中,队列的先进先出性质则保证了字符的输入和显示次序一致性。 |
| |
2.抽象数据类型Queue
| Queue():创建一个空队列对象,返回值为Queue对象; |
| enqueue(item);将数据项item添加到队尾,无返回值; |
| dequeue():从队首移除数据项,返回值为队首数据项,队列被修改; |
| isEmpty():测试是否空队列,返回值为布尔值 |
| size:返回队列中数据项的个数。 |

3.python实现ADT Queue
| 采用List来容纳Queue的数据项: |
| 将List的首段作为队列尾端 |
| List的末端作为队列手段 |
| enqueue()复杂度为O(n) |
| dequeue()复杂度为O(1) |
| |
| 首尾倒过来的实现,复杂度也倒过来 |
| |
| |
| 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) |
4.举例 热土豆问题(约瑟夫问题)
| “击鼓传花”的土豆版本 |
| 传烫手的热土豆,鼓声停的时候,手里有土豆的小孩就要出列 |
| |
| 如果去掉鼓,改为传过固定人数,就成了“现代版”的约瑟夫问题 |
| 传说犹太人反叛罗马人,落到困境,约瑟夫和39人决定殉难,做成一圈儿,报数1~7,报到7的人由旁边杀死,结果约瑟夫给自己安排了个位置,最后活了下来…… |

| from pythonds.basic.queue 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() |
| |
| print(hotPotato(["Bill", "David", "Susan", "Jane", "Kent", "Brad"],7)) |
5.举例:打印队列
| 过程:生成和提交打印任务 |
| 确定生成概率:实例尾每小时会有10个学生提交的20个作业,这样,概率是每180秒会有1个作业生成并提交,概率为每秒1/180。 |
| 确定打印页数:实例是1~20页,那么就是1~20页之间概率相同。 |
| |
| |
| from pythonds.basic.queue import Queue |
| |
| import random |
| |
| class Printer: |
| def __init__(self, ppm): |
| self.page_rate = 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.get_pages()*60/self.page_rate |
| |
| |
| class Task: |
| def __init__(self, time): |
| self.timestamp = time |
| self.pages = random.randrange(1,21) |
| |
| def get_stamp(self): |
| return self.timestamp |
| |
| def get_pages(self): |
| return self.pages |
| |
| def waitTime(self,currenttime): |
| return currenttime-self.timestamp |
| |
| def new_print_task(): |
| num = random.randrange(1,181) |
| if num == 180: |
| return True |
| else: |
| return False |
| |
| def simulation(num_seconds, pages_per_minute): |
| labprinter = Printer(pages_per_minute) |
| print_queue = Queue() |
| waitingtimes = [] |
| |
| for current_second in range(num_seconds): |
| if new_print_task(): |
| task = Task(current_second) |
| print_queue.enqueue(task) |
| |
| if (not labprinter.busy()) and (not print_queue.isEmpty()): |
| next_task = print_queue.dequeue() |
| waitingtimes.append(next_task.waitTime(current_second)) |
| labprinter.startNext(next_task) |
| |
| labprinter.tick() |
| |
| average_wait = sum(waitingtimes)/len(waitingtimes) |
| print('Average Wai#t %6.2f secs %3d tasks remaining.'%(average_wait,print_queue.size())) |
| |
| |
| for i in range(10): |
| simulation(3600, 5) |
| simulation(3600, 10) |
| |
| |
| ''' |
| 为了队打印模式设置进行决策,我们用模拟程序来评估任务等待时间,通过两种情况模拟仿真结果的分析,我们认识到如果有那么多学生要拿着打印号的程序源代码赶去上课的话,那么,必须得牺牲打印质量,提高打印速度。 |
| 模拟系统对现实得仿真,在不耗费现实资源得情况下,有时候真实的实验是无法进行的,可以以不同得设定,反复多次模拟,来帮助我们进行决策。 |
| ''' |
二、双端队列
1.什么是双端队列?
| 双端队列Deque是一种有次序得数据集,跟队列相似,其两端可以称作“首”“尾”端,但deque中数据项既可以从队列首加入,也可以从队尾加入;数据项也可以从两端移除。 |
| 某种意义上说,双端队列集成了栈和队列得能力。 |

| 但双端队列并不具有内在得LIFO或者FIFO特性,如果用双端队列来模拟栈或队列,需要由使用者自行维护操作得一致性 |
2.抽象数据类型Deque
| Deque():创建一个空双端队列 |
| addFront(item):将item加入队首 |
| addRear(item):将item键入队尾 |
| removeFront():从队首移除数据项,返回值尾移除得数据项 |
| removeRear():从队尾移除数据项,返回值为移除得数据项 |
| isEmpty():返回deque是否为空 |
| size():返回deque中包含数据项得个数。 |

3.python ADT Deque
| 采用List实现:List下标0作为deque的尾端,List下表-1作为deque的首段 |
| 操作复杂度:addFront/removeFront O(1),addRear/removeRear O(n) |
| |
| 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): |
| return self.items.pop() |
| |
| def removeRear(self): |
| return self.items.pop(0) |
| |
| def size(self): |
| return len(self.items) |
4.举例:“回文词”判定
| 如radar、madam、toot |
| 中文“上海自来水来自海上”,“山东落花生花落东山”,“北京输油管油输京北” |
| |
| 用双端队列很容易解决“回文词”问题,先将需要判定的词从队尾加入deque,再从两端同时移除字符判定是否相同,直到deque中剩下0个或一个字符 |
| |
| |
| from pythonds.basic.deque import Deque |
| |
| def palchecker(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 |
| |
| print(palchecker('lsdkjfskf')) |
| print(palchecker('radar')) |
三、无序表抽象数据类型及python实现
1.列表List:什么是列表?
| 在前面基本数据结构的讨论中,我们采用python list来实现了多种现行数据结构。列表list是一种简单强大的数据集结构,提供了丰富的操作接口。但并不是所有的编程语言都提供了List数据类型,有时候需要程序员自己实现。 |

| 无序表List的操作如下: |
| List():创建一个空列表 |
| add(item):添加一个数据项到列表中,假设item原先不存在于列表中 |
| remove(item):从列表中移除item,列表被修改,item原先应存在于表中 |
| search(item):在列表中查找item,返回布尔值类型值 |
| |
| isEmpty():返回列表是否为空 |
| size():返回列表包含了多少数据项 |
| |
| append(item):添加一个数据项到表末尾,假设item原先不存在于列表中 |
| index(item):返回数据项在表中的位置 |
| insert(pos,item):将数据项插入到位置pos,假设item原先不存在于列表中,同时原列表具有足够多个数据项,能让item占据位置pos |
| pop():从列表末尾移除数据项,假设原列表至少有1个数据项 |
| pop(pos):移除位置为pos的数据项,假设原列表存在位置pos |
| 为了实现无需表数据结构,可以采用链接表的方案。 |
| 虽然列表数据结构要求保持数据项的前后相对位置,但这种前后位置的保持,并不要求数据项一次存放在连续的存储空间 |
| 如下图,数据项存放位置并没有规则,但如果在数据项之间建立连接指向,就可以保持其前后相对位置。 |
| 第一个和最后一个数据项需要现实标记出来,一个是队首,一个是队尾,后面再无数据了。 |

2.链表实现:节点Node
| 链表实现的最基本元素是节点Node: |
| 每个节点至少要包括2个信息:数据项本身,以及指向下一个节点的引用信息。 |
| 注意next为None的意义是没有下一个节点了,这个很重要。 |

| 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 |
| |
| temp = Node(93) |
| temp.getData() |
| 可以采用连接节点的方式构建数据集来实现无序表,链表的第一个和最后一个节点最重要。如果想访问到链表中的所有节点,就必须从第一个节点开始沿着链接遍历下去 |

| 所以无序表必须要有对第一个节点的引用信息。设立一个属性head,保存对第一个节点的引用空表的head为None。 |
| |
| class UnorderedList: |
| def __init__(self): |
| self.head = None |
| |
| mylist = UnorderedList() |
| print(mylist.head) |
| 随着数据项的加入,无序表的head始终指向链条中的第一个节点。注意!无序表mylist对象本身并不包含数据项(数据项在节点中),其中包含的head只是对首个节点Node的引用,判断空表的isEmpty()很容易实现。 |
| |
| return self.head == None |

| 接下来,考虑如何实现想无序表中添加数据项,实现add方法。由于无序表并没有限定数据项之间的顺序,新数据项可以加入到原表的任何位置,按照现实的性能考虑,应添加到最容易加入的位置上。由链表结构我们直到,要访问整条链上的所有数据项,都必须从表头head开始沿着next链接逐个向后查找,所以添加新数据项最快捷的位置是表头,整个链表的首位置。 |

| add方法按照有图的代码调用,形成的链表如下图: |
| 31是最先被加入的数据项,所以成为链表中最后一个项。而54是最后被加入的,是链表第一个数据项。 |
| mylist.add(54) |
| mylist.add(26) |
| mylist.add(93) |
| mylist.add(17) |
| mylist.add(77) |
| mylist.add(31) |

| size:从链条头head开始遍历到表尾同时用变量累加经过的节点个数。 |
| |
| def size(self): |
| current = self.head |
| count = 0 |
| while cuttent != None: |
| count = count +1 |
| current = current.getNext() |
| return count |

| 从链表头head开始遍历到表尾,同时判断当前节点的数据项是否目标 |
| |
| def search(self,item): |
| current =self.head |
| found = False |
| while current != None and not found: |
| if current.getData() == item: |
| found = True |
| else: |
| current = current.getNext() |
| |
| return found |

| remove(item)方法: |
| |
| 首先要找到item,这个过程跟search一样,但在删除节点时,需要特别的技巧。current指向的是当前匹配数据项的节点,而删除需要把前一个节点的next指向current的下一个节点。所以我们在search current的同时,还要维护前一个(previous)节点的引用。 |
| |

| 找到item之后,current指向item节点,previous指向前一个节点,开始执行删除,需要区分两种情况: |
| 1.current 是首个节点; |
| 2.或者是位于里那条中间的节点。 |

| 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 == None: |
| self.head = current.getNext() |
| else: |
| previous.setNext(current.getNext()) |
四、抽象数据类型:有序表OrderedList
| 有序表是一种数据项依照其某可比性质(如整数大小、字母表先后)来决定在列表中的位置。越“小”的数据项越靠近列表的头,越靠“前” |

| OrderedList():创建一个空的有序表 |
| add(item):在表中添加一个数据项,并保持整体顺序,此项原不存在 |
| remove(item):从有序表中移除一个数据项,此项应存在,有序表被修改 |
| search(item):在有序表中查找数据项,返回是否存在 |
| isEmpty():是否空表 |
| size():返回表中数据项的个数 |
| index(item):返回数据项在表中的位置,此项应存在 |
| pop():移除并返回有序表中最后一项,表中应至少存在一项 |
| pop(pos):移除并返回有序表中指定位置的数据项,此位置应存在。 |
| 以整数数据项为例,(17,26,31,54,77,93)的链表形式如图 |

| 有序表OrderedList实现,同样采用链表方法实现,Node定义相同,OrderedList也设置一个head来保存链表表头的引用 |
| |
| class OrderedList: |
| def __init__(self): |
| self.head = None |
| |
| 对于isEmpty/size/remove这些方法,与节点的次序无关,所以其实现跟UnorderdList是一样的。 |
| search/add方法则需要有修改 |
| |
| 在无序表的search中,如果需要查找的数据项不存在,则会从表头沿着next链接搜遍整个链表,直到表尾。对于有序表来说,可以利用链表节点有序排列的特性,来为search节省不存在数据项的查找时间。一旦当前节点的数据项大于所要查找的数据项,则说明链表后面已经不可能再有要查找的数据项,可以直接返回False。 |
| |
| def search(self,item): |
| current = self.head |
| found = False |
| stop = False |
| while current != None and not found and not stop: |
| if current.getData() == item: |
| found = True |
| else: |
| if current.getData() > item: |
| stop = True |
| else: |
| current = current.getNext() |
| |
| return found |
| |
| 相比无序表,改变最大的方法是add,因为add方法必须保证加入的数据项添加在合适的位置,以维护整个链表的有序性。 |
| 比如在(17,26,54,77,93)的有序表中,加入数据项31,我们需要沿着链表,找到第一个比31大的数据项54,将31插入到54的前面 |
| |
| 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 == None: |
| temp.setNext(self.head) |
| self.head = temp |
| else: |
| temp.setNext(current) |
| previous.setNext(temp) |

五、链表实现的算法分析
| 对于链表复杂度的分析,主要是看相应的方法是否设计到链表的遍历 |
| 对于一个包含节点数为n的链表: |
| isEmpty是O(1),因为仅需要检查head是否为None |
| size是O(n),因为除了遍历到表尾,没有其他办法得知节点的数量 |
| search/remove以及有序表的add方法,则是O(n),因为涉及到链表的遍历,按照概率其平均操作的次数是n/2,无序表的add方法是O(1),因为仅需要插入到表头。 |
| |
| 链表实现的List,跟Python内置的列表数据类型,在有些相同方法的实现上的时间复杂度不同。主要是因为python内置的列表数据类型是基于顺序存储来实现的,并进行了优化。 |
六、线性结构小结
| 线性数据结构Linear DS将数据项以某种线性的次序组织起来。 |
| 栈Stack维持了数据项后进先出LIFO的次序,stack的基本操作包括push,pop,isEmpty |
| 书写表达式的方法有前缀prefix、中缀infix和后缀postfix三种,由于栈结构具有次序反转的特性,所以栈结构适合用于开发表达式求值和转换的算法 |
| |
| 队列Queue维持了数据项先进先出FIFO的次序,queue的基本操作包括enqueue,dequeue,isEmpty |
| “模拟系统”可以通过一个对现实世界问题进行抽象建模,并加入随机数动态运行,为复杂问题的决策提供各种情况的参考,队列queue可以用来进行模拟系统的开发 |
| |
| |
| 双端队列Deque可以同时具备栈和队列的功能,deque的主要操作包括addFront,addRear,removeFront,removeRear,isEmpty |
| |
| 列表list是数据项能够维持相对位置的数据集,链表的实现,可以保持列表维持相对位置的特点,而不需要连续的存储空间 |
| |
| 链表实现时,其各种方法,对链表头部head需要特别的处理。 |
| |
七、双链表
| 双链表的每个节点有两个指针:一个指向后一个节点,另一个指向前一个节点。 |
| class Node: |
| def __init__(self,item=None): |
| self.item = item |
| self.next = None |
| self.prior = None |

| |
| 1. p.next = curNode.next |
| 2. curNode.next.prior = p |
| 3. p.prior = curNode |
| 4. curNode.next = p |

| |
| 1. p = curNode.next |
| 2. curNode.next = p.next |
| 3. p.next.prior = curNode |
| 4. del p |

1.多重链表、二叉链表
| |
| class StudentMListNode(object): |
| def __init__(self, data): |
| self.data = data |
| self.nextById = None |
| self.nextByName = None |

二叉链表:

2.用链表实现栈
| class LinkStack: |
| def __init__(self): |
| self.head = None |
| self.length = 0 |
| |
| def isEmpty(self): |
| return self.head is None |
| |
| def size(self): |
| return self.length |
| |
| def peek(self): |
| return self.head.getData() |
| |
| def push(self, item): |
| top = Node(item) |
| top.setNext(self.head) |
| self.head = top |
| self.length += 1 |
| |
| def pop(self): |
| top_item = self.head.getData() |
| self.head = self.head.getNext() |
| self.length -= 1 |
| return top_item |

3.链表实现队列
| class LinkQueue: |
| def __init__(self): |
| self.head = None |
| self.tail = None |
| self.length = 0 |
| |
| def isEmpty(self): |
| return self.head is None |
| |
| def size(self): |
| return self.length |
| |
| def enqueue(self, item): |
| tail = Node(item) |
| if self.head is None: |
| |
| self.head = self.tail = tail |
| else: |
| self.tail.setNext(tail) |
| self.tail = self.tail.getNext() |
| |
| self.length += 1 |
| |
| def dequeue(self): |
| top_item = self.head.getData() |
| if self.head == self.tail: |
| |
| self.head = self.tail = None |
| else: |
| self.head = self.head.getNext() |
| self.length -= 1 |
| return top_item |

4.双链无序表(双链表)

insert:

| class DoublyLinkedList: |
| def __init__(self,it = None): |
| self.head = None |
| self.tail = None |
| self.length = 0 |
| if it is not None: |
| for d in it: |
| self.append(d) |
| |
| def isEmpty(self): |
| return self.head is None |
| |
| def size(self): |
| return self.length |
| __len__ = size |
| |
| def getTail(self): |
| return self.tail |
| |
| def add(self,item): |
| idx0 = Node(item) |
| if self.head is None: |
| |
| self.head = self.tail = idx0 |
| else: |
| |
| self.head.setPrev(idx0) |
| idx0.setNext(self.head) |
| |
| self.length += 1 |
| |
| |
| def append(self,item): |
| idx_1 = Node(item) |
| if self.head is None: |
| |
| self.head = self.tail = idx_1 |
| else: |
| |
| self.tail.setNext(idx_1) |
| idx_1.setPrev(self.tail) |
| |
| self.length += 1 |
| |
| |
| |
| def insert(self,idx,item): |
| current, n = self.head, 0 |
| while n < idx: |
| current = current.getNext() |
| n += 1 |
| |
| if current is None: |
| if self.head is None: |
| |
| self.add(item) |
| else: |
| |
| self.append(item) |
| else: |
| |
| idx = Node(item) |
| idx.setNext(current) |
| idx.setPrev(current.getPrev()) |
| if idx.getPrev() is not None: |
| idx.getPrev().setNext(idx) |
| current.setPrev(idx) |
| |
| self.length += 1 |
| |
| |
| def index(self, item): |
| current, n = self.head, 0 |
| while current is not None: |
| if current.getData() == item: |
| break |
| current = current.getNext() |
| else: |
| return None |
| return n |
| |
| |
| def search(self, item): |
| return self.index(item) is not None |
| |
| |
| def delete(self, current): |
| |
| if self.head == current: |
| |
| self.head = current.getNext() |
| if self.tail == current: |
| |
| self.tail = current.getPrev() |
| if current.getPrev() is not None: |
| current.getPrev().setNext(current.getNext()) |
| if current.getNext() is not None: |
| current.getNext().setPrev(current.getPrev) |
| self.length += 1 |
| |
| |
| def remove(self, item): |
| current = self.head |
| while current is not None: |
| if current.getData() == item: |
| self.delete(current) |
| break |
| current = current.getNext() |
| |
| |
| |
| def pop(self, n = None): |
| if n is None: |
| n = self.length -1 |
| current, i = self.head,0 |
| while i < n: |
| current = current.getNext() |
| i += 1 |
| dat = current.getData() |
| self.delete(current) |
| return dat |
| |
| |
| def __str__(self): |
| tlist = [] |
| current = self.head |
| while current is not None: |
| tlist.append(current.getData()) |
| current = current.getNext() |
| return str(tlist) |
| __repr__ = __str__ |
| |
| |
| |
| def __getitem__(self, key): |
| if isinstance(key, int): |
| |
| current, i = self.head, 0 |
| while i < key: |
| current = current.getNext() |
| i += 1 |
| if current is not None: |
| return current.getData() |
| else: |
| raise StopIteration |
| elif isinstance(key,slice): |
| start = 0 if key.start is None else key.start |
| stop = self.length if key.stop is None else key.stop |
| step = 1 if key.step is None else key.step |
| current, i = self.head, 0 |
| |
| while i < start: |
| current = current.getNext() |
| i += 1 |
| |
| dcopy = DoublyLinkedList() |
| while i < stop: |
| dcopy.append(current.getData()) |
| s = step |
| while current is not None and s > 0: |
| current = current.getNext() |
| s -= 1 |
| i += step |
| |
| return dcopy |
| |
| def __eq__(self, other): |
| if other is None or not isinstance(other,DoublyLinkedList): |
| return False |
| if len(self) != len(other): |
| return False |
| |
| for s,o in zip(self, other): |
| if s != 0: |
| return False |
| else: |
| return True |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!