对于一般线性表,虽然必须通过遍历逐一查找再对目标位置进行增、删和查操作,但至少一般线性表对于可操作元素并没有限制。说到这里,大家应该明白了,所谓的受限线性表,就是可操作元素受到了限制。
受限线性表可分为栈(Stack)和队列(Queue),如下图所示,这是比较特珠但很重要的数据结构,一定要掌握。
栈,讲究的是“先进后出”,即最先进栈的数据最后出栈。就像箱子,我们整理东西时,先放进箱于里的东西会被压在最下面,后放进箱子里的东西会被放在最上面,等到从箱子里往外拿东西时,需要先把上面的东西拿出来才能拿到箱子最底下的东西,这就叫“先进栈的后出栈,后进栈的先出栈”。这与线性表分为顺序表和链表一样,栈也分为顺序栈和链栈。
顺序栈也属于线性存储结构,而且和顺序表的数组结构极为相似,如下图所示。
可以看出,在这个数组中,我们先把1放了进去,然后依次是2、3和4,当我们要取出1时,必须先依次取出4、3和2。
链栈的原理和顺序核很相似。顺序栈是将顺序表的一端封死作为栈底,将另一端作为栈顶。链栈也是如此,它把链表一端(尾部)封死作为栈底,将另一端(头部)作为栈顶,如下图所示。
可以看出,链栈其实就是一个只能用头插法插入和删除元素的链表。问题来了:如此限制链表有什么好处呢?有!那就是可以提高效率。在我们只开放链表头部进行插入和删除元素的同时,避免了大量遍历链表所带来的耗时操作。
队列讲究的是“先进先出”,即最先进队列的数据最先出队列。队列就像一根吸管,队列里的元素就像珍珠奶茶里的珍珠——最先进入吸管的珍珠将最先离开吸管(当然是被你吃了)。这就叫“先进队的先出队,后进队的后出队”。
生活中的队列应用也很多,如排队买票。前面的人比你先到,因此他先买,然后才轮到你。队列和栈一样,也可以分为顺序队列和链式队列。
顺序队列其实就是在顺序表上实现队列结构。它和顺序栈的区别是,顺序栈是一边开口,而顺序队列是两边开口,如下图所示。
聪明的读者肯定会发现一个问题:顺序队列一直在往前“蹭”,前面的存储空间无法再次使用,这样会造成很大的空间浪费,而且还很容易导致数组溢出,引发错误。
那应该怎么办呢?读者肯定以为笔者要讲链式队列吧,然而并不是。不知读者是否还记得前面我们讲过的“环”?环就是解决这个问题的基本思路。我们完全可以用一个环状的顺序队列,把它的头尾巧妙地连接在一起,即可实现存储空间的循环使用,如下图所示。
剩下的就让 top 和rear 这两个指针像仓鼠玩跑轮一样去工作就可以了。
链式队列也叫链队列,可以说是单链表和顺序队列的结合体,它的初始状态如下图所示。
此时队列里什么都没有,因此top 和 rear 指针同时指向了头节点。当我们试图添加新的数据元素时,需要按照以下3 步来操作:
(1)创建一个该数据元素的新节点。
(2)将队尾指针 rear所指向的节点的指针指向新节点。
(3)将队尾指针 rear 指向新节点。
当我们试图删除数据元素时,可以按照以下3步来操作:
(1)创建一个新指针p指向将出队的节点(首元节点)。
(2)将头节点的指针指向p指针所指向节点的下一个节点。
(3)释放p指针所指向的节点,回收内存空间。
如果该链式队列没有头节点,则第(1)步应改为将队头指针 top指向p指针所指向节点的下一个节点。