使用抽象数据类型能够帮助咱们更好的理解数据所需的操做,以后再进行具体的数据类型实现。实际上,每每是操做影响着咱们决定数据类型该如何实现,这里有两种典型的数据结构-栈和队列。前端
本质上,栈和队列都是线性表,只是根据操做的需求咱们人为地在线性表上加上限制,造成了两种具备独特功能的数据结构。
算法
一、栈数组
首先,普通的线性表实现是有两个端口能够访问的,可是若是做为栈就要封闭一端,只能访问另外一端。这固然不是自讨苦吃,栈是一种抽象数据结构,是对现实世界对象的模拟。好比,自助餐厅中的一叠盘子,新盘子放在这一叠盘子的最上面,取得时候也是从最上面取。将其抽象出来就是栈,这是最合适的抽象方式。bash
基于栈的操做很是简单:数据结构
栈的实现不是难点,基于栈的操做也很简单,重点是栈的运用。post
动态图:spa
栈的先进后出规则,本质上表明着数据的次序,常见的二叉树先序、中序、后序非递归遍历,其实就是借用这种规则实现的。想要深刻理解栈,没有取巧的方法,见多识广用在这里再合适不过了,这里使用一个简单的例子来加深对栈的理解。设计
大整数加法,如今有个需求,将1856845129568452684和8948756841235879相加,怎么处理?这两个整数太大了,寻常的整数类型根本没法存储他们,更别说他们相加的结果。为了解决这个问题,能够将这种很是大的数当作一串数字,分别存到两个栈中,而后从栈中弹出数,进行加法操做。code
伪代码以下:cdn
largeNumAdd()
{
读第一个数的数字,并将这些数字压入到一个栈中;
读第二个数的数字,并将这些数字压入到另外一个栈中;
carry = 0; //表明进位
while(至少有一个栈不为空)
从每一个非空的栈中弹出一个数,将这两个数字与进位相加;
将和的个位数字压入到结果栈中;
将和的进位存到carry中;
若是进位不为0,将其压入到结果栈中;
从结果栈中弹出数字并显示;
}复制代码
简单起见,这里给出456和7891相加时栈的结构:
这不就是咱们学过的加法计算公式嘛,是的,这里使用栈模拟了加法过程。
将数字压入栈中,其实维持了千位、百位、十位、个位之间的次序,正是这个缘由才能保证栈弹出的时候数字相加是合理的。这只是栈简单的一种运用,在现实生活中,全部须要保持次序的数据,均可以使用栈这种先进后出的结构,经过巧妙的设计完成算法逻辑。
二、队列
队列是一种简单的等待序列,在尾部加入元素时队列加长,在前端删除数据时队列缩短。与栈不一样,队列是一种使用两端的结构:一端用来加入新元素,另外一端用来删除元素。队列是先进先出的结构。
队列的操做与栈操做类似:
动态图:
队列的实现:
队列的一种可能实现方式是使用数组,但这并不是最佳选择。元素从队尾加入而从队首删除,这会释放数组中的某些单元,这些单元不该该浪费。一种可能的作法是使用循环数组,若是队尾已满而队首有空的单元,能够将新加元素放入队首,造成循环数组,这种作法是空间比较紧张时的无奈之举,由于它破坏了队列的简单易用性,因此不推荐。
队列的另外一种可能实现是使用双向链表,那么执行入队列和出队列操做仅须要常数时间,而且没有数组实现中空间的浪费,所以,推荐这种方法。
队列的变种:
在许多状况下,简单的队列结构是不够的,先入先出机制须要使用某些优先规则来完善。在邮政局中,残疾人应该比其余人享有必定的优先权。在进程队列中,因为系统的功能需求,即便在等待队列中进程P1在P2以前,P2也须要在P1以前执行。以此类推,须要一种修正的队列,这就是所谓的优先队列。
优先队列能够用两种链表的变种实现。一种变种是全部的元素按照进入顺序排序,出队时按照优先级。另外一种是根据元素的优先级决定新增元素的位置。在这两种状况下,总的执行时间都是O(n)
,在标准库中使用后一种方式实现的,由于咱们但愿在元素出队时能够尽量的快。
顾名思义,双端队列就是能够在队列的两端压入、弹出元素。这就有问题了,双端队列和普通的数组、链表有什么区别?不均可以两端访问嘛。固然是有区别的,双端队列的产生是基于如下需求的。众所周知,数组和链表是线性表的两种实现方式,数组的优点在于能够常数时间内随机访问元素,链表的优点在于能够常数时间内在两端插入数据。那么,有没有一种实现方式能够综合这两个特色呢?答案是双端队列。
一切的奥妙在于双端队列的实现方式。首先从数组讲起,咱们定义了数组A,数组A自己是支持常数时间内随机访问元素的,可是若是在头部插入数据,就会形成大量元素后移,这是不能容忍的。怎么解决呢?那就再定义一个数组B,若是在A头部插入新元素a,就将a放到数组B的尾端,这时候数组A和数组B都是被封装在双端队列中的,而且双端队列维护了一段链式结构,其中每一个节点指向一个数组。看到这里想必你们已经明白,双端队列经过维护多个数组来避免头部插入操做形成的大量数据后移,尽管双端队列的实现比较复杂,可是做为使用者,既能够常数时间内随机访问元素,又能够常数时间内在队列两端插入数据,这对于某些场景下很是合适。
双端队列并不能取代数组和链表,由于数组和链表的实现简单、直观,能够知足大部分需求,只有在特殊场景下才去考虑双端队列,这就是所谓的对症下药。
三、标准库实现
这里简单介绍下标准库中的栈和队列。
在标准库中栈和队列是一种容器适配器。什么叫作容器适配器呢?其实就是拿一种已有的容器,在上面从新封装对外暴漏的接口,拼装成一种新的特殊容器。
标准库首先实现了双端队列,它是一种真正的容器,不是容器适配器。
标准库中的栈是一种容器适配器,默认是基于双端队列实现的,咱们在使用过程当中能够指定新的底层容器,好比向量或者链表。
标准库中的队列也是一种容器适配器,默认也是基于双端队列实现的,可是咱们只能选择链表做为新的底层容器,不能选择向量。这是由于队列是容许在头部删除数据的,而向量没有实现这种操做。
标准库中的优先队列也是一种容器适配器,默认是基于向量实现的,可是咱们也能选择使用双端队列做为底层容器。注意,这里不能选择链表,由于标准库中的优先队列要求底层容器提供随机访问迭代器,而链表并无提供。所谓的随机访问迭代器是指经过该迭代器能够访问容器中任一元素,而链表的迭代器只能自增或者自减,并不能随机访问任一元素。
到此为止,栈和队列的相关概念已经探讨完毕,本文也只是浅尝辄止,更加深刻的知识须要在实践中摸索获取,毕竟,成就在于我的。