上个文章介绍了栈的顺序存储结构,以及两栈共享的处理方式,接下来我们开始介绍线性结构的另一个储存方式,链式存储。如果你链表运用的不够熟练,那么接下来的内容会带你回顾链表的基本操作,以及栈的链式存储操作方法
链表是由一系列结点组成,对于每个结点结构,有数据域和指针域两个区域,数据域存放结点需要存储的具体数据,指针域存放指向下一个结点的指针。每个结点通过指针链接。就像一个导航,你在第一个结点,你知道第二个结点的地址,然后你到了第二个结点,你获得了第三个结点的地址……….
一般我们使用指向第一个结点的头指针作为整个链表头,通过头指针进行对链表的一系列操作
/*链栈的结点结构定义*/
struct Node {
T data;
Node *next;
};
对于栈这一数据结构,它是一个单向的线性结构,因此我们使用最简单的单链表即可实现,但是这时出现了一个问题,应该将哪端设为栈定,哪端设为栈底呢?可能你会习惯性的想将链表头做栈底,入栈则添加一个链表结点,并将栈顶指针后移。就像下图所示
这时出现了一个问题,我们在删除结点时需要将rear指针前移,但是单向链表只有后继指针,rear是无法向前访问的。或许你可以选择使用双向链表,但仅仅为了移动rear指针就额外添加内存开销并且增加了代码复杂程度,意义不大。
所以我们应该换种策略,令链表头作为栈顶,链表尾作为栈底。在栈底元素入栈时,以及栈底元素出栈时需要移动一次bottom指针。
这样设计更加直观便于理解,在空栈->非空栈,非空栈->空栈的操作时移动指针,其他情况只是对单链表的基本插入删除操作,因为链表的动态增删性,栈顶与栈底指针是不动的,这十分便于处理
下面是出入栈方法的具体实现
void Push(T e)
{
Node *q = new Node; //生成新结点
q->data = e;
if (rear == bottom) //空栈的情况需要移动bottom指针单独处理
{
rear->next = q;
bottom = q;
bottom->next = NULL;
}
/*普通的链表插入*/
else
{
Node *p = rear->next;
rear->next = q;
q->next = p;
}
length++;
}
void Pop(T &e)
{
e = rear->next->data;
/*只剩栈底元素的情况*/
if (rear->next == bottom)
{
delete bottom; bottom = rear; rear->next = NULL;
}
else
{
Node *p = rear->next;
rear->next = p->next;
delete p;
}
length--;
}
我们现在绍完了方法,那么栈具体有什么用呢?在学习程序设计的过程中,想必你一定写过递归函数吧,斐波那契数,汉诺塔等等。。。递归函数,就是函数自身调用自身的函数,递归函数必须有一个条件来时自身能够跳出函数防止无穷递归。
那么我们思考递归函数的递归过程,可以分为两个部分,第一个部分是逐层向下递归调用的过程,第二个是递归到达终点,逐层向上返回的过程。很显然,这两个过程是逆序的。在逐层递归是依次向下存储每层的数据,递归终点到达又逐层向上恢复。很显然这很符合栈这一数据结构的特性。 对于每一层递归,函数内的变量,参数,返回地址等信息入栈,返回过程再从栈顶出栈恢复。在程序设计语言中实现递归,就是栈这一数据结构十分具有意义的一个应用