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

一、栈

栈(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('\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)

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

posted on 2020-05-10 23:55  Nicholas--  阅读(473)  评论(0编辑  收藏  举报

导航