算法漫游指北(第五篇):栈队列栈结构实现基于列表实现栈基于链表实现栈基于列表实现队列基于链表实现队列

Posted nicholas0707

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法漫游指北(第五篇):栈队列栈结构实现基于列表实现栈基于链表实现栈基于列表实现队列基于链表实现队列相关的知识,希望对你有一定的参考价值。

一、栈

栈(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指针即下次最先被取出的元素。

 

基于链表实现栈

链表栈是以单链表为基础实现的栈数据结构,主要有以下几个关键点:

  1. 栈顶元素:栈顶元素即为链表的头结点

  2. 压栈:向链表的头结点插进入栈元素,无表头链表则替换插入元素为头结点

  3. 弹栈:弹出链表头结点,并将链表头结点替换为下一个元素

 

?
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(‘
‘.join(map(lambda x:‘|{:^7}|‘.format(str(x)),_traversal(self)))+ ‘
‘ + 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(‘
‘.join(map(lambda x:‘|{:^7}|‘.format(str(x)),_traversal(self)))+ ‘
‘ + 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)

添加操作、删除操作时间复杂度皆为O(1)

以上是关于算法漫游指北(第五篇):栈队列栈结构实现基于列表实现栈基于链表实现栈基于列表实现队列基于链表实现队列的主要内容,如果未能解决你的问题,请参考以下文章

数据结构初阶第五篇——栈和队列(实现+图解)

数据结构第五篇——栈和队列

数据结构第五篇——栈和队列

Python全栈开发记录_第五篇(装饰器)

算法漫游指北(第六篇)双端队列排序算法分类排序算法的稳定性排序算法复杂度

算法漫游指北(第六篇)双端队列排序算法分类排序算法的稳定性排序算法复杂度