算法漫游指北(第五篇):栈、队列、栈结构实现、基于列表实现栈、基于链表实现栈、基于列表实现队列、基于链表实现队列
一、栈
栈(stack),有些地方称为堆栈,但是不能叫堆,是一种容器,可存入数据元素、访问元素、删除元素,它的特点在于只能允许在容器的一端(称为栈顶端指标,英语:top)进行加入数据(英语:push)和输出数据(英语:pop)的运算。
没有了位置概念,保证任何时候可以访问、删除的元素都是此前最后存入的那个元素,确定了一种默认的访问顺序。
注意:栈(stack),有些地方称为堆栈,可以叫栈,但是不能叫堆,堆是Heap,是另外一种数据结构。
由于栈数据结构只允许在一端进行操作,因而按照后进先出(LIFO, Last In First Out)的原理运作。
栈结构实现
栈可以用顺序表(数组)实现,也可以用链表实现。
对栈的两种主要操作是将一个元素压入栈和将一个元素弹出栈。入栈使用push()方法,出栈使用pop()方法。
另一个常用的操作是预览栈顶的元素。pop()方法虽然可以访问栈顶的元素,但是调用该方法后,栈顶元素也从栈中被永久性地删除了。peek()方法则只返回栈顶元素,而不删除它。
push()、pop()和peek()是栈的3个主要方法,但是栈还有其他方法和属性。
stack通常的操作:
Stack() 建立一个空的栈对象 push() 把一个元素添加到栈的最顶层 pop() 删除栈最顶层的元素,并返回这个元素 peek() 返回最顶层的元素,并不删除它 isEmpty() 判断栈是否为空 size() 返回栈中元素的个数
基于列表实现栈
数组栈是栈的一种实现方式,在C语言中以数组的形式实现,而在Python中,则可以使用与数组类似的列表进行实现。
class Stack(object): """栈""" def __init__(self): self.items = [] def is_empty(self): """判断是否为空""" return self.items == [] def push(self, item): """加入元素""" self.items.append(item) def pop(self): """弹出元素""" if self.is_empty(): raise IndexError,'pop from empty stack' return self.items.pop() def peek(self): """返回栈顶元素""" return self.items[len(self.items)-1] def size(self): """返回栈的大小""" return len(self.items) if __name__ == "__main__": stack = Stack() stack.push("hello") stack.push("world") print stack.size() print stack.peek() print stack.pop() print stack.pop() print stack.pop()
执行过程图
分析:用列表作为容器,按照顺序存入元素,将最新存入的元素作为top指针即下次最先被取出的元素。
基于链表实现栈
链表栈是以单链表为基础实现的栈数据结构,主要有以下几个关键点:
-
栈顶元素:栈顶元素即为链表的头结点
-
压栈:向链表的头结点插进入栈元素,无表头链表则替换插入元素为头结点
-
弹栈:弹出链表头结点,并将链表头结点替换为下一个元素
class Node: """链表节点类""" def __init__(self,val): self.val = val self.next = None def __str__(self): return str(self.val) class LinkedListStack: """链表结构堆栈类""" def __init__(self): """初始化堆栈的属性""" self.head = None self.top = self.head # 堆栈的顶端,当前表示堆栈为空 self.length = 0 def is_empty(self): """判断堆栈是否为空""" if self.top == None: return True else: return False def push(self, item): """向堆栈中存入数据""" new_node = Node(item) #创造节点类 new_node.next = self.top #设置node为栈顶 self.top = new_node #将设置过的栈顶的指针指向原栈顶 self.length += 1 print("成功存入数据",item) def pop(self): """从堆栈中取出数据""" if self.is_empty(): print("堆栈已空,不能再取数据") node = self.top # 先取到原栈顶 self.top = self.top.next # 将栈顶设置为原栈顶的下一个元素 self.length -= 1 print("取出数据",node.val) return node.val # 返回原栈顶的值 def peek(self): """获取栈顶元素""" if self.is_empty(): raise Exception("堆栈已空!") print('查看栈顶元素',self.top.val) return self.top.val def size(self): """得到栈的长度""" return self.length def show(self): #输出栈 def _traversal(self): node = self.top while node and node.next: yield node node = node.next yield node # 这里如果不yield,则栈底的元素会无法被遍历到,因为最后一个元素并不满足while循环的条件,会中止迭代 print('\n'.join(map(lambda x:'|{:^7}|'.format(str(x)),_traversal(self)))+ '\n' + 7*'-') if __name__ == '__main__': s = LinkedListStack() s.push(10) s.push(20) # s.pop() s.show() s.push(30) s.peek() s.show() s.pop() s.show()
链表实现栈动图
分析过程:
1、创建值为10的第一个节点,将新节点的next指向之前的self.top即None,设置新的top指针为新节点node(10)
2、创建第二个节点node(20),将第二个节点的next指向第一个节点,设置新的top指针为新节点node(20)
3、创建第三个节点node(30),将第三个节点的next指向第二个节点,设置新的top指针为新节点node(20)
其实这里类似链表的头插法,即在链表的头部插入链表节点
4、从栈里取值过程,找到现在的top即下一个要被取出的节点,将self.top设置为之前的一个,返回现在的top的值。
列表栈与链表栈
1、对于顺序表和单链表,前端插入和删除都是O(1)的时间复杂度。
2、在操作数较少的情况下,数组栈需要不停的拓展存储空间、转移元素,这样消耗时间比较多。
3、在操作数较大的情况下,链表栈需要不停的创建存储Node的内存空间,相对耗时(注意数组栈的扩容方式是乘以2哦),所以此时链表栈用时稍多。
栈的时间复杂度
方法 | 复杂度 |
---|---|
Access(取值) | O(n) |
Search | O(n) |
Insertion | O(1) |
Deletion | O(1) |
添加操作、删除操作时间复杂度皆为O(1)
二、队列
队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
队列是一种先进先出的(First In First Out)的线性表,简称FIFO。允许插入的一端为队尾,允许删除的一端为队头。队列不允许在中间部位进行操作!假设队列是q=(a1,a2,……,an),那么a1就是队头元素,而an是队尾元素。这样我们就可以删除时,总是从a1开始,而插入时,总是在队列最后。这也比较符合我们通常生活中的习惯,排在第一个的优先出列,最后来的当然排在队伍最后。
进行插入的一端成为队尾(rear),插入动作称为进队或入队。
进行删除的一端称为队头(front),删除动作称为出队。
队列的性质:先进先出(First-in, First-out)。
栈:后进先出(LIFO-last in first out):最后插入的元素最先出来。
队列:先进先出(FIFO-first in first out):最先插入的元素最先出来。
队列的实现
同栈一样,队列也可以用顺序表或者链表实现。
常见方法
- Queue() 创建一个空的队列 - enqueue(item) 往队列中添加一个item元素 - dequeue() 从队列头部删除一个元素 - is_empty() 判断一个队列是否为空 - size() 返回队列的大小
基于列表实现队列
class Queue(object): """队列""" def __init__(self): self.items = [] def is_empty(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) if __name__ == "__main__": q = Queue() q.enqueue("hello") q.enqueue("world") q.enqueue("nicholas") print q.size() print q.dequeue() print q.dequeue() print q.dequeue()
列表实现队列动图
分析:
1、队列以列表作为容器,按顺序添加元素,将新增加的元素按照顺序添加到之前的列表的后面,head计数
是下一个要取出元素的下标,tail计数是目前使用已经使用过的列表容量的下标。
2、添加元素时,按照列表申请的内存空间顺序添加,将head计数指向0
3、取出元素时,根据head计数来取出,同时head计数+1
4、当head计数与tail计数相等时,则队列为空
基于链表实现队列
class Node: """链表节点类""" def __init__(self,val): self.val = val self.next = None def __str__(self): return str(self.val) class LinkedListQueue: """链表结构堆栈类""" def __init__(self): """初始化队列的属性""" self.front = None #队列头部元素 self.rear = None #队尾元素 self.length = 0 #队列长度 def is_empty(self): """判断队列是否为空""" if self.front == None: return True else: return False def enqueue(self,item): """进队列,向队列中加入一个元素""" node=Node(item) if self.is_empty(): self.front = node else: self.rear.next=node self.rear = node self.length += 1 print('向队列中加入',item) def dequeue(self): """出队列,从队列中取出一个元素""" if self.front is None: raise "The queue is empty" elif self.front is self.rear: self.rear = None item = self.front.val self.front = self.front.next self.length -= 1 print("取出数据",item) return item def peek(self): """查看队列头部""" if self.is_empty(): raise Exception("队列已空!") print('查看队列头部元素',self.front.val) return self.front.val def size(self): """得到队列的长度""" return self.length def show(self): #显示队列 def _traversal(self): node = self.front while node and node.next: yield node node = node.next yield node # 这里如果不yield,则队列最后的元素会无法被遍历到,因为最后一个元素并不满足while循环的条件,会中止迭代 print('\n'.join(map(lambda x:'|{:^7}|'.format(str(x)),_traversal(self)))+ '\n' + 7*'-') if __name__ == '__main__': s = LinkedListQueue() s.enqueue(10) s.enqueue(20) s.enqueue(30) s.enqueue(40) s.enqueue(50) s.dequeue() s.show() s.enqueue(60) s.peek() s.show() s.dequeue() s.show()
链表实现队列动图
过程分析:
1、添加第一个节点node(10),head指针(即代码中的self.front)指向node(10),tail指针(即代码中的self.rear)也指向node(10)
2、添加第二个节点node(20),head指针指向node(10),
在enqueue方法中,由于队列不为空,则执行else语句,
执行self.rear.next=node语句,即node(10).next=node(20)
执行self.rear = node ,使tail指针指向node(20),self.rear = node(20)
3、添加第三个节点node(30),head指针仍然指向node(10),
执行self.rear.next=node语句,即node(20).next=node(30)
执行self.rear = node ,使得tail指针指向node(30),self.rear = node(30)
4、添加第四个节点node(40),head指针仍然指向node(10),
执行self.rear.next=node语句,即node(30).next=node(40)
执行self.rear = node ,使tail指针指向node(40),self.rear = node(40)
5、取出队列节点,执行dequeue方法
执行item = self.front.val,即head指针指向的node(10)的值,取出10
执行self.front = self.front.next,即将head指针指向下一个,当前的self.front=node(10),
node(10).next = node(20) ,所以这里执行的是self.front = node(20).
最后返回取出的节点的值。
队列时间复杂度
方法 | 复杂度 |
---|---|
Access | O(n) |
Search | O(n) |
Insertion | O(1) |
Deletion | O(1) |
posted on 2020-05-10 23:55 Nicholas-- 阅读(473) 评论(0) 编辑 收藏 举报