前篇讨论了一下具体的链表结构以及常规操作。现在我们趁热打铁,感觉再来熟悉一下具体的操作。此时我们用链表的形式来实现之前的顺序栈。
链栈
栈结构这里不多说,我们直接进入正题。先考虑,当栈为空的时候,如何表示?当然就是一个空指针了
将新元素推入堆栈时,该元素只会添加到链表的前端。因此,如果将元素e1 push到空栈上,该元素将存储在一个新单元格中,该单元格将成为链中唯一的链接:
将一个新元素push栈将该元素添加到链的开头。所涉及的步骤与将字符插入链表列表缓冲区所需的步骤相同。 首先分配一个新的单元格,然后输入数据,最后更新链接指针,使新单元格成为链中的第一个元素。 因此,如果将元素e2推到栈上,将得到以下配置:
在链表表示中,pop操作包括删除链中的第一个单元格并返回存储在该列中的值。因此,来自上图所示的栈的pop操作返回e2并恢复栈的先前状态,如下所示:
对链栈中的pop和push的分析
push
我们以一个最基本的数字栈为基础分析,下面是需要的一些接口内容:
class
接下来看代码分析:
void
老办法,图解push过程,假设此时我们的链栈如下:
我们的目的是push(7),将7放入栈顶,也就是要这样的结果:
下面解释整个过程:
1. 新建一个临时节点,并用创建临时指针指向该节点,接着向该节点写入数字7,就像这样:
2. 然后,将头指针data里面的指针值,赋值给新节点的link字段,以便新节点可以连接到8这个节点:
3. 将temp里面的值,赋值给首指针data处。将data的指向正确改变:
4. 函数返回,得出正确的push后的结构:
pop
先看代码:
int
图解过程如下: 1. 先将要pop出去的值进行保存
2. 我们这边创建一个临时的指针变量Temp,然后将其指向首指针data指向的节点(即栈顶元素),就像这样:
3. 然后改变首指针(即栈顶)的指向:
4. 最后删除temp指向的节点
5. 此时函数返回。
注意,我们第二步不可以直接改变指针的指向而不使用临时节点,因为直接改变指针的指向会像下图一样:
这显然不是我们希望看到的。
链队列
队列类也使用链表结构进行简单的表示。为了说明基本的方法,队列的元素存储在从队列头部开始并以尾部结尾的列表中。并且为了保证入队和出队正确运行,Queue对象必须含有指向队列两端的指针。 因此,私有实例变量的定义在queuepriv.h的修改版本如下:
/*
那么为什么我们要设置一个尾指针呢? 首先我们假设一下我们初始化的队列如下所示:
这个时候我们执行后入队操作:
显然,这个时候我们相当于在链表中插入一个元素,且位于头结点的第一个,自然我们的复杂度为O(1)。但是当我们执行出队操作时,情况就不同了,我们必须验证头指针遍历到最后一个节点,再执行delet操作,此时算法复杂度为O(N)。
入队(enqueue)
那么是否有更好的办法呢?因此我们考虑一下添加一个尾指针(因为队列多数在链表的两端操作)。所以我们看下面一段代码:
void
图解代码执行过程: 1. 假设我们初始的队列值为123,并且我们添加的尾指针指向的是我们的3的节点,如图:
2. 此时我们在队列尾部(3),这里加入数值4,此时,我们的操作应该是,建立一个指针,指向新建立的节点,然后往这个新节点写入数值:
3. 接下来我们改变指针的指向,也就是将cp的内容复制到3下面的link字段(也就是tail指向的节点的link字段)。此时情况如下(分两步):
4. 最终我们返回我们的队列就可以了,因此复杂度为O(1)
出队(dequeue)
我们就拿上面完成的例子为例,进行我们的dequeue操作,我们先看2 3行代码,建立一个临时节点,指向与我们的head指针的指向相同,如下: 首先假设我们有完整的队列
现在我们要进行的queue操作,于是将队头移除,以达到下图的结果:
同理,看代码:
int
- 我们先将此时队头指向的数字给存储起来,然后新建一个指针,跟队头指针一同指向首元素1.
- 然后改变头指针的指向
- 释放被dequeue掉的那个节点